15 changed files with 1747 additions and 1199 deletions
@ -1,27 +1,616 @@ |
|||||
package com.example.dattingapp |
import androidx.compose.foundation.BorderStroke |
||||
|
import androidx.compose.foundation.background |
||||
import android.os.Bundle |
import androidx.compose.foundation.border |
||||
import androidx.activity.ComponentActivity |
import androidx.compose.foundation.layout.* |
||||
import androidx.activity.compose.setContent |
import androidx.compose.foundation.lazy.LazyColumn |
||||
import androidx.activity.enableEdgeToEdge |
import androidx.compose.foundation.lazy.LazyRow |
||||
import androidx.compose.foundation.layout.fillMaxSize |
import androidx.compose.foundation.lazy.items |
||||
import androidx.compose.foundation.layout.padding |
import androidx.compose.foundation.shape.CircleShape |
||||
import androidx.compose.material3.Scaffold |
import androidx.compose.foundation.shape.RoundedCornerShape |
||||
import androidx.compose.material3.Text |
import androidx.compose.material.icons.Icons |
||||
|
import androidx.compose.material.icons.filled.* |
||||
|
import androidx.compose.material3.* |
||||
import androidx.compose.runtime.Composable |
import androidx.compose.runtime.Composable |
||||
|
import androidx.compose.ui.Alignment |
||||
import androidx.compose.ui.Modifier |
import androidx.compose.ui.Modifier |
||||
|
import androidx.compose.ui.draw.clip |
||||
|
import androidx.compose.ui.graphics.Color |
||||
|
import androidx.compose.ui.text.font.FontWeight |
||||
import androidx.compose.ui.tooling.preview.Preview |
import androidx.compose.ui.tooling.preview.Preview |
||||
import com.example.dattingapp.ui.theme.DattingAPPTheme |
import androidx.compose.ui.unit.dp |
||||
|
import androidx.compose.ui.unit.sp |
||||
|
|
||||
class ChatScreen : ComponentActivity() { |
// Data Models |
||||
override fun onCreate(savedInstanceState: Bundle?) { |
data class ActiveSpark( |
||||
super.onCreate(savedInstanceState) |
val name: String, |
||||
enableEdgeToEdge() |
val isUser: Boolean = false |
||||
setContent { |
) |
||||
DattingAPPTheme { |
|
||||
|
|
||||
|
data class ChatMessage( |
||||
|
val id: Int, |
||||
|
val name: String, |
||||
|
val age: Int, |
||||
|
val time: String, |
||||
|
val isVerified: Boolean = false, |
||||
|
val matchPercentage: Int? = null, |
||||
|
val messageText: String? = null, |
||||
|
val isVoiceNote: Boolean = false, |
||||
|
val voiceDuration: String? = null, |
||||
|
val isPhoto: Boolean = false, |
||||
|
val photoText: String? = null, |
||||
|
val isAiBot: Boolean = false, |
||||
|
val aiTag: String? = null, |
||||
|
val unreadCount: Int = 0, |
||||
|
val isPinned: Boolean = false, |
||||
|
val isOnline: Boolean = false |
||||
|
) |
||||
|
|
||||
|
@OptIn(ExperimentalMaterial3Api::class) |
||||
|
@Composable |
||||
|
fun ChatScreen(modifier: Modifier = Modifier) { |
||||
|
Scaffold( |
||||
|
topBar = { |
||||
|
Column( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.background(Color.White) |
||||
|
.statusBarsPadding() |
||||
|
) { |
||||
|
// Header |
||||
|
Row( |
||||
|
modifier = Modifier.fillMaxWidth(), |
||||
|
horizontalArrangement = Arrangement.SpaceBetween, |
||||
|
verticalAlignment = Alignment.CenterVertically |
||||
|
) { |
||||
|
Row( |
||||
|
verticalAlignment = Alignment.CenterVertically, |
||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp) |
||||
|
) { |
||||
|
Row( |
||||
|
verticalAlignment = Alignment.CenterVertically, |
||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
"Messages", |
||||
|
fontSize = 22.sp, |
||||
|
fontWeight = FontWeight.Bold |
||||
|
) |
||||
|
Surface( |
||||
|
color = Color(0xFFE91E63), |
||||
|
shape = RoundedCornerShape(12.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
"3 new", |
||||
|
color = Color.White, |
||||
|
fontSize = 11.sp, |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Row( |
||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp), |
||||
|
verticalAlignment = Alignment.CenterVertically |
||||
|
) { |
||||
|
IconButton( |
||||
|
onClick = {}, |
||||
|
modifier = Modifier |
||||
|
.size(36.dp) |
||||
|
.background(Color(0xFFF5F5F5), CircleShape) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.Shield, |
||||
|
contentDescription = "Safety", |
||||
|
tint = Color.Gray, |
||||
|
modifier = Modifier.size(18.dp) |
||||
|
) |
||||
|
} |
||||
|
IconButton( |
||||
|
onClick = {}, |
||||
|
modifier = Modifier |
||||
|
.size(36.dp) |
||||
|
.background(Color(0xFFF5F5F5), CircleShape) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.Search, |
||||
|
contentDescription = "Search", |
||||
|
tint = Color.Gray, |
||||
|
modifier = Modifier.size(18.dp) |
||||
|
) |
||||
|
} |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.size(36.dp) |
||||
|
.clip(CircleShape) |
||||
|
.background(Color(0xFFCE93D8)) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Spacer(modifier = Modifier.height(12.dp)) |
||||
|
|
||||
|
// Messages Title Bar |
||||
|
|
||||
|
|
||||
|
} |
||||
|
}, |
||||
|
modifier = modifier.fillMaxSize() |
||||
|
) { innerPadding -> |
||||
|
LazyColumn( |
||||
|
modifier = Modifier |
||||
|
.fillMaxSize() |
||||
|
.background(Color(0xFFFAFAFA)), |
||||
|
contentPadding = PaddingValues( |
||||
|
top = innerPadding.calculateTopPadding(), |
||||
|
bottom = innerPadding.calculateBottomPadding() + 16.dp |
||||
|
) |
||||
|
) { |
||||
|
// Search Bar |
||||
|
item { |
||||
|
OutlinedTextField( |
||||
|
value = "", |
||||
|
onValueChange = {}, |
||||
|
placeholder = { Text("Search chats or matches...", fontSize = 14.sp) }, |
||||
|
leadingIcon = { |
||||
|
Icon( |
||||
|
Icons.Default.Search, |
||||
|
contentDescription = null, |
||||
|
tint = Color.Gray |
||||
|
) |
||||
|
}, |
||||
|
trailingIcon = { |
||||
|
Icon( |
||||
|
Icons.Default.Mic, |
||||
|
contentDescription = null, |
||||
|
tint = Color.Gray |
||||
|
) |
||||
|
}, |
||||
|
shape = RoundedCornerShape(24.dp), |
||||
|
colors = OutlinedTextFieldDefaults.colors( |
||||
|
focusedBorderColor = Color(0xFFFFCDD2), |
||||
|
unfocusedBorderColor = Color(0xFFFFCDD2), |
||||
|
focusedContainerColor = Color.White, |
||||
|
unfocusedContainerColor = Color.White |
||||
|
), |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(horizontal = 16.dp, vertical = 8.dp) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// Active Sparks & Vibes Row |
||||
|
item { |
||||
|
Column(modifier = Modifier.padding(vertical = 8.dp)) { |
||||
|
Row( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(horizontal = 16.dp), |
||||
|
horizontalArrangement = Arrangement.SpaceBetween, |
||||
|
verticalAlignment = Alignment.CenterVertically |
||||
|
) { |
||||
|
Text( |
||||
|
"Active Sparks & Vibes", |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
fontSize = 14.sp |
||||
|
) |
||||
|
Text( |
||||
|
"View all", |
||||
|
color = Color(0xFFE91E63), |
||||
|
fontSize = 12.sp, |
||||
|
fontWeight = FontWeight.SemiBold |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
Spacer(modifier = Modifier.height(8.dp)) |
||||
|
|
||||
|
LazyRow( |
||||
|
contentPadding = PaddingValues(horizontal = 16.dp), |
||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp) |
||||
|
) { |
||||
|
items(sampleSparks) { spark -> |
||||
|
ActiveSparkItem(spark = spark) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Filter Chips |
||||
|
item { |
||||
|
Row( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(horizontal = 16.dp, vertical = 8.dp), |
||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp) |
||||
|
) { |
||||
|
Button( |
||||
|
onClick = {}, |
||||
|
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF9C27B0)), |
||||
|
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 6.dp), |
||||
|
modifier = Modifier.height(32.dp) |
||||
|
) { |
||||
|
Text("All", fontSize = 12.sp) |
||||
|
} |
||||
|
|
||||
|
OutlinedButton( |
||||
|
onClick = {}, |
||||
|
border = BorderStroke(1.dp, Color(0xFFFFCDD2)), |
||||
|
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 6.dp), |
||||
|
modifier = Modifier.height(32.dp) |
||||
|
) { |
||||
|
Text("Unread", color = Color.Black, fontSize = 12.sp) |
||||
|
Spacer(modifier = Modifier.width(4.dp)) |
||||
|
Text( |
||||
|
"3", |
||||
|
color = Color(0xFFE91E63), |
||||
|
fontSize = 11.sp, |
||||
|
fontWeight = FontWeight.Bold |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
OutlinedButton( |
||||
|
onClick = {}, |
||||
|
border = BorderStroke(1.dp, Color(0xFFFFCDD2)), |
||||
|
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 6.dp), |
||||
|
modifier = Modifier.height(32.dp) |
||||
|
) { |
||||
|
Text("Matches", color = Color.Black, fontSize = 12.sp) |
||||
|
} |
||||
|
|
||||
|
OutlinedButton( |
||||
|
onClick = {}, |
||||
|
border = BorderStroke(1.dp, Color(0xFFFFCDD2)), |
||||
|
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 6.dp), |
||||
|
modifier = Modifier.height(32.dp) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.FlashOn, |
||||
|
contentDescription = null, |
||||
|
tint = Color(0xFFE91E63), |
||||
|
modifier = Modifier.size(14.dp) |
||||
|
) |
||||
|
Spacer(modifier = Modifier.width(2.dp)) |
||||
|
Text("Vibes", color = Color.Black, fontSize = 12.sp) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Chat List Items |
||||
|
items(sampleChats) { chat -> |
||||
|
ChatItemCard(chat = chat) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Composable |
||||
|
fun ActiveSparkItem(spark: ActiveSpark) { |
||||
|
Column( |
||||
|
horizontalAlignment = Alignment.CenterHorizontally, |
||||
|
verticalArrangement = Arrangement.spacedBy(4.dp) |
||||
|
) { |
||||
|
Box(contentAlignment = Alignment.BottomEnd) { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.size(56.dp) |
||||
|
.clip(CircleShape) |
||||
|
.background(Color(0xFFCE93D8)) |
||||
|
.border( |
||||
|
width = 2.dp, |
||||
|
color = if (spark.isUser) Color.Transparent else Color(0xFFE91E63), |
||||
|
shape = CircleShape |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
if (spark.isUser) { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.size(18.dp) |
||||
|
.background(Color(0xFFE91E63), CircleShape) |
||||
|
.border(1.5.dp, Color.White, CircleShape), |
||||
|
contentAlignment = Alignment.Center |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.Add, |
||||
|
contentDescription = "Add", |
||||
|
tint = Color.White, |
||||
|
modifier = Modifier.size(12.dp) |
||||
|
) |
||||
|
} |
||||
|
} else { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.size(12.dp) |
||||
|
.background(Color(0xFF4CAF50), CircleShape) |
||||
|
.border(1.5.dp, Color.White, CircleShape) |
||||
|
) |
||||
} |
} |
||||
} |
} |
||||
|
Text( |
||||
|
text = spark.name, |
||||
|
fontSize = 11.sp, |
||||
|
fontWeight = FontWeight.Medium |
||||
|
) |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
|
@Composable |
||||
|
fun ChatItemCard(chat: ChatMessage) { |
||||
|
Card( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(horizontal = 16.dp, vertical = 4.dp), |
||||
|
shape = RoundedCornerShape(16.dp), |
||||
|
colors = CardDefaults.cardColors(containerColor = Color.White), |
||||
|
border = BorderStroke(1.dp, Color(0xFFFFEBEE)) |
||||
|
) { |
||||
|
Row( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(12.dp), |
||||
|
verticalAlignment = Alignment.CenterVertically |
||||
|
) { |
||||
|
// Profile Avatar Box |
||||
|
Box(modifier = Modifier.size(52.dp)) { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.fillMaxSize() |
||||
|
.clip(RoundedCornerShape(12.dp)) |
||||
|
.background(Color(0xFFBA68C8)) |
||||
|
) |
||||
|
|
||||
|
if (chat.matchPercentage != null) { |
||||
|
Surface( |
||||
|
color = Color(0xFF9C27B0), |
||||
|
shape = RoundedCornerShape(8.dp), |
||||
|
modifier = Modifier.align(Alignment.BottomStart) |
||||
|
) { |
||||
|
Text( |
||||
|
text = "${chat.matchPercentage}%", |
||||
|
color = Color.White, |
||||
|
fontSize = 8.sp, |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (chat.isOnline) { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.size(10.dp) |
||||
|
.background(Color(0xFF4CAF50), CircleShape) |
||||
|
.border(1.dp, Color.White, CircleShape) |
||||
|
.align(Alignment.BottomEnd) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
if (chat.isAiBot) { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.size(16.dp) |
||||
|
.background(Color(0xFF9C27B0), CircleShape) |
||||
|
.align(Alignment.TopEnd), |
||||
|
contentAlignment = Alignment.Center |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.AutoAwesome, |
||||
|
contentDescription = null, |
||||
|
tint = Color.White, |
||||
|
modifier = Modifier.size(10.dp) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Spacer(modifier = Modifier.width(12.dp)) |
||||
|
|
||||
|
// Details Section |
||||
|
Column( |
||||
|
modifier = Modifier.weight(1f), |
||||
|
verticalArrangement = Arrangement.spacedBy(4.dp) |
||||
|
) { |
||||
|
Row( |
||||
|
verticalAlignment = Alignment.CenterVertically, |
||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
text = if (chat.age > 0) "${chat.name}, ${chat.age}" else chat.name, |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
fontSize = 15.sp |
||||
|
) |
||||
|
if (chat.isVerified) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.CheckCircle, |
||||
|
contentDescription = "Verified", |
||||
|
tint = Color(0xFFE91E63), |
||||
|
modifier = Modifier.size(14.dp) |
||||
|
) |
||||
|
} |
||||
|
if (chat.aiTag != null) { |
||||
|
Surface( |
||||
|
color = Color(0xFFFFEBEE), |
||||
|
shape = RoundedCornerShape(4.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
text = chat.aiTag, |
||||
|
color = Color(0xFFE91E63), |
||||
|
fontSize = 9.sp, |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Message Content |
||||
|
when { |
||||
|
chat.messageText != null -> { |
||||
|
Text( |
||||
|
text = chat.messageText, |
||||
|
fontSize = 12.sp, |
||||
|
color = Color.Gray, |
||||
|
maxLines = 1 |
||||
|
) |
||||
|
} |
||||
|
chat.isVoiceNote -> { |
||||
|
Row( |
||||
|
verticalAlignment = Alignment.CenterVertically, |
||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.Mic, |
||||
|
contentDescription = null, |
||||
|
tint = Color(0xFFE91E63), |
||||
|
modifier = Modifier.size(14.dp) |
||||
|
) |
||||
|
Surface( |
||||
|
color = Color(0xFFFFEBEE), |
||||
|
shape = RoundedCornerShape(8.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
text = "|||!|!|| ${chat.voiceDuration}", |
||||
|
color = Color(0xFFE91E63), |
||||
|
fontSize = 10.sp, |
||||
|
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp) |
||||
|
) |
||||
|
} |
||||
|
Text( |
||||
|
text = "Voice note", |
||||
|
fontSize = 12.sp, |
||||
|
fontWeight = FontWeight.SemiBold |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
chat.isPhoto -> { |
||||
|
Row( |
||||
|
verticalAlignment = Alignment.CenterVertically, |
||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.CameraAlt, |
||||
|
contentDescription = null, |
||||
|
tint = Color(0xFFE91E63), |
||||
|
modifier = Modifier.size(14.dp) |
||||
|
) |
||||
|
Text( |
||||
|
text = chat.photoText ?: "Photo", |
||||
|
fontSize = 12.sp, |
||||
|
color = Color.Gray |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Time & Status Section |
||||
|
Column( |
||||
|
horizontalAlignment = Alignment.End, |
||||
|
verticalArrangement = Arrangement.spacedBy(4.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
text = chat.time, |
||||
|
fontSize = 11.sp, |
||||
|
color = if (chat.unreadCount > 0) Color(0xFFE91E63) else Color.Gray |
||||
|
) |
||||
|
|
||||
|
Row( |
||||
|
verticalAlignment = Alignment.CenterVertically, |
||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp) |
||||
|
) { |
||||
|
if (chat.isPinned) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.PushPin, |
||||
|
contentDescription = "Pinned", |
||||
|
tint = Color(0xFFE91E63), |
||||
|
modifier = Modifier.size(12.dp) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
if (chat.unreadCount > 0) { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.size(18.dp) |
||||
|
.background(Color(0xFF9C27B0), CircleShape), |
||||
|
contentAlignment = Alignment.Center |
||||
|
) { |
||||
|
Text( |
||||
|
text = chat.unreadCount.toString(), |
||||
|
color = Color.White, |
||||
|
fontSize = 10.sp, |
||||
|
fontWeight = FontWeight.Bold |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Sample Data |
||||
|
val sampleSparks = listOf( |
||||
|
ActiveSpark("Your Note", isUser = true), |
||||
|
ActiveSpark("Elena"), |
||||
|
ActiveSpark("Sarah"), |
||||
|
ActiveSpark("Jordan"), |
||||
|
ActiveSpark("Marcus") |
||||
|
) |
||||
|
|
||||
|
val sampleChats = listOf( |
||||
|
ChatMessage( |
||||
|
id = 1, |
||||
|
name = "Elena", |
||||
|
age = 24, |
||||
|
time = "12:45 PM", |
||||
|
isVerified = true, |
||||
|
matchPercentage = 98, |
||||
|
messageText = "Are you free for that coffee in ...", |
||||
|
unreadCount = 2, |
||||
|
isPinned = true, |
||||
|
isOnline = true |
||||
|
), |
||||
|
ChatMessage( |
||||
|
id = 2, |
||||
|
name = "Sarah", |
||||
|
age = 26, |
||||
|
time = "11:30 AM", |
||||
|
isVoiceNote = true, |
||||
|
voiceDuration = "0:24", |
||||
|
unreadCount = 1, |
||||
|
isOnline = true |
||||
|
), |
||||
|
ChatMessage( |
||||
|
id = 3, |
||||
|
name = "Jordan", |
||||
|
age = 27, |
||||
|
time = "Yesterday", |
||||
|
isPhoto = true, |
||||
|
photoText = "Photo: Check out this spot!" |
||||
|
), |
||||
|
ChatMessage( |
||||
|
id = 4, |
||||
|
name = "VibeBot Wingmate", |
||||
|
age = 0, |
||||
|
time = "Yesterday", |
||||
|
isAiBot = true, |
||||
|
aiTag = "AI", |
||||
|
messageText = "Elena liked your coffe..." |
||||
|
), |
||||
|
ChatMessage( |
||||
|
id = 5, |
||||
|
name = "Marcus", |
||||
|
age = 28, |
||||
|
time = "Wednesday", |
||||
|
messageText = "Haha sounds like a plan, let me kn..." |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
@Preview(showBackground = true, showSystemUi = true) |
||||
|
@Composable |
||||
|
fun ChatScreenPreview() { |
||||
|
MaterialTheme { |
||||
|
ChatScreen() |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,252 @@ |
|||||
|
package com.example.dattingapp |
||||
|
import androidx.compose.foundation.background |
||||
|
import androidx.compose.foundation.layout.* |
||||
|
import androidx.compose.foundation.lazy.grid.GridCells |
||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid |
||||
|
import androidx.compose.foundation.lazy.grid.items |
||||
|
import androidx.compose.foundation.shape.CircleShape |
||||
|
import androidx.compose.foundation.shape.RoundedCornerShape |
||||
|
import androidx.compose.material.icons.Icons |
||||
|
import androidx.compose.material.icons.filled.Close |
||||
|
import androidx.compose.material.icons.filled.Favorite |
||||
|
import androidx.compose.material.icons.filled.LocationOn |
||||
|
import androidx.compose.material.icons.outlined.ChatBubbleOutline |
||||
|
import androidx.compose.material3.* |
||||
|
import androidx.compose.runtime.Composable |
||||
|
import androidx.compose.ui.Alignment |
||||
|
import androidx.compose.ui.Modifier |
||||
|
import androidx.compose.ui.draw.clip |
||||
|
import androidx.compose.ui.graphics.Brush |
||||
|
import androidx.compose.ui.graphics.Color |
||||
|
import androidx.compose.ui.layout.ContentScale |
||||
|
import androidx.compose.ui.text.font.FontWeight |
||||
|
import androidx.compose.ui.tooling.preview.Preview |
||||
|
import androidx.compose.ui.unit.dp |
||||
|
import androidx.compose.ui.unit.sp |
||||
|
import coil.compose.AsyncImage |
||||
|
|
||||
|
|
||||
|
|
||||
|
class LikeAndMatchScreen { |
||||
|
} |
||||
|
|
||||
|
data class Profile( |
||||
|
val name: String, |
||||
|
val age: Int, |
||||
|
val distance: String, |
||||
|
val matchPercentage: Int, |
||||
|
val tags: List<String>, |
||||
|
val imageUrl: String, |
||||
|
val hasUnread: Boolean = false |
||||
|
) |
||||
|
|
||||
|
@Composable |
||||
|
fun ProfileGrid(profiles: List<Profile>, modifier: Modifier = Modifier) { |
||||
|
LazyVerticalGrid( |
||||
|
columns = GridCells.Fixed(2), |
||||
|
contentPadding = PaddingValues(12.dp), |
||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp), |
||||
|
verticalArrangement = Arrangement.spacedBy(12.dp), |
||||
|
modifier = modifier.fillMaxSize() |
||||
|
) { |
||||
|
items(profiles) { profile -> |
||||
|
ProfileCard(profile = profile) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Composable |
||||
|
fun ProfileCard(profile: Profile, modifier: Modifier = Modifier) { |
||||
|
Card( |
||||
|
shape = RoundedCornerShape(16.dp), |
||||
|
colors = CardDefaults.cardColors(containerColor = Color.White), |
||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), |
||||
|
modifier = modifier.fillMaxWidth() |
||||
|
) { |
||||
|
Column( |
||||
|
modifier = Modifier.padding(8.dp), |
||||
|
verticalArrangement = Arrangement.spacedBy(8.dp) |
||||
|
) { |
||||
|
// Image with Overlays |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.height(180.dp) |
||||
|
.clip(RoundedCornerShape(12.dp)) |
||||
|
) { |
||||
|
AsyncImage( |
||||
|
model = profile.imageUrl, |
||||
|
contentDescription = profile.name, |
||||
|
contentScale = ContentScale.Crop, |
||||
|
modifier = Modifier.fillMaxSize() |
||||
|
) |
||||
|
|
||||
|
// Gradient Overlay for Text Readability |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.fillMaxSize() |
||||
|
.background( |
||||
|
Brush.verticalGradient( |
||||
|
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)), |
||||
|
startY = 100f |
||||
|
) |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
// Match Badge |
||||
|
Surface( |
||||
|
shape = RoundedCornerShape(20.dp), |
||||
|
color = Color.White.copy(alpha = 0.9f), |
||||
|
modifier = Modifier |
||||
|
.padding(8.dp) |
||||
|
.align(Alignment.TopStart) |
||||
|
) { |
||||
|
Row( |
||||
|
verticalAlignment = Alignment.CenterVertically, |
||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp), |
||||
|
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.Favorite, |
||||
|
contentDescription = null, |
||||
|
tint = Color(0xFF6C5CE7), |
||||
|
modifier = Modifier.size(12.dp) |
||||
|
) |
||||
|
Text( |
||||
|
text = "${profile.matchPercentage}%", |
||||
|
fontSize = 11.sp, |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
color = Color(0xFF2D3436) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Notification Dot |
||||
|
if (profile.hasUnread) { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.padding(8.dp) |
||||
|
.size(8.dp) |
||||
|
.clip(CircleShape) |
||||
|
.background(Color(0xFF6C5CE7)) |
||||
|
.align(Alignment.TopEnd) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// Name, Age & Distance |
||||
|
Column( |
||||
|
modifier = Modifier |
||||
|
.align(Alignment.BottomStart) |
||||
|
.padding(8.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
text = "${profile.name}, ${profile.age}", |
||||
|
color = Color.White, |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
fontSize = 14.sp |
||||
|
) |
||||
|
Row( |
||||
|
verticalAlignment = Alignment.CenterVertically, |
||||
|
horizontalArrangement = Arrangement.spacedBy(2.dp) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.LocationOn, |
||||
|
contentDescription = null, |
||||
|
tint = Color.White.copy(alpha = 0.8f), |
||||
|
modifier = Modifier.size(10.dp) |
||||
|
) |
||||
|
Text( |
||||
|
text = profile.distance, |
||||
|
color = Color.White.copy(alpha = 0.8f), |
||||
|
fontSize = 10.sp |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Tags Row |
||||
|
Row( |
||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp), |
||||
|
modifier = Modifier.fillMaxWidth() |
||||
|
) { |
||||
|
profile.tags.take(2).forEach { tag -> |
||||
|
Surface( |
||||
|
shape = RoundedCornerShape(6.dp), |
||||
|
color = Color(0xFFF1F2F6), |
||||
|
modifier = Modifier.weight(1f) |
||||
|
) { |
||||
|
Text( |
||||
|
text = tag, |
||||
|
fontSize = 10.sp, |
||||
|
color = Color(0xFF636E72), |
||||
|
modifier = Modifier.padding(vertical = 4.dp, horizontal = 4.dp), |
||||
|
maxLines = 1 |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Action Buttons |
||||
|
Row( |
||||
|
horizontalArrangement = Arrangement.SpaceBetween, |
||||
|
verticalAlignment = Alignment.CenterVertically, |
||||
|
modifier = Modifier.fillMaxWidth().padding(10.dp) |
||||
|
) { |
||||
|
IconButton( |
||||
|
onClick = { }, |
||||
|
modifier = Modifier |
||||
|
.size(22.dp) |
||||
|
.background(Color(0xFFF8F9FA), CircleShape) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.Close, |
||||
|
contentDescription = "Pass", |
||||
|
tint = Color(0xFF636E72), |
||||
|
modifier = Modifier.size(16.dp) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
IconButton( |
||||
|
onClick = { }, |
||||
|
modifier = Modifier |
||||
|
.size(22.dp) |
||||
|
.background(Color(0xFFF1EFFE), CircleShape) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Outlined.ChatBubbleOutline, |
||||
|
contentDescription = "Message", |
||||
|
tint = Color(0xFF6C5CE7), |
||||
|
modifier = Modifier.size(16.dp) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
IconButton( |
||||
|
onClick = { }, |
||||
|
modifier = Modifier |
||||
|
.size(22.dp) |
||||
|
.background(Color(0xFFD63031), CircleShape) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.Favorite, |
||||
|
contentDescription = "Like", |
||||
|
tint = Color.White, |
||||
|
modifier = Modifier.size(16.dp) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Preview(showBackground = true) |
||||
|
@Composable |
||||
|
fun ProfileGridPreview() { |
||||
|
val sampleProfiles = listOf( |
||||
|
Profile("Elena", 24, "2 miles away", 98, listOf("Photography", "Coffee"), "", true), |
||||
|
Profile("Sarah", 26, "4 miles away", 94, listOf("Travel", "Music"), ""), |
||||
|
Profile("Julian", 27, "3 miles away", 91, listOf("Music Prod", "Vinyl"), ""), |
||||
|
Profile("Maya", 25, "5 miles away", 89, listOf("Surfing", "Yoga"), "") |
||||
|
) |
||||
|
|
||||
|
ProfileGrid(profiles = sampleProfiles) |
||||
|
} |
||||
@ -0,0 +1,385 @@ |
|||||
|
package com.example.dattingapp.chat |
||||
|
|
||||
|
|
||||
|
import androidx.compose.foundation.Image |
||||
|
import androidx.compose.foundation.background |
||||
|
import androidx.compose.foundation.layout.* |
||||
|
import androidx.compose.foundation.lazy.LazyColumn |
||||
|
import androidx.compose.foundation.lazy.items |
||||
|
import androidx.compose.foundation.shape.CircleShape |
||||
|
import androidx.compose.foundation.shape.RoundedCornerShape |
||||
|
import androidx.compose.material.icons.Icons |
||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack |
||||
|
import androidx.compose.material.icons.automirrored.filled.Send |
||||
|
import androidx.compose.material.icons.filled.Add |
||||
|
import androidx.compose.material.icons.filled.PlayArrow |
||||
|
import androidx.compose.material.icons.outlined.SentimentSatisfied |
||||
|
import androidx.compose.material3.* |
||||
|
import androidx.compose.runtime.* |
||||
|
import androidx.compose.ui.Alignment |
||||
|
import androidx.compose.ui.Modifier |
||||
|
import androidx.compose.ui.draw.clip |
||||
|
import androidx.compose.ui.graphics.Brush |
||||
|
import androidx.compose.ui.graphics.Color |
||||
|
import androidx.compose.ui.layout.ContentScale |
||||
|
import androidx.compose.ui.res.painterResource |
||||
|
import androidx.compose.ui.text.font.FontWeight |
||||
|
import androidx.compose.ui.text.style.TextAlign |
||||
|
import androidx.compose.ui.tooling.preview.Preview |
||||
|
import androidx.compose.ui.unit.dp |
||||
|
import androidx.compose.ui.unit.sp |
||||
|
import com.example.dattingapp.ui.theme.AppBackground |
||||
|
import com.example.dattingapp.ui.theme.HeaderTitleColor |
||||
|
import com.example.dattingapp.ui.theme.InputFieldBg |
||||
|
import com.example.dattingapp.ui.theme.RecipientBubbleBg |
||||
|
import com.example.dattingapp.ui.theme.SenderBubbleBg |
||||
|
import com.example.dattingapp.ui.theme.SubtextColor |
||||
|
|
||||
|
|
||||
|
class PersonChatScreen { |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
@OptIn(ExperimentalMaterial3Api::class) |
||||
|
@Composable |
||||
|
fun ChatDetailScreen( |
||||
|
userName: String = "Sarah", |
||||
|
onBackClick: () -> Unit = {} |
||||
|
) { |
||||
|
var textValue by remember { mutableStateOf("") } |
||||
|
|
||||
|
Scaffold( |
||||
|
topBar = { |
||||
|
TopAppBar( |
||||
|
title = { |
||||
|
Box( |
||||
|
modifier = Modifier.fillMaxWidth(), |
||||
|
contentAlignment = Alignment.Center |
||||
|
) { |
||||
|
Text( |
||||
|
text = userName, |
||||
|
fontSize = 18.sp, |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
color = HeaderTitleColor |
||||
|
) |
||||
|
} |
||||
|
}, |
||||
|
navigationIcon = { |
||||
|
IconButton(onClick = onBackClick) { |
||||
|
Icon( |
||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack, |
||||
|
contentDescription = "Back", |
||||
|
tint = Color.DarkGray |
||||
|
) |
||||
|
} |
||||
|
}, |
||||
|
actions = { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.padding(end = 16.dp) |
||||
|
.size(32.dp) |
||||
|
.clip(CircleShape) |
||||
|
.background(Color.LightGray) |
||||
|
) { |
||||
|
// Replace drawable resource with profile image |
||||
|
Image( |
||||
|
painter = painterResource(id = android.R.drawable.ic_menu_gallery), |
||||
|
contentDescription = "Profile", |
||||
|
contentScale = ContentScale.Crop, |
||||
|
modifier = Modifier.fillMaxSize() |
||||
|
) |
||||
|
} |
||||
|
}, |
||||
|
colors = TopAppBarDefaults.topAppBarColors(containerColor = AppBackground) |
||||
|
) |
||||
|
}, |
||||
|
bottomBar = { |
||||
|
ChatBottomInputBar( |
||||
|
textValue = textValue, |
||||
|
onValueChange = { textValue = it } |
||||
|
) |
||||
|
}, |
||||
|
containerColor = AppBackground, |
||||
|
modifier = Modifier.fillMaxSize() |
||||
|
) { innerPadding -> |
||||
|
LazyColumn( |
||||
|
modifier = Modifier |
||||
|
.fillMaxSize() |
||||
|
.padding(innerPadding) |
||||
|
.padding(horizontal = 16.dp), |
||||
|
verticalArrangement = Arrangement.spacedBy(16.dp) |
||||
|
) { |
||||
|
// Video Intro Item |
||||
|
item { |
||||
|
Row( |
||||
|
modifier = Modifier.fillMaxWidth(), |
||||
|
horizontalArrangement = Arrangement.Start |
||||
|
) { |
||||
|
AvatarIcon() |
||||
|
Spacer(modifier = Modifier.width(8.dp)) |
||||
|
Column { |
||||
|
VideoCardItem(duration = "0:15") |
||||
|
Spacer(modifier = Modifier.height(6.dp)) |
||||
|
Text( |
||||
|
text = "$userName sent a video intro!", |
||||
|
fontSize = 14.sp, |
||||
|
color = Color.DarkGray, |
||||
|
fontWeight = FontWeight.Medium |
||||
|
) |
||||
|
Text( |
||||
|
text = "Just now", |
||||
|
fontSize = 12.sp, |
||||
|
color = SubtextColor |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Received Text Message Item |
||||
|
item { |
||||
|
Row( |
||||
|
horizontalArrangement = Arrangement.Start, |
||||
|
verticalAlignment = Alignment.Top |
||||
|
) { |
||||
|
AvatarIcon() |
||||
|
Spacer(modifier = Modifier.width(8.dp)) |
||||
|
Column { |
||||
|
Surface( |
||||
|
shape = RoundedCornerShape( |
||||
|
topStart = 16.dp, |
||||
|
topEnd = 16.dp, |
||||
|
bottomEnd = 16.dp, |
||||
|
bottomStart = 4.dp |
||||
|
), |
||||
|
color = SenderBubbleBg, |
||||
|
modifier = Modifier.widthIn(max = 240.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
text = "Hi", |
||||
|
fontSize = 15.sp, |
||||
|
color = Color.Black, |
||||
|
modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp) |
||||
|
) |
||||
|
} |
||||
|
Spacer(modifier = Modifier.height(4.dp)) |
||||
|
Text( |
||||
|
text = "2:46 PM", |
||||
|
fontSize = 11.sp, |
||||
|
color = SubtextColor |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Timestamp Header Item |
||||
|
item { |
||||
|
Text( |
||||
|
text = "Today, 2:45 PM", |
||||
|
fontSize = 12.sp, |
||||
|
color = SubtextColor, |
||||
|
textAlign = TextAlign.Center, |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(vertical = 8.dp) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// Sent Message Item |
||||
|
item { |
||||
|
Row( |
||||
|
modifier = Modifier.fillMaxWidth(), |
||||
|
horizontalArrangement = Arrangement.End |
||||
|
) { |
||||
|
Surface( |
||||
|
shape = RoundedCornerShape( |
||||
|
topStart = 16.dp, |
||||
|
topEnd = 16.dp, |
||||
|
bottomEnd = 4.dp, |
||||
|
bottomStart = 16.dp |
||||
|
), |
||||
|
color = RecipientBubbleBg |
||||
|
) { |
||||
|
Text( |
||||
|
text = "Hello", |
||||
|
fontSize = 15.sp, |
||||
|
color = Color.White, |
||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp) |
||||
|
) |
||||
|
} |
||||
|
AvatarIcon() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Composable |
||||
|
fun VideoCardItem(duration: String) { |
||||
|
Card( |
||||
|
shape = RoundedCornerShape(16.dp), |
||||
|
modifier = Modifier |
||||
|
.width(280.dp) |
||||
|
.height(280.dp) |
||||
|
) { |
||||
|
Box(modifier = Modifier.fillMaxSize()) { |
||||
|
// Replace R.drawable.ic_menu_gallery with your video thumbnail resource |
||||
|
Image( |
||||
|
painter = painterResource(id = android.R.drawable.ic_menu_gallery), |
||||
|
contentDescription = "Video Thumbnail", |
||||
|
contentScale = ContentScale.Crop, |
||||
|
modifier = Modifier.fillMaxSize() |
||||
|
) |
||||
|
|
||||
|
// Center Play Button Overlay |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.size(52.dp) |
||||
|
.align(Alignment.Center) |
||||
|
.clip(CircleShape) |
||||
|
.background(Color.White.copy(alpha = 0.4f)), |
||||
|
contentAlignment = Alignment.Center |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.PlayArrow, |
||||
|
contentDescription = "Play", |
||||
|
tint = Color.White, |
||||
|
modifier = Modifier.size(32.dp) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// Video Duration Chip |
||||
|
Surface( |
||||
|
color = Color.Black.copy(alpha = 0.6f), |
||||
|
shape = RoundedCornerShape(6.dp), |
||||
|
modifier = Modifier |
||||
|
.align(Alignment.BottomEnd) |
||||
|
.padding(12.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
text = duration, |
||||
|
color = Color.White, |
||||
|
fontSize = 11.sp, |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Composable |
||||
|
fun AvatarIcon() { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.size(28.dp) |
||||
|
.clip(CircleShape) |
||||
|
.background(Color.LightGray) |
||||
|
) { |
||||
|
Image( |
||||
|
painter = painterResource(id = android.R.drawable.ic_menu_gallery), |
||||
|
contentDescription = "Avatar", |
||||
|
contentScale = ContentScale.Crop, |
||||
|
modifier = Modifier.fillMaxSize() |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Composable |
||||
|
fun ChatBottomInputBar( |
||||
|
textValue: String, |
||||
|
onValueChange: (String) -> Unit |
||||
|
) { |
||||
|
Surface( |
||||
|
color = Color.White, |
||||
|
modifier = Modifier.fillMaxWidth() |
||||
|
) { |
||||
|
Row( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.navigationBarsPadding() |
||||
|
.padding(horizontal = 12.dp, vertical = 8.dp), |
||||
|
verticalAlignment = Alignment.CenterVertically |
||||
|
) { |
||||
|
// Plus/Attachment Button |
||||
|
IconButton( |
||||
|
onClick = {}, |
||||
|
modifier = Modifier.size(36.dp) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.Add, |
||||
|
contentDescription = "Add Attachment", |
||||
|
tint = Color.DarkGray |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
Spacer(modifier = Modifier.width(4.dp)) |
||||
|
|
||||
|
// Text Field Container |
||||
|
TextField( |
||||
|
value = textValue, |
||||
|
onValueChange = onValueChange, |
||||
|
placeholder = { |
||||
|
Text( |
||||
|
"Message Sarah...", |
||||
|
color = Color.Gray, |
||||
|
fontSize = 14.sp |
||||
|
) |
||||
|
}, |
||||
|
trailingIcon = { |
||||
|
IconButton(onClick = {}) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Outlined.SentimentSatisfied, |
||||
|
contentDescription = "Emoji", |
||||
|
tint = Color.Gray |
||||
|
) |
||||
|
} |
||||
|
}, |
||||
|
colors = TextFieldDefaults.colors( |
||||
|
focusedContainerColor = InputFieldBg, |
||||
|
unfocusedContainerColor = InputFieldBg, |
||||
|
disabledContainerColor = InputFieldBg, |
||||
|
focusedIndicatorColor = Color.Transparent, |
||||
|
unfocusedIndicatorColor = Color.Transparent |
||||
|
), |
||||
|
shape = RoundedCornerShape(24.dp), |
||||
|
modifier = Modifier |
||||
|
.weight(1f) |
||||
|
.height(48.dp) |
||||
|
) |
||||
|
|
||||
|
Spacer(modifier = Modifier.width(8.dp)) |
||||
|
|
||||
|
// Gradient Send Button |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.size(44.dp) |
||||
|
.clip(CircleShape) |
||||
|
.background( |
||||
|
Brush.linearGradient( |
||||
|
colors = listOf( |
||||
|
Color(0xFFC2185B), |
||||
|
Color(0xFF4A148C) |
||||
|
) |
||||
|
) |
||||
|
), |
||||
|
contentAlignment = Alignment.Center |
||||
|
) { |
||||
|
IconButton(onClick = {}) { |
||||
|
Icon( |
||||
|
imageVector = Icons.AutoMirrored.Filled.Send, |
||||
|
contentDescription = "Send", |
||||
|
tint = Color.White, |
||||
|
modifier = Modifier.size(20.dp) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Preview(showBackground = true) |
||||
|
@Composable |
||||
|
fun ChatDetailScreenPreview() { |
||||
|
ChatDetailScreen() |
||||
|
} |
||||
@ -1,231 +0,0 @@ |
|||||
package com.example.dattingapp.permission |
|
||||
|
|
||||
import android.Manifest |
|
||||
import android.content.Intent |
|
||||
import android.os.Build |
|
||||
import android.os.Bundle |
|
||||
import android.widget.Toast |
|
||||
import androidx.activity.ComponentActivity |
|
||||
import androidx.activity.compose.setContent |
|
||||
import androidx.activity.enableEdgeToEdge |
|
||||
import androidx.activity.result.contract.ActivityResultContracts |
|
||||
import androidx.compose.foundation.Image |
|
||||
import androidx.compose.foundation.background |
|
||||
import androidx.compose.foundation.layout.* |
|
||||
import androidx.compose.foundation.shape.CircleShape |
|
||||
import androidx.compose.material.icons.Icons |
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward |
|
||||
import androidx.compose.material.icons.filled.Image |
|
||||
import androidx.compose.material3.* |
|
||||
import androidx.compose.runtime.Composable |
|
||||
import androidx.compose.ui.Alignment |
|
||||
import androidx.compose.ui.Modifier |
|
||||
import androidx.compose.ui.draw.clip |
|
||||
import androidx.compose.ui.graphics.Brush |
|
||||
import androidx.compose.ui.graphics.Color |
|
||||
import androidx.compose.ui.layout.ContentScale |
|
||||
import androidx.compose.ui.res.painterResource |
|
||||
import androidx.compose.ui.text.font.FontWeight |
|
||||
import androidx.compose.ui.text.style.TextAlign |
|
||||
import androidx.compose.ui.tooling.preview.Preview |
|
||||
import androidx.compose.ui.unit.dp |
|
||||
import androidx.compose.ui.unit.sp |
|
||||
import com.example.dattingapp.R |
|
||||
import com.example.dattingapp.ui.theme.DattingAPPTheme |
|
||||
import com.example.dattingapp.ui.theme.GradientEnd |
|
||||
import com.example.dattingapp.ui.theme.GradientStart |
|
||||
|
|
||||
class GallaryPermission : ComponentActivity() { |
|
||||
|
|
||||
// 1. Permission Launcher to handle gallery/photos permission request |
|
||||
private val requestGalleryPermissionLauncher = |
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> |
|
||||
if (isGranted) { |
|
||||
// Permission granted: navigate to next screen |
|
||||
navigateToNextActivity() |
|
||||
} else { |
|
||||
// Permission denied: show toast or proceed directly |
|
||||
Toast.makeText(this, "Gallery permission denied", Toast.LENGTH_SHORT).show() |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
|
||||
super.onCreate(savedInstanceState) |
|
||||
enableEdgeToEdge() |
|
||||
setContent { |
|
||||
DattingAPPTheme { |
|
||||
GallaryPermissionScreen( |
|
||||
onEnableGalleryClick = { |
|
||||
// Request correct permission based on Android SDK version |
|
||||
val permissionToRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
|
||||
Manifest.permission.READ_MEDIA_IMAGES |
|
||||
} else { |
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE |
|
||||
} |
|
||||
requestGalleryPermissionLauncher.launch(permissionToRequest) |
|
||||
}, |
|
||||
onNotNowClick = { |
|
||||
// Directly skip to next activity |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Helper function to launch the target activity |
|
||||
private fun navigateToNextActivity() { |
|
||||
// TODO: Replace NextActivity::class.java with your target Activity class |
|
||||
val intent = Intent(this, LocationPermision::class.java) |
|
||||
startActivity(intent) |
|
||||
finish() // Closes current permission screen |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Composable |
|
||||
fun GallaryPermissionScreen( |
|
||||
onEnableGalleryClick: () -> Unit, |
|
||||
onNotNowClick: () -> Unit, |
|
||||
modifier: Modifier = Modifier |
|
||||
) { |
|
||||
Box( |
|
||||
modifier = modifier.fillMaxSize(), |
|
||||
contentAlignment = Alignment.Center |
|
||||
) { |
|
||||
Image( |
|
||||
painter = painterResource(id = R.drawable.gallarypermissionbg), |
|
||||
contentDescription = null, |
|
||||
modifier = Modifier.fillMaxSize(), |
|
||||
contentScale = ContentScale.Crop |
|
||||
) |
|
||||
NotificationPermissionCard( |
|
||||
onEnableGalleryClick = onEnableGalleryClick, |
|
||||
onNotNowClick = onNotNowClick |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Composable |
|
||||
private fun NotificationPermissionCard( |
|
||||
onEnableGalleryClick: () -> Unit, |
|
||||
onNotNowClick: () -> Unit, |
|
||||
modifier: Modifier = Modifier |
|
||||
) { |
|
||||
val buttonGradient = Brush.horizontalGradient( |
|
||||
colors = listOf(GradientStart, GradientEnd) |
|
||||
) |
|
||||
Column( |
|
||||
modifier = modifier |
|
||||
.fillMaxSize() |
|
||||
.padding(horizontal = 24.dp, vertical = 50.dp), |
|
||||
horizontalAlignment = Alignment.CenterHorizontally, |
|
||||
verticalArrangement = Arrangement.SpaceBetween |
|
||||
) { |
|
||||
Column( |
|
||||
horizontalAlignment = Alignment.CenterHorizontally |
|
||||
) { |
|
||||
Box( |
|
||||
modifier = Modifier |
|
||||
.size(72.dp) |
|
||||
.clip(CircleShape) |
|
||||
.background(buttonGradient), |
|
||||
contentAlignment = Alignment.Center |
|
||||
) { |
|
||||
Icon( |
|
||||
imageVector = Icons.Default.Image, |
|
||||
contentDescription = null, |
|
||||
tint = Color.White, |
|
||||
modifier = Modifier.size(36.dp) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
Spacer(modifier = Modifier.height(16.dp)) |
|
||||
|
|
||||
Text( |
|
||||
text = "Discover Cultural\nVibes", |
|
||||
fontSize = 22.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
color = Color.Black, |
|
||||
textAlign = TextAlign.Center, |
|
||||
lineHeight = 28.sp |
|
||||
) |
|
||||
|
|
||||
Spacer(modifier = Modifier.height(12.dp)) |
|
||||
|
|
||||
Text( |
|
||||
text = "Find amazing people sharing the same space. Let's make a radiant connection at your favorite spots.", |
|
||||
fontSize = 17.sp, |
|
||||
fontWeight = FontWeight.Normal, |
|
||||
color = Color.Black, |
|
||||
textAlign = TextAlign.Center, |
|
||||
lineHeight = 22.sp, |
|
||||
modifier = Modifier.padding(horizontal = 4.dp) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
Column( |
|
||||
horizontalAlignment = Alignment.CenterHorizontally, |
|
||||
modifier = Modifier.fillMaxWidth() |
|
||||
) { |
|
||||
Button( |
|
||||
onClick = onEnableGalleryClick, |
|
||||
modifier = Modifier |
|
||||
.fillMaxWidth() |
|
||||
.height(50.dp) |
|
||||
.clip(CircleShape) |
|
||||
.background(buttonGradient), |
|
||||
colors = ButtonDefaults.buttonColors( |
|
||||
containerColor = Color.Transparent |
|
||||
), |
|
||||
contentPadding = PaddingValues() |
|
||||
) { |
|
||||
Row( |
|
||||
verticalAlignment = Alignment.CenterVertically, |
|
||||
horizontalArrangement = Arrangement.Center |
|
||||
) { |
|
||||
Text( |
|
||||
text = "ALLOW GALLERY ACCESS", |
|
||||
color = Color.White, |
|
||||
fontSize = 13.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
letterSpacing = 0.5.sp |
|
||||
) |
|
||||
Spacer(modifier = Modifier.width(8.dp)) |
|
||||
Icon( |
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowForward, |
|
||||
contentDescription = null, |
|
||||
tint = Color.White, |
|
||||
modifier = Modifier.size(16.dp) |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Spacer(modifier = Modifier.height(4.dp)) |
|
||||
|
|
||||
TextButton( |
|
||||
onClick = onNotNowClick, |
|
||||
modifier = Modifier.height(40.dp) |
|
||||
) { |
|
||||
Text( |
|
||||
text = "NOT NOW", |
|
||||
color = GradientStart, |
|
||||
fontSize = 13.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
letterSpacing = 0.5.sp |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Preview(showBackground = true, showSystemUi = true) |
|
||||
@Composable |
|
||||
fun GallaryPermissionPreview() { |
|
||||
DattingAPPTheme { |
|
||||
GallaryPermissionScreen( |
|
||||
onEnableGalleryClick = {}, |
|
||||
onNotNowClick = {} |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
@ -1,331 +0,0 @@ |
|||||
package com.example.dattingapp.permission |
|
||||
|
|
||||
import android.Manifest |
|
||||
import android.content.Intent |
|
||||
import android.os.Bundle |
|
||||
import android.widget.Toast |
|
||||
import androidx.activity.ComponentActivity |
|
||||
import androidx.activity.compose.setContent |
|
||||
import androidx.activity.enableEdgeToEdge |
|
||||
import androidx.activity.result.contract.ActivityResultContracts |
|
||||
import androidx.compose.foundation.Image |
|
||||
import androidx.compose.foundation.background |
|
||||
import androidx.compose.foundation.border |
|
||||
import androidx.compose.foundation.layout.* |
|
||||
import androidx.compose.foundation.shape.CircleShape |
|
||||
import androidx.compose.foundation.shape.GenericShape |
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape |
|
||||
import androidx.compose.material.icons.Icons |
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward |
|
||||
import androidx.compose.material.icons.filled.Check |
|
||||
import androidx.compose.material.icons.filled.LocationOn |
|
||||
import androidx.compose.material3.* |
|
||||
import androidx.compose.runtime.Composable |
|
||||
import androidx.compose.ui.Alignment |
|
||||
import androidx.compose.ui.Modifier |
|
||||
import androidx.compose.ui.draw.clip |
|
||||
import androidx.compose.ui.graphics.Brush |
|
||||
import androidx.compose.ui.graphics.Color |
|
||||
import androidx.compose.ui.layout.ContentScale |
|
||||
import androidx.compose.ui.res.painterResource |
|
||||
import androidx.compose.ui.text.font.FontWeight |
|
||||
import androidx.compose.ui.text.style.TextAlign |
|
||||
import androidx.compose.ui.tooling.preview.Preview |
|
||||
import androidx.compose.ui.unit.dp |
|
||||
import androidx.compose.ui.unit.sp |
|
||||
import com.example.dattingapp.R |
|
||||
import com.example.dattingapp.ui.theme.DattingAPPTheme |
|
||||
import com.example.dattingapp.ui.theme.GradientEnd |
|
||||
import com.example.dattingapp.ui.theme.GradientStart |
|
||||
import com.example.dattingapp.ui.theme.whitecolor |
|
||||
|
|
||||
class LocationPermision : ComponentActivity() { |
|
||||
|
|
||||
// 1. Permission Launcher for Location Permissions |
|
||||
private val requestLocationPermissionLauncher = |
|
||||
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> |
|
||||
val isFineLocationGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: false |
|
||||
val isCoarseLocationGranted = permissions[Manifest.permission.ACCESS_COARSE_LOCATION] ?: false |
|
||||
|
|
||||
if (isFineLocationGranted || isCoarseLocationGranted) { |
|
||||
navigateToNextActivity() |
|
||||
} else { |
|
||||
Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show() |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
|
||||
super.onCreate(savedInstanceState) |
|
||||
enableEdgeToEdge() |
|
||||
setContent { |
|
||||
DattingAPPTheme { |
|
||||
LocationPermissionScreen( |
|
||||
onEnableLocationClick = { |
|
||||
// Request FINE and COARSE location runtime permissions |
|
||||
requestLocationPermissionLauncher.launch( |
|
||||
arrayOf( |
|
||||
Manifest.permission.ACCESS_FINE_LOCATION, |
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION |
|
||||
) |
|
||||
) |
|
||||
}, |
|
||||
onNotNowClick = { |
|
||||
// Skip permission prompt and go straight to next activity |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun navigateToNextActivity() { |
|
||||
val intent = Intent(this, OtherAppPermission::class.java) |
|
||||
startActivity(intent) |
|
||||
finish() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Composable |
|
||||
fun LocationPermissionScreen( |
|
||||
onEnableLocationClick: () -> Unit, |
|
||||
onNotNowClick: () -> Unit, |
|
||||
modifier: Modifier = Modifier |
|
||||
) { |
|
||||
Box( |
|
||||
modifier = modifier.fillMaxSize(), |
|
||||
contentAlignment = Alignment.Center |
|
||||
) { |
|
||||
Image( |
|
||||
painter = painterResource(id = R.drawable.locationpermissionbg), |
|
||||
contentDescription = null, |
|
||||
modifier = Modifier.fillMaxSize(), |
|
||||
contentScale = ContentScale.Crop |
|
||||
) |
|
||||
LocationPermissionCard( |
|
||||
onEnableLocationClick = onEnableLocationClick, |
|
||||
onNotNowClick = onNotNowClick |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Composable |
|
||||
private fun LocationPermissionCard( |
|
||||
onEnableLocationClick: () -> Unit, |
|
||||
onNotNowClick: () -> Unit, |
|
||||
modifier: Modifier = Modifier |
|
||||
) { |
|
||||
val buttonGradient = Brush.horizontalGradient( |
|
||||
colors = listOf(GradientStart, GradientEnd) |
|
||||
) |
|
||||
Column( |
|
||||
modifier = modifier |
|
||||
.fillMaxSize() |
|
||||
.padding(horizontal = 24.dp, vertical = 50.dp), |
|
||||
horizontalAlignment = Alignment.CenterHorizontally, |
|
||||
verticalArrangement = Arrangement.SpaceBetween |
|
||||
) { |
|
||||
Column( |
|
||||
horizontalAlignment = Alignment.CenterHorizontally |
|
||||
) { |
|
||||
Box( |
|
||||
modifier = Modifier |
|
||||
.size(72.dp) |
|
||||
.clip(CircleShape) |
|
||||
.background(buttonGradient), |
|
||||
contentAlignment = Alignment.Center |
|
||||
) { |
|
||||
Icon( |
|
||||
imageVector = Icons.Default.LocationOn, |
|
||||
contentDescription = null, |
|
||||
tint = Color.White, |
|
||||
modifier = Modifier.size(36.dp) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
Spacer(modifier = Modifier.height(16.dp)) |
|
||||
|
|
||||
Text( |
|
||||
text = "Discover Cultural\nVibes", |
|
||||
fontSize = 22.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
color = whitecolor, |
|
||||
textAlign = TextAlign.Center, |
|
||||
lineHeight = 28.sp |
|
||||
) |
|
||||
|
|
||||
Spacer(modifier = Modifier.height(12.dp)) |
|
||||
|
|
||||
Text( |
|
||||
text = "Find amazing people sharing the same space. Let's make a radiant connection at your favorite spots.", |
|
||||
fontSize = 13.sp, |
|
||||
fontWeight = FontWeight.Normal, |
|
||||
color = whitecolor, |
|
||||
textAlign = TextAlign.Center, |
|
||||
lineHeight = 18.sp, |
|
||||
modifier = Modifier.padding(horizontal = 4.dp) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
vibeFoundCard(modifier = Modifier.offset(y = (-100).dp)) |
|
||||
|
|
||||
Column( |
|
||||
horizontalAlignment = Alignment.CenterHorizontally, |
|
||||
modifier = Modifier.fillMaxWidth() |
|
||||
) { |
|
||||
Button( |
|
||||
onClick = onEnableLocationClick, |
|
||||
modifier = Modifier |
|
||||
.fillMaxWidth() |
|
||||
.height(50.dp) |
|
||||
.clip(CircleShape) |
|
||||
.background(buttonGradient), |
|
||||
colors = ButtonDefaults.buttonColors( |
|
||||
containerColor = Color.Transparent |
|
||||
), |
|
||||
contentPadding = PaddingValues() |
|
||||
) { |
|
||||
Row( |
|
||||
verticalAlignment = Alignment.CenterVertically, |
|
||||
horizontalArrangement = Arrangement.Center |
|
||||
) { |
|
||||
Text( |
|
||||
text = "ENABLE LOCATION", |
|
||||
color = Color.White, |
|
||||
fontSize = 13.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
letterSpacing = 0.5.sp |
|
||||
) |
|
||||
Spacer(modifier = Modifier.width(8.dp)) |
|
||||
Icon( |
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowForward, |
|
||||
contentDescription = null, |
|
||||
tint = Color.White, |
|
||||
modifier = Modifier.size(16.dp) |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Spacer(modifier = Modifier.height(4.dp)) |
|
||||
|
|
||||
TextButton( |
|
||||
onClick = onNotNowClick, |
|
||||
modifier = Modifier.height(40.dp) |
|
||||
) { |
|
||||
Text( |
|
||||
text = "NOT NOW", |
|
||||
color = GradientStart, |
|
||||
fontSize = 13.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
letterSpacing = 0.5.sp |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Composable |
|
||||
fun vibeFoundCard( |
|
||||
modifier: Modifier = Modifier |
|
||||
) { |
|
||||
val BubbleTailShape = GenericShape { size, _ -> |
|
||||
moveTo(0f, 0f) |
|
||||
lineTo(size.width, 0f) |
|
||||
lineTo(size.width / 2f, size.height) |
|
||||
close() |
|
||||
} |
|
||||
|
|
||||
Column( |
|
||||
horizontalAlignment = Alignment.CenterHorizontally, |
|
||||
modifier = modifier |
|
||||
) { |
|
||||
Surface( |
|
||||
shape = RoundedCornerShape(28.dp), |
|
||||
color = Color.Transparent, |
|
||||
shadowElevation = 8.dp |
|
||||
) { |
|
||||
Box( |
|
||||
modifier = Modifier |
|
||||
.background( |
|
||||
brush = Brush.verticalGradient( |
|
||||
colors = listOf( |
|
||||
Color(0xFFFBF8FE).copy(alpha = 0.95f), |
|
||||
Color(0xFFEFE8FA).copy(alpha = 0.95f) |
|
||||
) |
|
||||
) |
|
||||
) |
|
||||
.border( |
|
||||
width = 1.dp, |
|
||||
color = Color.White.copy(alpha = 0.8f), |
|
||||
shape = RoundedCornerShape(28.dp) |
|
||||
) |
|
||||
.padding(horizontal = 20.dp, vertical = 14.dp) |
|
||||
) { |
|
||||
Row( |
|
||||
verticalAlignment = Alignment.CenterVertically, |
|
||||
horizontalArrangement = Arrangement.Center |
|
||||
) { |
|
||||
Box( |
|
||||
contentAlignment = Alignment.BottomEnd, |
|
||||
) { |
|
||||
Image( |
|
||||
painter = painterResource(id = R.drawable.locationpermissionbg), |
|
||||
contentDescription = "User Avatar", |
|
||||
contentScale = ContentScale.Crop, |
|
||||
modifier = Modifier |
|
||||
.size(30.dp) |
|
||||
.clip(CircleShape) |
|
||||
) |
|
||||
|
|
||||
Box( |
|
||||
contentAlignment = Alignment.Center, |
|
||||
modifier = Modifier |
|
||||
.size(20.dp) |
|
||||
.offset(x = 2.dp, y = 2.dp) |
|
||||
.clip(CircleShape) |
|
||||
.background(Color(0xFF8A62D6)) |
|
||||
.border(1.5.dp, Color.White, CircleShape) |
|
||||
) { |
|
||||
Icon( |
|
||||
imageVector = Icons.Default.Check, |
|
||||
contentDescription = "Verified", |
|
||||
tint = Color.White, |
|
||||
modifier = Modifier.size(12.dp) |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Spacer(modifier = Modifier.width(16.dp)) |
|
||||
|
|
||||
Text( |
|
||||
text = "Vibe Found!", |
|
||||
fontWeight = FontWeight.Normal, |
|
||||
color = Color(0xFF4A154B), |
|
||||
letterSpacing = (-0.5).sp |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Box( |
|
||||
modifier = Modifier |
|
||||
.offset(y = (-1).dp) |
|
||||
.size(width = 18.dp, height = 10.dp) |
|
||||
.background( |
|
||||
color = Color(0xFFEFE8FA).copy(alpha = 0.95f), |
|
||||
shape = BubbleTailShape |
|
||||
) |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Preview(showBackground = true, showSystemUi = true) |
|
||||
@Composable |
|
||||
fun LocationPermissionScreenPreview() { |
|
||||
DattingAPPTheme { |
|
||||
LocationPermissionScreen( |
|
||||
onEnableLocationClick = {}, |
|
||||
onNotNowClick = {} |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
@ -1,274 +0,0 @@ |
|||||
package com.example.dattingapp.permission |
|
||||
|
|
||||
import android.Manifest |
|
||||
import android.content.Intent |
|
||||
import android.os.Build |
|
||||
import android.os.Bundle |
|
||||
import android.widget.Toast |
|
||||
import androidx.activity.ComponentActivity |
|
||||
import androidx.activity.compose.setContent |
|
||||
import androidx.activity.enableEdgeToEdge |
|
||||
import androidx.activity.result.contract.ActivityResultContracts |
|
||||
import androidx.compose.foundation.Image |
|
||||
import androidx.compose.foundation.background |
|
||||
import androidx.compose.foundation.layout.Arrangement |
|
||||
import androidx.compose.foundation.layout.Box |
|
||||
import androidx.compose.foundation.layout.Column |
|
||||
import androidx.compose.foundation.layout.PaddingValues |
|
||||
import androidx.compose.foundation.layout.Row |
|
||||
import androidx.compose.foundation.layout.Spacer |
|
||||
import androidx.compose.foundation.layout.fillMaxSize |
|
||||
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.foundation.layout.width |
|
||||
import androidx.compose.foundation.shape.CircleShape |
|
||||
import androidx.compose.material.icons.Icons |
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward |
|
||||
import androidx.compose.material.icons.filled.Notifications |
|
||||
import androidx.compose.material3.Button |
|
||||
import androidx.compose.material3.ButtonDefaults |
|
||||
import androidx.compose.material3.Icon |
|
||||
import androidx.compose.material3.Text |
|
||||
import androidx.compose.material3.TextButton |
|
||||
import androidx.compose.runtime.Composable |
|
||||
import androidx.compose.ui.Alignment |
|
||||
import androidx.compose.ui.Modifier |
|
||||
import androidx.compose.ui.draw.clip |
|
||||
import androidx.compose.ui.graphics.Brush |
|
||||
import androidx.compose.ui.graphics.Color |
|
||||
import androidx.compose.ui.layout.ContentScale |
|
||||
import androidx.compose.ui.res.painterResource |
|
||||
import androidx.compose.ui.text.font.FontWeight |
|
||||
import androidx.compose.ui.text.style.TextAlign |
|
||||
import androidx.compose.ui.tooling.preview.Preview |
|
||||
import androidx.compose.ui.unit.dp |
|
||||
import androidx.compose.ui.unit.sp |
|
||||
import com.example.dattingapp.Login.LoginActivity |
|
||||
import com.example.dattingapp.R |
|
||||
import com.example.dattingapp.ui.theme.DattingAPPTheme |
|
||||
import com.example.dattingapp.ui.theme.GradientEnd |
|
||||
import com.example.dattingapp.ui.theme.GradientStart |
|
||||
|
|
||||
class NotificationPermission : ComponentActivity() { |
|
||||
|
|
||||
private val prefs by lazy { |
|
||||
getSharedPreferences("app_preferences", MODE_PRIVATE) |
|
||||
} |
|
||||
|
|
||||
private val requestNotificationPermissionLauncher = |
|
||||
registerForActivityResult( |
|
||||
ActivityResultContracts.RequestPermission() |
|
||||
) { isGranted -> |
|
||||
|
|
||||
// Mark this screen as completed |
|
||||
markPermissionScreenCompleted() |
|
||||
|
|
||||
// Go to next screen regardless of permission result |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
|
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
|
||||
super.onCreate(savedInstanceState) |
|
||||
|
|
||||
// Check if this screen was already completed |
|
||||
if (prefs.getBoolean("notification_screen_completed", false)) { |
|
||||
val intent = Intent(this, LoginActivity::class.java) |
|
||||
startActivity(intent) |
|
||||
finish() |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
enableEdgeToEdge() |
|
||||
|
|
||||
setContent { |
|
||||
DattingAPPTheme { |
|
||||
NotificationPermissionScreen( |
|
||||
onEnableNotificationClick = { |
|
||||
requestNotificationPermission() |
|
||||
}, |
|
||||
onNotNowClick = { |
|
||||
// User doesn't want notification permission |
|
||||
markPermissionScreenCompleted() |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun requestNotificationPermission() { |
|
||||
|
|
||||
// Mark screen as completed immediately |
|
||||
// so it won't appear again |
|
||||
markPermissionScreenCompleted() |
|
||||
|
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
|
||||
|
|
||||
requestNotificationPermissionLauncher.launch( |
|
||||
Manifest.permission.POST_NOTIFICATIONS |
|
||||
) |
|
||||
|
|
||||
} else { |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun markPermissionScreenCompleted() { |
|
||||
prefs.edit() |
|
||||
.putBoolean("notification_screen_completed", true) |
|
||||
.apply() |
|
||||
} |
|
||||
|
|
||||
private fun navigateToNextActivity() { |
|
||||
val intent = Intent(this, GallaryPermission::class.java) |
|
||||
startActivity(intent) |
|
||||
finish() |
|
||||
} |
|
||||
} |
|
||||
// screen create |
|
||||
@Composable |
|
||||
fun NotificationPermissionScreen( |
|
||||
onEnableNotificationClick: () -> Unit, |
|
||||
onNotNowClick: () -> Unit, |
|
||||
modifier: Modifier = Modifier |
|
||||
) { |
|
||||
Box( |
|
||||
modifier = modifier.fillMaxSize(), |
|
||||
contentAlignment = Alignment.Center |
|
||||
) { |
|
||||
Image( |
|
||||
painter = painterResource(id = R.drawable.notificationpermission), |
|
||||
contentDescription = null, |
|
||||
modifier = Modifier.fillMaxSize(), |
|
||||
contentScale = ContentScale.Crop |
|
||||
) |
|
||||
NotificationPermissionCard( |
|
||||
onEnableNotificationClick = onEnableNotificationClick, |
|
||||
onNotNowClick = onNotNowClick |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Composable |
|
||||
private fun NotificationPermissionCard( |
|
||||
onEnableNotificationClick: () -> Unit, |
|
||||
onNotNowClick: () -> Unit, |
|
||||
modifier: Modifier = Modifier |
|
||||
) { |
|
||||
val buttonGradient = Brush.horizontalGradient( |
|
||||
colors = listOf(GradientStart, GradientEnd) |
|
||||
) |
|
||||
Column( |
|
||||
modifier = modifier |
|
||||
.fillMaxSize() |
|
||||
.padding(horizontal = 24.dp, vertical = 50.dp), |
|
||||
horizontalAlignment = Alignment.CenterHorizontally, |
|
||||
verticalArrangement = Arrangement.SpaceBetween |
|
||||
) { |
|
||||
Column( |
|
||||
horizontalAlignment = Alignment.CenterHorizontally |
|
||||
) { |
|
||||
Box( |
|
||||
modifier = Modifier |
|
||||
.size(72.dp) |
|
||||
.clip(CircleShape) |
|
||||
.background(buttonGradient), |
|
||||
contentAlignment = Alignment.Center |
|
||||
) { |
|
||||
Icon( |
|
||||
imageVector = Icons.Default.Notifications, |
|
||||
contentDescription = null, |
|
||||
tint = Color.White, |
|
||||
modifier = Modifier.size(36.dp) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
Spacer(modifier = Modifier.height(16.dp)) |
|
||||
|
|
||||
Text( |
|
||||
text = "Discover Cultural\nVibes", |
|
||||
fontSize = 22.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
color = Color.Black, |
|
||||
textAlign = TextAlign.Center, |
|
||||
lineHeight = 28.sp |
|
||||
) |
|
||||
|
|
||||
Spacer(modifier = Modifier.height(12.dp)) |
|
||||
|
|
||||
Text( |
|
||||
text = "Find amazing people sharing the same space. Let's make a radiant connection at your favorite spots.", |
|
||||
fontSize = 17.sp, |
|
||||
fontWeight = FontWeight.Normal, |
|
||||
color = Color.Black, |
|
||||
textAlign = TextAlign.Center, |
|
||||
lineHeight = 18.sp, |
|
||||
modifier = Modifier.padding(horizontal = 4.dp) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
Column( |
|
||||
horizontalAlignment = Alignment.CenterHorizontally, |
|
||||
modifier = Modifier.fillMaxWidth() |
|
||||
) { |
|
||||
Button( |
|
||||
onClick = onEnableNotificationClick, |
|
||||
modifier = Modifier |
|
||||
.fillMaxWidth() |
|
||||
.height(50.dp) |
|
||||
.clip(CircleShape) |
|
||||
.background(buttonGradient), |
|
||||
colors = ButtonDefaults.buttonColors( |
|
||||
containerColor = Color.Transparent |
|
||||
), |
|
||||
contentPadding = PaddingValues() |
|
||||
) { |
|
||||
Row( |
|
||||
verticalAlignment = Alignment.CenterVertically, |
|
||||
horizontalArrangement = Arrangement.Center |
|
||||
) { |
|
||||
Text( |
|
||||
text = "ENABLE NOTIFICATIONS", |
|
||||
color = Color.White, |
|
||||
fontSize = 13.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
letterSpacing = 0.5.sp |
|
||||
) |
|
||||
Spacer(modifier = Modifier.width(8.dp)) |
|
||||
Icon( |
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowForward, |
|
||||
contentDescription = null, |
|
||||
tint = Color.White, |
|
||||
modifier = Modifier.size(16.dp) |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Spacer(modifier = Modifier.height(4.dp)) |
|
||||
|
|
||||
TextButton( |
|
||||
onClick = onNotNowClick, |
|
||||
modifier = Modifier.height(40.dp) |
|
||||
) { |
|
||||
Text( |
|
||||
text = "NOT NOW", |
|
||||
color = GradientStart, |
|
||||
fontSize = 13.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
letterSpacing = 0.5.sp |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Preview(showBackground = true, showSystemUi = true) |
|
||||
@Composable |
|
||||
fun NotificationPermissionPreview() { |
|
||||
NotificationPermissionScreen( |
|
||||
onEnableNotificationClick = {}, |
|
||||
onNotNowClick = {} |
|
||||
) |
|
||||
} |
|
||||
@ -1,264 +0,0 @@ |
|||||
package com.example.dattingapp.permission |
|
||||
|
|
||||
import android.content.Intent |
|
||||
import android.net.Uri |
|
||||
import android.os.Build |
|
||||
import android.os.Bundle |
|
||||
import android.provider.Settings |
|
||||
import androidx.activity.ComponentActivity |
|
||||
import androidx.activity.compose.setContent |
|
||||
import androidx.activity.enableEdgeToEdge |
|
||||
import androidx.activity.result.contract.ActivityResultContracts |
|
||||
import androidx.compose.foundation.Image |
|
||||
import androidx.compose.foundation.background |
|
||||
import androidx.compose.foundation.layout.Arrangement |
|
||||
import androidx.compose.foundation.layout.Box |
|
||||
import androidx.compose.foundation.layout.Column |
|
||||
import androidx.compose.foundation.layout.PaddingValues |
|
||||
import androidx.compose.foundation.layout.Row |
|
||||
import androidx.compose.foundation.layout.Spacer |
|
||||
import androidx.compose.foundation.layout.fillMaxSize |
|
||||
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.foundation.layout.width |
|
||||
import androidx.compose.foundation.shape.CircleShape |
|
||||
import androidx.compose.material.icons.Icons |
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward |
|
||||
import androidx.compose.material3.Button |
|
||||
import androidx.compose.material3.ButtonDefaults |
|
||||
import androidx.compose.material3.Icon |
|
||||
import androidx.compose.material3.Text |
|
||||
import androidx.compose.material3.TextButton |
|
||||
import androidx.compose.runtime.Composable |
|
||||
import androidx.compose.ui.Alignment |
|
||||
import androidx.compose.ui.Modifier |
|
||||
import androidx.compose.ui.draw.clip |
|
||||
import androidx.compose.ui.graphics.Brush |
|
||||
import androidx.compose.ui.graphics.Color |
|
||||
import androidx.compose.ui.layout.ContentScale |
|
||||
import androidx.compose.ui.res.painterResource |
|
||||
import androidx.compose.ui.text.font.FontWeight |
|
||||
import androidx.compose.ui.text.style.TextAlign |
|
||||
import androidx.compose.ui.tooling.preview.Preview |
|
||||
import androidx.compose.ui.unit.dp |
|
||||
import androidx.compose.ui.unit.sp |
|
||||
import com.example.dattingapp.Login.LoginActivity |
|
||||
import com.example.dattingapp.R |
|
||||
import com.example.dattingapp.ui.theme.DattingAPPTheme |
|
||||
import com.example.dattingapp.ui.theme.GradientEnd |
|
||||
import com.example.dattingapp.ui.theme.GradientStart |
|
||||
import com.example.dattingapp.ui.theme.whitecolor |
|
||||
|
|
||||
class OtherAppPermission : ComponentActivity() { |
|
||||
|
|
||||
private var wasSettingsOpened = false |
|
||||
|
|
||||
private val overlayPermissionLauncher = |
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { |
|
||||
checkAndNavigate() |
|
||||
} |
|
||||
|
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
|
||||
super.onCreate(savedInstanceState) |
|
||||
enableEdgeToEdge() |
|
||||
setContent { |
|
||||
DattingAPPTheme { |
|
||||
OtherAppPermissionScreen( |
|
||||
onEnableOverlayClick = { |
|
||||
requestOverlayPermission() |
|
||||
}, |
|
||||
onNotNowClick = { |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
override fun onResume() { |
|
||||
super.onResume() |
|
||||
// If returning from system settings, verify permission status |
|
||||
if (wasSettingsOpened) { |
|
||||
wasSettingsOpened = false |
|
||||
checkAndNavigate() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun requestOverlayPermission() { |
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|
||||
if (!Settings.canDrawOverlays(this)) { |
|
||||
wasSettingsOpened = true |
|
||||
try { |
|
||||
// Direct link to Floating Window settings |
|
||||
val intent = Intent( |
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION, |
|
||||
Uri.parse("package:$packageName") |
|
||||
) |
|
||||
overlayPermissionLauncher.launch(intent) |
|
||||
} catch (e: Exception) { |
|
||||
// Fallback for custom OS settings pages (Realme/OPPO/Xiaomi) |
|
||||
val fallbackIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION) |
|
||||
overlayPermissionLauncher.launch(fallbackIntent) |
|
||||
} |
|
||||
} else { |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
} else { |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun checkAndNavigate() { |
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|
||||
if (Settings.canDrawOverlays(this)) { |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
} else { |
|
||||
navigateToNextActivity() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun navigateToNextActivity() { |
|
||||
val intent = Intent(this, LoginActivity::class.java).apply { |
|
||||
// Re-anchors app context preventing app auto-close on return |
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) |
|
||||
} |
|
||||
startActivity(intent) |
|
||||
finish() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Composable |
|
||||
fun OtherAppPermissionScreen( |
|
||||
onEnableOverlayClick: () -> Unit, |
|
||||
onNotNowClick: () -> Unit, |
|
||||
modifier: Modifier = Modifier |
|
||||
) { |
|
||||
Box( |
|
||||
modifier = modifier.fillMaxSize(), |
|
||||
contentAlignment = Alignment.Center |
|
||||
) { |
|
||||
Image( |
|
||||
painter = painterResource(id = R.drawable.otherpermission), |
|
||||
contentDescription = null, |
|
||||
modifier = Modifier.fillMaxSize(), |
|
||||
contentScale = ContentScale.Crop |
|
||||
) |
|
||||
NotificationPermissionCard( |
|
||||
onEnableOverlayClick = onEnableOverlayClick, |
|
||||
onNotNowClick = onNotNowClick |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Composable |
|
||||
private fun NotificationPermissionCard( |
|
||||
onEnableOverlayClick: () -> Unit, |
|
||||
onNotNowClick: () -> Unit, |
|
||||
modifier: Modifier = Modifier |
|
||||
) { |
|
||||
val buttonGradient = Brush.horizontalGradient( |
|
||||
colors = listOf(GradientStart, GradientEnd) |
|
||||
) |
|
||||
Column( |
|
||||
modifier = modifier |
|
||||
.fillMaxSize() |
|
||||
.padding( |
|
||||
start = 16.dp, |
|
||||
top = 140.dp, |
|
||||
end = 24.dp, |
|
||||
bottom = 60.dp |
|
||||
), |
|
||||
horizontalAlignment = Alignment.CenterHorizontally, |
|
||||
verticalArrangement = Arrangement.SpaceBetween |
|
||||
) { |
|
||||
Column( |
|
||||
horizontalAlignment = Alignment.CenterHorizontally |
|
||||
) { |
|
||||
Text( |
|
||||
text = "Discover Cultural\nVibes", |
|
||||
fontSize = 22.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
color = whitecolor, |
|
||||
textAlign = TextAlign.Center, |
|
||||
lineHeight = 28.sp |
|
||||
) |
|
||||
|
|
||||
Spacer(modifier = Modifier.height(12.dp)) |
|
||||
|
|
||||
Text( |
|
||||
text = "Find amazing people sharing the same space. Let's make a radiant connection at your favorite spots.", |
|
||||
fontSize = 13.sp, |
|
||||
fontWeight = FontWeight.Normal, |
|
||||
color = whitecolor, |
|
||||
textAlign = TextAlign.Center, |
|
||||
lineHeight = 18.sp, |
|
||||
modifier = Modifier.padding(horizontal = 4.dp) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
Column( |
|
||||
horizontalAlignment = Alignment.CenterHorizontally, |
|
||||
modifier = Modifier.fillMaxWidth() |
|
||||
) { |
|
||||
Button( |
|
||||
onClick = onEnableOverlayClick, |
|
||||
modifier = Modifier |
|
||||
.fillMaxWidth() |
|
||||
.height(50.dp) |
|
||||
.clip(CircleShape) |
|
||||
.background(buttonGradient), |
|
||||
colors = ButtonDefaults.buttonColors( |
|
||||
containerColor = Color.Transparent |
|
||||
), |
|
||||
contentPadding = PaddingValues() |
|
||||
) { |
|
||||
Row( |
|
||||
verticalAlignment = Alignment.CenterVertically, |
|
||||
horizontalArrangement = Arrangement.Center |
|
||||
) { |
|
||||
Text( |
|
||||
text = "ALLOW OVERLAY ACCESS", |
|
||||
color = Color.White, |
|
||||
fontSize = 13.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
letterSpacing = 0.5.sp |
|
||||
) |
|
||||
Spacer(modifier = Modifier.width(8.dp)) |
|
||||
Icon( |
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowForward, |
|
||||
contentDescription = null, |
|
||||
tint = Color.White, |
|
||||
modifier = Modifier.size(16.dp) |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Spacer(modifier = Modifier.height(4.dp)) |
|
||||
|
|
||||
TextButton( |
|
||||
onClick = onNotNowClick, |
|
||||
modifier = Modifier.height(40.dp) |
|
||||
) { |
|
||||
Text( |
|
||||
text = "NOT NOW", |
|
||||
color = GradientStart, |
|
||||
fontSize = 13.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
letterSpacing = 0.5.sp |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Preview(showBackground = true, showSystemUi = true) |
|
||||
@Composable |
|
||||
fun OtherAppPermissionScreenPreview() { |
|
||||
OtherAppPermissionScreen( |
|
||||
onEnableOverlayClick = {}, |
|
||||
onNotNowClick = {} |
|
||||
) |
|
||||
} |
|
||||
@ -0,0 +1,142 @@ |
|||||
|
package com.example.dattingapp.permission |
||||
|
|
||||
|
import android.Manifest |
||||
|
import android.content.Context |
||||
|
import android.content.Intent |
||||
|
import android.content.pm.PackageManager |
||||
|
import android.net.Uri |
||||
|
import android.os.Build |
||||
|
import android.provider.Settings |
||||
|
import androidx.activity.ComponentActivity |
||||
|
import androidx.activity.result.ActivityResultLauncher |
||||
|
import androidx.activity.result.contract.ActivityResultContracts |
||||
|
import androidx.core.content.ContextCompat |
||||
|
|
||||
|
class PermissionHelper(private val activity: ComponentActivity) { |
||||
|
|
||||
|
private lateinit var permissionLauncher: ActivityResultLauncher<Array<String>> |
||||
|
private lateinit var overlayLauncher: ActivityResultLauncher<Intent> |
||||
|
|
||||
|
private var onResult: ((Boolean) -> Unit)? = null |
||||
|
|
||||
|
init { |
||||
|
registerLaunchers() |
||||
|
} |
||||
|
|
||||
|
private fun registerLaunchers() { |
||||
|
// Multi-permission launcher for standard runtime permissions |
||||
|
permissionLauncher = activity.registerForActivityResult( |
||||
|
ActivityResultContracts.RequestMultiplePermissions() |
||||
|
) { permissions -> |
||||
|
// Check if all permissions requested are granted |
||||
|
val allGranted = permissions.entries.all { it.value } |
||||
|
onResult?.invoke(allGranted) |
||||
|
} |
||||
|
|
||||
|
// Launcher for special permissions (Draw Over Other Apps) |
||||
|
overlayLauncher = activity.registerForActivityResult( |
||||
|
ActivityResultContracts.StartActivityForResult() |
||||
|
) { |
||||
|
val isGranted = isOverlayPermissionGranted() |
||||
|
onResult?.invoke(isGranted) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks whether permissions for a given type are already granted. |
||||
|
*/ |
||||
|
fun hasPermission(type: PermissionType): Boolean { |
||||
|
val permissions = getPermissionsForType(type) |
||||
|
if (permissions.isEmpty()) return true |
||||
|
|
||||
|
return permissions.all { permission -> |
||||
|
ContextCompat.checkSelfPermission( |
||||
|
activity, |
||||
|
permission |
||||
|
) == PackageManager.PERMISSION_GRANTED |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Request standard runtime permissions: NOTIFICATION, GALLERY, AUDIO, or LOCATION |
||||
|
*/ |
||||
|
fun requestPermissions(type: PermissionType, callback: (Boolean) -> Unit) { |
||||
|
this.onResult = callback |
||||
|
val permissionsToRequest = getPermissionsForType(type) |
||||
|
|
||||
|
if (permissionsToRequest.isNotEmpty()) { |
||||
|
permissionLauncher.launch(permissionsToRequest) |
||||
|
} else { |
||||
|
callback(true) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Request Special Permission: Draw Over Other Apps |
||||
|
*/ |
||||
|
fun requestOverlayPermission(callback: (Boolean) -> Unit) { |
||||
|
this.onResult = callback |
||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||
|
if (!Settings.canDrawOverlays(activity)) { |
||||
|
val intent = Intent( |
||||
|
Settings.ACTION_MANAGE_OVERLAY_PERMISSION, |
||||
|
Uri.parse("package:${activity.packageName}") |
||||
|
) |
||||
|
overlayLauncher.launch(intent) |
||||
|
} else { |
||||
|
callback(true) |
||||
|
} |
||||
|
} else { |
||||
|
callback(true) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if Overlay Permission is currently granted without opening Settings |
||||
|
*/ |
||||
|
fun isOverlayPermissionGranted(): Boolean { |
||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||
|
Settings.canDrawOverlays(activity) |
||||
|
} else { |
||||
|
true |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private fun getPermissionsForType(type: PermissionType): Array<String> { |
||||
|
return when (type) { |
||||
|
PermissionType.NOTIFICATION -> { |
||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
||||
|
arrayOf(Manifest.permission.POST_NOTIFICATIONS) |
||||
|
} else { |
||||
|
emptyArray() |
||||
|
} |
||||
|
} |
||||
|
PermissionType.GALLERY -> { |
||||
|
when { |
||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> arrayOf( |
||||
|
Manifest.permission.READ_MEDIA_IMAGES, |
||||
|
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED |
||||
|
) |
||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> arrayOf( |
||||
|
Manifest.permission.READ_MEDIA_IMAGES |
||||
|
) |
||||
|
else -> arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) |
||||
|
} |
||||
|
} |
||||
|
PermissionType.AUDIO -> arrayOf(Manifest.permission.RECORD_AUDIO) |
||||
|
PermissionType.LOCATION -> arrayOf( |
||||
|
Manifest.permission.ACCESS_FINE_LOCATION, |
||||
|
Manifest.permission.ACCESS_COARSE_LOCATION |
||||
|
) |
||||
|
PermissionType.OVERLAY -> emptyArray() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
enum class PermissionType { |
||||
|
NOTIFICATION, |
||||
|
GALLERY, |
||||
|
AUDIO, |
||||
|
LOCATION, |
||||
|
OVERLAY |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue