mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 04:05:21 +00:00
Redesign: Permissions screen, battery optimization, location sheet (#415)
* update permissions screen design * battery optimization screen * remove init screen * skip init screen on load * skip on load only * location sheet design wip * denser location sheet * location sheet layout fix
This commit is contained in:
@@ -25,6 +25,7 @@ import com.bitchat.android.onboarding.BluetoothCheckScreen
|
|||||||
import com.bitchat.android.onboarding.BluetoothStatus
|
import com.bitchat.android.onboarding.BluetoothStatus
|
||||||
import com.bitchat.android.onboarding.BluetoothStatusManager
|
import com.bitchat.android.onboarding.BluetoothStatusManager
|
||||||
import com.bitchat.android.onboarding.BatteryOptimizationManager
|
import com.bitchat.android.onboarding.BatteryOptimizationManager
|
||||||
|
import com.bitchat.android.onboarding.BatteryOptimizationPreferenceManager
|
||||||
import com.bitchat.android.onboarding.BatteryOptimizationScreen
|
import com.bitchat.android.onboarding.BatteryOptimizationScreen
|
||||||
import com.bitchat.android.onboarding.BatteryOptimizationStatus
|
import com.bitchat.android.onboarding.BatteryOptimizationStatus
|
||||||
import com.bitchat.android.onboarding.InitializationErrorScreen
|
import com.bitchat.android.onboarding.InitializationErrorScreen
|
||||||
@@ -163,7 +164,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
when (onboardingState) {
|
when (onboardingState) {
|
||||||
OnboardingState.CHECKING -> {
|
OnboardingState.PERMISSION_REQUESTING -> {
|
||||||
InitializingScreen(modifier)
|
InitializingScreen(modifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,15 +228,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
OnboardingState.PERMISSION_REQUESTING -> {
|
OnboardingState.CHECKING, OnboardingState.INITIALIZING, OnboardingState.COMPLETE -> {
|
||||||
InitializingScreen(modifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
OnboardingState.INITIALIZING -> {
|
|
||||||
InitializingScreen(modifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
OnboardingState.COMPLETE -> {
|
|
||||||
// Set up back navigation handling for the chat screen
|
// Set up back navigation handling for the chat screen
|
||||||
val backCallback = object : OnBackPressedCallback(true) {
|
val backCallback = object : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
@@ -533,6 +526,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if user has previously skipped battery optimization
|
||||||
|
if (BatteryOptimizationPreferenceManager.isSkipped(this)) {
|
||||||
|
android.util.Log.d("MainActivity", "User previously skipped battery optimization, proceeding to permissions")
|
||||||
|
proceedWithPermissionCheck()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// For existing users, check battery optimization status
|
// For existing users, check battery optimization status
|
||||||
batteryOptimizationManager.logBatteryOptimizationStatus()
|
batteryOptimizationManager.logBatteryOptimizationStatus()
|
||||||
val currentBatteryOptimizationStatus = when {
|
val currentBatteryOptimizationStatus = when {
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package com.bitchat.android.onboarding
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference manager for battery optimization skip choice
|
||||||
|
*/
|
||||||
|
object BatteryOptimizationPreferenceManager {
|
||||||
|
private const val PREFS_NAME = "bitchat_settings"
|
||||||
|
private const val KEY_BATTERY_SKIP = "battery_optimization_skipped"
|
||||||
|
|
||||||
|
private val _skipFlow = MutableStateFlow(false)
|
||||||
|
val skipFlow: StateFlow<Boolean> = _skipFlow
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
val skipped = prefs.getBoolean(KEY_BATTERY_SKIP, false)
|
||||||
|
_skipFlow.value = skipped
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSkipped(context: Context, skipped: Boolean) {
|
||||||
|
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
prefs.edit().putBoolean(KEY_BATTERY_SKIP, skipped).apply()
|
||||||
|
_skipFlow.value = skipped
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSkipped(context: Context): Boolean {
|
||||||
|
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
return prefs.getBoolean(KEY_BATTERY_SKIP, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.bitchat.android.onboarding
|
|||||||
import androidx.compose.animation.core.*
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
@@ -12,11 +13,13 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import com.bitchat.android.R
|
import com.bitchat.android.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,10 +35,16 @@ fun BatteryOptimizationScreen(
|
|||||||
onSkip: () -> Unit,
|
onSkip: () -> Unit,
|
||||||
isLoading: Boolean = false
|
isLoading: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
|
// Initialize preference manager
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
BatteryOptimizationPreferenceManager.init(context)
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier.padding(32.dp),
|
modifier = modifier.padding(24.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
when (status) {
|
when (status) {
|
||||||
@@ -73,6 +82,8 @@ private fun BatteryOptimizationEnabledContent(
|
|||||||
colorScheme: ColorScheme,
|
colorScheme: ColorScheme,
|
||||||
isLoading: Boolean
|
isLoading: Boolean
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
@@ -82,78 +93,112 @@ private fun BatteryOptimizationEnabledContent(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(bottom = 16.dp),
|
.padding(bottom = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
) {
|
||||||
|
// Header Section - matching AboutSheet style
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "bitchat",
|
text = "bitchat",
|
||||||
style = MaterialTheme.typography.headlineLarge.copy(
|
style = MaterialTheme.typography.headlineLarge.copy(
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = colorScheme.primary
|
fontSize = 32.sp
|
||||||
)
|
),
|
||||||
)
|
color = colorScheme.onBackground
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.BatteryAlert,
|
|
||||||
contentDescription = "Battery Optimization",
|
|
||||||
modifier = Modifier.size(64.dp),
|
|
||||||
tint = colorScheme.error
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.battery_optimization_detected),
|
text = "battery optimization detected",
|
||||||
style = MaterialTheme.typography.headlineSmall.copy(
|
fontSize = 12.sp,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontFamily = FontFamily.Monospace,
|
||||||
color = colorScheme.onSurface
|
color = colorScheme.onBackground.copy(alpha = 0.7f)
|
||||||
),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
// Battery optimization info section
|
||||||
text = stringResource(R.string.battery_optimization_explanation),
|
Surface(
|
||||||
style = MaterialTheme.typography.bodyLarge.copy(
|
|
||||||
color = colorScheme.onSurfaceVariant
|
|
||||||
),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
color = colorScheme.surfaceVariant.copy(alpha = 0.25f),
|
||||||
containerColor = colorScheme.surfaceVariant.copy(alpha = 0.5f)
|
shape = RoundedCornerShape(12.dp)
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.Top,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Power,
|
||||||
|
contentDescription = "Battery Optimization",
|
||||||
|
tint = colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 2.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
)
|
||||||
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.battery_optimization_why_disable),
|
text = "Battery Optimization Enabled",
|
||||||
style = MaterialTheme.typography.titleSmall.copy(
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.Medium,
|
||||||
color = colorScheme.onSurface
|
color = colorScheme.onBackground
|
||||||
)
|
)
|
||||||
)
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.battery_optimization_benefits),
|
text = "bitchat needs to run in the background to maintain mesh connections. battery optimization can interrupt these connections.",
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = colorScheme.onSurfaceVariant
|
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
// Benefits section
|
||||||
text = stringResource(R.string.battery_optimization_note),
|
Surface(
|
||||||
style = MaterialTheme.typography.bodySmall.copy(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
color = colorScheme.onSurfaceVariant
|
color = colorScheme.surfaceVariant.copy(alpha = 0.25f),
|
||||||
),
|
shape = RoundedCornerShape(12.dp)
|
||||||
textAlign = TextAlign.Center
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.Top,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Benefits",
|
||||||
|
tint = colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 2.dp)
|
||||||
|
.size(20.dp)
|
||||||
)
|
)
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = "Benefits of Disabling",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = colorScheme.onBackground
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "• reliable message delivery\n• maintains mesh connectivity\n• prevents connection drops",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fixed buttons at the bottom
|
// Fixed buttons at the bottom
|
||||||
@@ -164,7 +209,10 @@ private fun BatteryOptimizationEnabledContent(
|
|||||||
Button(
|
Button(
|
||||||
onClick = onDisableBatteryOptimization,
|
onClick = onDisableBatteryOptimization,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = !isLoading
|
enabled = !isLoading,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = colorScheme.primary
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
@@ -174,7 +222,13 @@ private fun BatteryOptimizationEnabledContent(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
}
|
}
|
||||||
Text(stringResource(R.string.battery_optimization_disable_button))
|
Text(
|
||||||
|
text = "Disable Battery Optimization",
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -186,15 +240,28 @@ private fun BatteryOptimizationEnabledContent(
|
|||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.battery_optimization_check_again))
|
Text(
|
||||||
|
text = "Check Again",
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = onSkip,
|
onClick = {
|
||||||
|
BatteryOptimizationPreferenceManager.setSkipped(context, true)
|
||||||
|
onSkip()
|
||||||
|
},
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.battery_optimization_skip))
|
Text(
|
||||||
|
text = "Skip for Now",
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,7 +273,12 @@ private fun BatteryOptimizationCheckingContent(
|
|||||||
colorScheme: ColorScheme
|
colorScheme: ColorScheme
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(32.dp),
|
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
// Header Section - matching AboutSheet style
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -214,9 +286,18 @@ private fun BatteryOptimizationCheckingContent(
|
|||||||
style = MaterialTheme.typography.headlineLarge.copy(
|
style = MaterialTheme.typography.headlineLarge.copy(
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = colorScheme.primary
|
fontSize = 32.sp
|
||||||
|
),
|
||||||
|
color = colorScheme.onBackground
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "battery optimization disabled",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = colorScheme.onBackground.copy(alpha = 0.7f)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val infiniteTransition = rememberInfiniteTransition(label = "rotation")
|
val infiniteTransition = rememberInfiniteTransition(label = "rotation")
|
||||||
val rotation by infiniteTransition.animateFloat(
|
val rotation by infiniteTransition.animateFloat(
|
||||||
@@ -239,18 +320,10 @@ private fun BatteryOptimizationCheckingContent(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.battery_optimization_disabled),
|
text = "bitchat can run reliably in the background",
|
||||||
style = MaterialTheme.typography.headlineSmall.copy(
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontFamily = FontFamily.Monospace,
|
||||||
color = colorScheme.onSurface
|
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||||
),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.battery_optimization_success_message),
|
|
||||||
style = MaterialTheme.typography.bodyLarge.copy(
|
|
||||||
color = colorScheme.onSurfaceVariant
|
|
||||||
),
|
),
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
@@ -265,15 +338,29 @@ private fun BatteryOptimizationNotSupportedContent(
|
|||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
// Header Section - matching AboutSheet style
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "bitchat",
|
text = "bitchat",
|
||||||
style = MaterialTheme.typography.headlineLarge.copy(
|
style = MaterialTheme.typography.headlineLarge.copy(
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = colorScheme.primary
|
fontSize = 32.sp
|
||||||
|
),
|
||||||
|
color = colorScheme.onBackground
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "battery optimization not required",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = colorScheme.onBackground.copy(alpha = 0.7f)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.CheckCircle,
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
@@ -283,27 +370,28 @@ private fun BatteryOptimizationNotSupportedContent(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.battery_optimization_not_required),
|
text = "your device doesn't require battery optimization settings. bitchat will run normally.",
|
||||||
style = MaterialTheme.typography.headlineSmall.copy(
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontFamily = FontFamily.Monospace,
|
||||||
color = colorScheme.onSurface
|
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||||
),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.battery_optimization_not_supported_message),
|
|
||||||
style = MaterialTheme.typography.bodyLarge.copy(
|
|
||||||
color = colorScheme.onSurfaceVariant
|
|
||||||
),
|
),
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = onRetry,
|
onClick = onRetry,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = colorScheme.primary
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.battery_optimization_continue))
|
Text(
|
||||||
|
text = "Continue",
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,22 @@ package com.bitchat.android.onboarding
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
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
|
||||||
|
import androidx.compose.material.icons.filled.Notifications
|
||||||
|
import androidx.compose.material.icons.filled.Power
|
||||||
|
import androidx.compose.material.icons.filled.Security
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@@ -40,86 +50,87 @@ fun PermissionExplanationScreen(
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
// Header
|
|
||||||
|
// Header Section - matching AboutSheet style
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.Bottom,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Welcome to bitchat",
|
text = "bitchat",
|
||||||
style = MaterialTheme.typography.headlineMedium.copy(
|
style = MaterialTheme.typography.headlineLarge.copy(
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = colorScheme.primary
|
fontSize = 32.sp
|
||||||
),
|
),
|
||||||
textAlign = TextAlign.Center
|
color = colorScheme.onBackground
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "Decentralized mesh messaging over Bluetooth",
|
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
color = colorScheme.onSurface.copy(alpha = 0.7f)
|
|
||||||
),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Text(
|
||||||
|
text = "decentralized mesh messaging with end-to-end encryption",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = colorScheme.onBackground.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Privacy assurance section
|
// Privacy assurance section - matching AboutSheet card style
|
||||||
Card(
|
Surface(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
color = colorScheme.surfaceVariant.copy(alpha = 0.25f),
|
||||||
containerColor = colorScheme.surfaceVariant.copy(alpha = 0.3f)
|
shape = RoundedCornerShape(12.dp)
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.Top,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Icon(
|
||||||
text = "🔒",
|
imageVector = Icons.Filled.Security,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
contentDescription = "Privacy Protected",
|
||||||
modifier = Modifier.size(20.dp)
|
tint = colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 2.dp)
|
||||||
|
.size(20.dp)
|
||||||
)
|
)
|
||||||
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = "Your Privacy is Protected",
|
text = "Your Privacy is Protected",
|
||||||
style = MaterialTheme.typography.titleSmall.copy(
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = colorScheme.onSurface
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "• bitchat doesn't track you or collect personal data\n" +
|
|
||||||
"• Bluetooth mesh chats are fully offline and require no internet\n" +
|
|
||||||
"• Geohash chats use the internet but your location is generalized\n" +
|
|
||||||
"• Your messages stay on your device and peer devices only",
|
|
||||||
style = MaterialTheme.typography.bodySmall.copy(
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
color = colorScheme.onSurface.copy(alpha = 0.8f)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "To work properly, bitchat needs these permissions:",
|
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(
|
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
color = colorScheme.onSurface
|
color = colorScheme.onBackground
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "• no tracking or data collection\n" +
|
||||||
|
"• Bluetooth mesh chats are fully offline\n" +
|
||||||
|
"• Geohash chats use the internet",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section header
|
||||||
|
Text(
|
||||||
|
text = "permissions",
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
color = colorScheme.onBackground.copy(alpha = 0.7f),
|
||||||
|
modifier = Modifier.padding(top = 8.dp, bottom = 8.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Permission categories
|
// Permission categories
|
||||||
@@ -168,55 +179,46 @@ private fun PermissionCategoryCard(
|
|||||||
category: PermissionCategory,
|
category: PermissionCategory,
|
||||||
colorScheme: ColorScheme
|
colorScheme: ColorScheme
|
||||||
) {
|
) {
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = colorScheme.surface
|
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.Top,
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Icon(
|
||||||
text = getPermissionEmoji(category.type),
|
imageVector = getPermissionIcon(category.type),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
contentDescription = category.type.nameValue,
|
||||||
color = getPermissionIconColor(category.type),
|
tint = colorScheme.primary,
|
||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier
|
||||||
|
.padding(top = 2.dp)
|
||||||
|
.size(20.dp)
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = category.type.nameValue,
|
text = category.type.nameValue,
|
||||||
style = MaterialTheme.typography.titleSmall.copy(
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Medium,
|
||||||
color = colorScheme.onSurface
|
color = colorScheme.onBackground
|
||||||
)
|
)
|
||||||
)
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = category.description,
|
text = category.description,
|
||||||
style = MaterialTheme.typography.bodySmall.copy(
|
style = MaterialTheme.typography.bodySmall,
|
||||||
fontFamily = FontFamily.Monospace,
|
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||||
color = colorScheme.onSurface.copy(alpha = 0.8f),
|
|
||||||
lineHeight = 18.sp
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (category.type == PermissionType.PRECISE_LOCATION) {
|
if (category.type == PermissionType.PRECISE_LOCATION) {
|
||||||
// Extra emphasis for location permission
|
// Extra emphasis for location permission
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Icon(
|
||||||
text = "⚠️",
|
imageVector = Icons.Filled.Warning,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
contentDescription = "Warning",
|
||||||
|
tint = Color(0xFFFF9800),
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
@@ -233,22 +235,12 @@ private fun PermissionCategoryCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPermissionEmoji(permissionType: PermissionType): String {
|
private fun getPermissionIcon(permissionType: PermissionType): ImageVector {
|
||||||
return when (permissionType) {
|
return when (permissionType) {
|
||||||
PermissionType.NEARBY_DEVICES -> "📱"
|
PermissionType.NEARBY_DEVICES -> Icons.Filled.Bluetooth
|
||||||
PermissionType.PRECISE_LOCATION -> "📍"
|
PermissionType.PRECISE_LOCATION -> Icons.Filled.LocationOn
|
||||||
PermissionType.NOTIFICATIONS -> "🔔"
|
PermissionType.NOTIFICATIONS -> Icons.Filled.Notifications
|
||||||
PermissionType.BATTERY_OPTIMIZATION -> "🔋"
|
PermissionType.BATTERY_OPTIMIZATION -> Icons.Filled.Power
|
||||||
PermissionType.OTHER -> "🔧"
|
PermissionType.OTHER -> Icons.Filled.Settings
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPermissionIconColor(permissionType: PermissionType): Color {
|
|
||||||
return when (permissionType) {
|
|
||||||
PermissionType.NEARBY_DEVICES -> Color(0xFF2196F3) // Blue
|
|
||||||
PermissionType.PRECISE_LOCATION -> Color(0xFFFF9800) // Orange
|
|
||||||
PermissionType.NOTIFICATIONS -> Color(0xFF4CAF50) // Green
|
|
||||||
PermissionType.BATTERY_OPTIMIZATION -> Color(0xFFF44336) // Red
|
|
||||||
PermissionType.OTHER -> Color(0xFF9C27B0) // Purple
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.bitchat.android.ui
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@@ -73,12 +75,21 @@ fun LocationChannelsSheet(
|
|||||||
|
|
||||||
// Bottom sheet state
|
// Bottom sheet state
|
||||||
val sheetState = rememberModalBottomSheetState(
|
val sheetState = rememberModalBottomSheetState(
|
||||||
skipPartiallyExpanded = isInputFocused
|
skipPartiallyExpanded = true
|
||||||
)
|
)
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
// Scroll state for LazyColumn
|
// Scroll state for LazyColumn with animated top bar
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
val isScrolled by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
listState.firstVisibleItemIndex > 0 || listState.firstVisibleItemScrollOffset > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val topBarAlpha by animateFloatAsState(
|
||||||
|
targetValue = if (isScrolled) 0.95f else 0f,
|
||||||
|
label = "topBarAlpha"
|
||||||
|
)
|
||||||
|
|
||||||
val mapPickerLauncher = rememberLauncherForActivityResult(
|
val mapPickerLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
@@ -100,39 +111,54 @@ fun LocationChannelsSheet(
|
|||||||
|
|
||||||
if (isPresented) {
|
if (isPresented) {
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
|
modifier = modifier.statusBarsPadding(),
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
sheetState = sheetState,
|
sheetState = sheetState,
|
||||||
modifier = modifier
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
|
dragHandle = null
|
||||||
) {
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentPadding = PaddingValues(top = 48.dp, bottom = 16.dp)
|
||||||
|
) {
|
||||||
|
// Header Section
|
||||||
|
item(key = "header") {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.then(
|
.padding(horizontal = 24.dp)
|
||||||
if (isInputFocused) {
|
.padding(bottom = 8.dp),
|
||||||
Modifier.fillMaxHeight().padding(horizontal = 16.dp, vertical = 24.dp)
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
} else {
|
|
||||||
Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
) {
|
||||||
// Header
|
|
||||||
Text(
|
Text(
|
||||||
text = "#location channels",
|
text = "#location channels",
|
||||||
fontSize = 18.sp,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "chat with people near you using geohash channels. only a coarse geohash is shared, never exact gps. do not screenshot or share this screen to protect your privacy.",
|
text = "chat with people near you using geohash channels. only a coarse geohash is shared, never exact gps. do not screenshot or share this screen to protect your privacy.",
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
|
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Permission controls if services enabled
|
// Permission controls if services enabled
|
||||||
if (locationServicesEnabled) {
|
if (locationServicesEnabled) {
|
||||||
|
item(key = "permissions") {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
|
.padding(bottom = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
when (permissionState) {
|
when (permissionState) {
|
||||||
LocationChannelManager.PermissionState.NOT_DETERMINED -> {
|
LocationChannelManager.PermissionState.NOT_DETERMINED -> {
|
||||||
Button(
|
Button(
|
||||||
@@ -152,7 +178,7 @@ fun LocationChannelsSheet(
|
|||||||
}
|
}
|
||||||
LocationChannelManager.PermissionState.DENIED,
|
LocationChannelManager.PermissionState.DENIED,
|
||||||
LocationChannelManager.PermissionState.RESTRICTED -> {
|
LocationChannelManager.PermissionState.RESTRICTED -> {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = "location permission denied. enable in settings to use location channels.",
|
text = "location permission denied. enable in settings to use location channels.",
|
||||||
fontSize = 11.sp,
|
fontSize = 11.sp,
|
||||||
@@ -185,7 +211,7 @@ fun LocationChannelsSheet(
|
|||||||
}
|
}
|
||||||
null -> {
|
null -> {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator(modifier = Modifier.size(12.dp))
|
CircularProgressIndicator(modifier = Modifier.size(12.dp))
|
||||||
@@ -199,15 +225,11 @@ fun LocationChannelsSheet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Channel list (iOS-style plain list)
|
|
||||||
|
|
||||||
LazyColumn(
|
|
||||||
state = listState,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
// Mesh option first
|
// Mesh option first
|
||||||
item {
|
item(key = "mesh") {
|
||||||
ChannelRow(
|
ChannelRow(
|
||||||
title = meshTitleWithCount(viewModel),
|
title = meshTitleWithCount(viewModel),
|
||||||
subtitle = "#bluetooth • ${bluetoothRangeString()}",
|
subtitle = "#bluetooth • ${bluetoothRangeString()}",
|
||||||
@@ -259,7 +281,7 @@ fun LocationChannelsSheet(
|
|||||||
} else if (permissionState == LocationChannelManager.PermissionState.AUTHORIZED && locationServicesEnabled) {
|
} else if (permissionState == LocationChannelManager.PermissionState.AUTHORIZED && locationServicesEnabled) {
|
||||||
item {
|
item {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator(modifier = Modifier.size(16.dp))
|
CircularProgressIndicator(modifier = Modifier.size(16.dp))
|
||||||
@@ -274,13 +296,16 @@ fun LocationChannelsSheet(
|
|||||||
|
|
||||||
// Bookmarked geohashes
|
// Bookmarked geohashes
|
||||||
if (bookmarks.isNotEmpty()) {
|
if (bookmarks.isNotEmpty()) {
|
||||||
item {
|
item(key = "bookmarked_header") {
|
||||||
Text(
|
Text(
|
||||||
text = "bookmarked",
|
text = "bookmarked",
|
||||||
fontSize = 12.sp,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
|
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f),
|
||||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp)
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
|
.padding(top = 8.dp, bottom = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(bookmarks) { gh ->
|
items(bookmarks) { gh ->
|
||||||
@@ -325,16 +350,19 @@ fun LocationChannelsSheet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom geohash teleport (iOS-style inline form)
|
// Custom geohash teleport (iOS-style inline form)
|
||||||
item {
|
item(key = "custom_geohash") {
|
||||||
Surface(
|
Surface(
|
||||||
|
color = Color.Transparent,
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 24.dp, vertical = 2.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 6.dp),
|
.padding(horizontal = 16.dp, vertical = 6.dp),
|
||||||
color = Color.Transparent
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(1.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -435,7 +463,7 @@ fun LocationChannelsSheet(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -453,21 +481,32 @@ fun LocationChannelsSheet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
customError?.let { error ->
|
// Error message for custom geohash
|
||||||
|
if (customError != null) {
|
||||||
|
item(key = "geohash_error") {
|
||||||
Text(
|
Text(
|
||||||
text = error,
|
text = customError!!,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
color = Color.Red
|
color = Color.Red,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Location services toggle button
|
// Location services toggle button
|
||||||
item {
|
item(key = "location_toggle") {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (locationServicesEnabled) {
|
if (locationServicesEnabled) {
|
||||||
@@ -503,6 +542,29 @@ fun LocationChannelsSheet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TopBar (animated)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(56.dp)
|
||||||
|
.background(MaterialTheme.colorScheme.background.copy(alpha = topBarAlpha))
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = onDismiss,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterEnd)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Close",
|
||||||
|
style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.Bold),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,18 +619,20 @@ private fun ChannelRow(
|
|||||||
Color.Transparent
|
Color.Transparent
|
||||||
},
|
},
|
||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.medium,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 24.dp, vertical = 2.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
.padding(horizontal = 16.dp, vertical = 6.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||||
) {
|
) {
|
||||||
// Split title to handle count part with smaller font (iOS style)
|
// Split title to handle count part with smaller font (iOS style)
|
||||||
val (baseTitle, countSuffix) = splitTitleAndCount(title)
|
val (baseTitle, countSuffix) = splitTitleAndCount(title)
|
||||||
|
|||||||
Reference in New Issue
Block a user