diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7b3006b --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..cde3e19 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,57 @@ + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..6d0ee1c --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..74dd639 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..509f39b --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,68 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) +} + +android { + namespace = "com.absar.cataliftapp" + compileSdk = 35 + + defaultConfig { + applicationId = "com.absar.cataliftapp" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildFeatures { + compose = true + } +} + +dependencies { + + implementation ("androidx.hilt:hilt-navigation-compose:1.0.0") + implementation ("com.google.dagger:hilt-android:2.44") + //kapt("com.google.dagger:hilt-compiler:2.44") + implementation("androidx.compose.material3:material3:1.2.0") + implementation("androidx.navigation:navigation-compose:2.7.0") + implementation ("io.coil-kt:coil-compose:2.3.0") + implementation ("androidx.compose.material:material-icons-extended") + + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/absar/cataliftapp/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/absar/cataliftapp/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..8f88090 --- /dev/null +++ b/app/src/androidTest/java/com/absar/cataliftapp/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.absar.cataliftapp + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.absar.cataliftapp", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1fac4dc --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/absar/cataliftapp/AddPost.kt b/app/src/main/java/com/absar/cataliftapp/AddPost.kt new file mode 100644 index 0000000..0f0b1b5 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/AddPost.kt @@ -0,0 +1,62 @@ +package com.absar.cataliftapp + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.material3.OutlinedTextField as OutlinedTextField1 + +@Composable +fun AddPostDialog( + onDismiss: () -> Unit, + onAdd: (title: String, summary: String, details: String) -> Unit +) { + var title by remember { mutableStateOf("") } + var summary by remember { mutableStateOf("") } + var details by remember { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismiss, + confirmButton = { + TextButton( + onClick = { + if (title.isNotBlank() && summary.isNotBlank() && details.isNotBlank()) { + onAdd(title, summary, details) + onDismiss() + } + } + ) { Text("Add") } + }, + dismissButton = { + TextButton(onClick = onDismiss) { Text("Cancel") } + }, + title = { Text("Add New Post") }, + text = { + Column{ + OutlinedTextField1( + value = title, + onValueChange = { title = it }, + label = { Text("Title") }, + singleLine = true + ) + OutlinedTextField1( + value = summary, + onValueChange = { summary = it }, + label = { Text("Summary") }, + singleLine = true + ) + OutlinedTextField1( + value = details, + onValueChange = { details = it }, + label = { Text("Details") }, + maxLines = 4 + ) + } + } + ) +} diff --git a/app/src/main/java/com/absar/cataliftapp/CataliftApp.kt b/app/src/main/java/com/absar/cataliftapp/CataliftApp.kt new file mode 100644 index 0000000..6ee4035 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/CataliftApp.kt @@ -0,0 +1,48 @@ +package com.absar.cataliftapp + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.absar.cataliftapp.Chat.ChatScreen + +@Composable +fun CataliftApp() { + val navController = rememberNavController() + + Scaffold( + topBar = { + TopBar(navController = navController) + }, + bottomBar = { BottomNavBar(navController) } + ) { padding -> + NavHost( + navController = navController, + startDestination = "home", + modifier = Modifier.padding(padding) + ) { + composable("home") { + HomeScreen(navController) + } + composable("chat_screen") { + ChatScreen() + } + composable("notifications_screen") { + NotificationScreen() + } + composable("profile") { + MyProfileScreen() + } + composable("explore") { + ExploreMentorsScreen() + } + composable("courses") { + CoursesScreen() + } + + } + } +} diff --git a/app/src/main/java/com/absar/cataliftapp/Chat/ChatContent.kt b/app/src/main/java/com/absar/cataliftapp/Chat/ChatContent.kt new file mode 100644 index 0000000..60b6be2 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/Chat/ChatContent.kt @@ -0,0 +1,138 @@ +package com.absar.cataliftapp.Chat + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Send +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.absar.cataliftapp.CircleAvatar + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChatContent(contactName: String, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .padding(16.dp) + .fillMaxSize() + ) { + // Header + Row(verticalAlignment = Alignment.CenterVertically) { + CircleAvatar() + Spacer(Modifier.width(8.dp)) + Column { + Text(contactName, color = Color.White, fontWeight = FontWeight.Bold) + Text("Product Manager", color = Color.Gray, fontSize = 12.sp) + } + } + + Spacer(Modifier.height(16.dp)) + + Box( + modifier = Modifier + .weight(1f) + .padding(bottom = 8.dp) + ) { + LazyColumn(reverseLayout = true) { + items(5) { + when (it % 2) { + 0 -> OutgoingMessage( + "Hello from Absar to $contactName! \nMessage ${5 - it}.", + "${it + 1} min ago" + ) + else -> IncomingMessage( + "Hi Absar, it's $contactName. \nMessage ${5 - it}.", + "${it + 1} min ago" + ) + } + Spacer(modifier = Modifier.height(8.dp)) + } + } + } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .background(Color(0xFF1E1E1E), RoundedCornerShape(24.dp)) + .padding(horizontal = 12.dp, vertical = 8.dp) + ) { + var message by remember { mutableStateOf("") } + + TextField( + value = message, + onValueChange = { message = it }, + placeholder = { Text("Type a message", color = Color.Gray) }, + colors = TextFieldDefaults.textFieldColors( + containerColor = Color.Transparent, + focusedTextColor = Color.White, + cursorColor = Color.White + ), + modifier = Modifier.weight(1f), + maxLines = 2 + ) + + IconButton(onClick = { /* Handle send */ }) { + Icon( + imageVector = Icons.Default.Send, + contentDescription = "Send", + tint = Color(0xFF00ACC1) + ) + } + } + } +} + +@Composable +fun OutgoingMessage(text: String, time: String) { + Column(horizontalAlignment = Alignment.End, modifier = Modifier.fillMaxWidth()) { + Box( + modifier = Modifier + .background(Color(0xFF245cb5), RoundedCornerShape(12.dp)) + .padding(12.dp) + ) { + Text(text, color = Color.White) + } + Text(time, color = Color.Gray, fontSize = 10.sp) + } +} + + +@Composable +fun IncomingMessage(text: String, time: String) { + Column(horizontalAlignment = Alignment.Start, modifier = Modifier.fillMaxWidth()) { + Box( + modifier = Modifier + .background(Color(0xFF1E1E1E), RoundedCornerShape(12.dp)) + .padding(12.dp) + ) { + Text(text, color = Color.White) + } + Text(time, color = Color.Gray, fontSize = 10.sp) + } +} diff --git a/app/src/main/java/com/absar/cataliftapp/Chat/ChatLeftSide.kt b/app/src/main/java/com/absar/cataliftapp/Chat/ChatLeftSide.kt new file mode 100644 index 0000000..677cff9 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/Chat/ChatLeftSide.kt @@ -0,0 +1,83 @@ +package com.absar.cataliftapp.Chat + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ContactListSidebar( + selectedContact: String, + onContactSelected: (String) -> Unit, + modifier: Modifier = Modifier +) { + Column(modifier = modifier.padding(12.dp)) { + Text("Chats", color = Color.White, style = MaterialTheme.typography.titleLarge) + + Spacer(Modifier.height(12.dp)) + + TextField( + value = "", + onValueChange = {}, + placeholder = { Text("Search", color = Color.White, fontSize = 14.sp) }, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = null, tint = Color.White) }, + colors = TextFieldDefaults.textFieldColors(containerColor = Color.DarkGray), + modifier = Modifier + .fillMaxWidth() + ) + + + Spacer(Modifier.height(16.dp)) + + // Contact list + ContactItem("Bob The Builder", "You: Hello from Absar", isSelected = (selectedContact == "Bob The Builder")) + { + onContactSelected("Bob The Builder") + } + ContactItem("Harry Potter", "You: Hello from Absar", isSelected = (selectedContact == "Harry Potter")) + { + onContactSelected("Harry Potter") + } + ContactItem("WalterWhite", "You: Hello from Absar", isSelected = (selectedContact == "WalterWhite")) + { + onContactSelected("WalterWhite") + } + } +} + + +@Composable +fun ContactItem(name: String, lastMsg: String, isSelected: Boolean = false, onClick: () -> Unit) { + val bgColor = if (isSelected) Color(0xFF245cb5) else Color.Transparent + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + .background(bgColor, RoundedCornerShape(12.dp)) + .padding(12.dp) + .clickable { onClick() } + ) { + Text(name, color = Color.White, fontWeight = FontWeight.Bold) + Text(lastMsg, color = Color.Gray, fontSize = 12.sp) + } +} + diff --git a/app/src/main/java/com/absar/cataliftapp/Chat/ChatScreen.kt b/app/src/main/java/com/absar/cataliftapp/Chat/ChatScreen.kt new file mode 100644 index 0000000..1ad005c --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/Chat/ChatScreen.kt @@ -0,0 +1,42 @@ + package com.absar.cataliftapp.Chat + + import androidx.compose.foundation.background + import androidx.compose.foundation.layout.Row + import androidx.compose.foundation.layout.fillMaxHeight + import androidx.compose.foundation.layout.fillMaxSize + import androidx.compose.foundation.layout.width + import androidx.compose.material3.Divider + import androidx.compose.runtime.Composable + import androidx.compose.runtime.getValue + import androidx.compose.runtime.mutableStateOf + import androidx.compose.runtime.remember + import androidx.compose.runtime.setValue + import androidx.compose.ui.Modifier + import androidx.compose.ui.graphics.Color + import androidx.compose.ui.unit.dp + + @Composable + fun ChatScreen() { + var selectedContact by remember { mutableStateOf("Bob The Builder") } + + Row(modifier = Modifier.fillMaxSize().background(Color.Black)) { + // Left sidebar (narrower) + ContactListSidebar( + selectedContact = selectedContact, + onContactSelected = { selectedContact = it }, + modifier = Modifier + .width(160.dp) // Reduced from 240.dp + .fillMaxHeight() + .background(Color(0xFF121212)) + ) + + // Divider + Divider(color = Color.Gray, modifier = Modifier.width(1.dp).fillMaxHeight()) + + // Chat content (wider area) + ChatContent( + contactName = selectedContact, + modifier = Modifier.weight(1f) // Takes remaining space + ) + } + } diff --git a/app/src/main/java/com/absar/cataliftapp/CircleAvatar.kt b/app/src/main/java/com/absar/cataliftapp/CircleAvatar.kt new file mode 100644 index 0000000..904b3ff --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/CircleAvatar.kt @@ -0,0 +1,26 @@ +package com.absar.cataliftapp + +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Person +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp + +@Composable +fun CircleAvatar() { + Icon( + imageVector = Icons.Default.Person, + contentDescription = "User Avatar", + modifier = Modifier + .width(40.dp) + .height(40.dp) + .clip(RoundedCornerShape(50)) + .padding(4.dp) + ) +} diff --git a/app/src/main/java/com/absar/cataliftapp/CoursesScreen.kt b/app/src/main/java/com/absar/cataliftapp/CoursesScreen.kt new file mode 100644 index 0000000..b532100 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/CoursesScreen.kt @@ -0,0 +1,93 @@ +package com.absar.cataliftapp + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.School +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight + +data class Course( + val id: Int, + val title: String, + val instructor: String, + val description: String +) + +val sampleCourses = listOf( + Course(1, "Introduction to Kotlin", "John Doe", "Learn the basics of Kotlin programming language."), + Course(2, "Jetpack Compose Essentials", "Jane Smith", "Master the fundamentals of Jetpack Compose for Android UI development."), + Course(3, "Advanced Android Development", "Emily Johnson", "Deep dive into advanced topics of Android development including performance optimization.") +) + +@Composable +fun CourseCard(course: Course, onClick: () -> Unit) { + Card( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() }, + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(4.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = Icons.Filled.School, + contentDescription = "Course Icon", + tint = Color.Black, + modifier = Modifier.size(40.dp) + ) + Spacer(Modifier.width(16.dp)) + Column { + Text(course.title, fontWeight = FontWeight.Bold, fontSize = 18.sp, color = Color.Black) + Text(course.instructor, fontSize = 14.sp, color = Color(0xFF20E0D0)) + } + } + Spacer(Modifier.height(8.dp)) + Text(course.description, fontSize = 14.sp, color = Color.DarkGray) + } + } +} + +@Composable +fun CoursesScreen(courses: List = sampleCourses) { + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + .padding(16.dp) + ) { + Text( + "Courses", + color = Color.Black, + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold + ) + Spacer(Modifier.height(12.dp)) + LazyColumn( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.fillMaxSize() + ) { + items(courses) { course -> + CourseCard(course = course, onClick = { + // TODO: onclick is left which i'll add later in a hurry of time + }) + } + } + } +} diff --git a/app/src/main/java/com/absar/cataliftapp/ExploreMentorsScreen.kt b/app/src/main/java/com/absar/cataliftapp/ExploreMentorsScreen.kt new file mode 100644 index 0000000..15a3142 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/ExploreMentorsScreen.kt @@ -0,0 +1,133 @@ +package com.absar.cataliftapp + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.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.filled.Person +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +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.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +data class Mentor( + val id: Int, + val name: String, + val role: String, + val bio: String, + val avatarUrl: String? = null +) + +val sampleMentors = listOf( + Mentor( + id = 1, + name = "Priya Sharma", + role = "AI Researcher", + bio = "Passionate about machine learning and data science. Loves teaching and mentoring the next generation." + ), + Mentor( + id = 2, + name = "Rahul Verma", + role = "Android Developer", + bio = "Building beautiful apps with Jetpack Compose. Kotlin enthusiast and open source contributor." + ), + Mentor( + id = 3, + name = "Aisha Khan", + role = "Product Manager", + bio = "Bridging the gap between tech and business. Here to help you grow your product mindset." + ) +) + +@Composable +fun MentorCard(mentor: Mentor, onClick: () -> Unit) { + Card( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() }, + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(6.dp) + ) { + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(56.dp) + .clip(CircleShape) + .background(Color(0xFF245CB5)), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.Person, + contentDescription = "Mentor Avatar", + tint = Color.Black, + modifier = Modifier.size(36.dp) + ) + } + + Spacer(Modifier.width(16.dp)) + + Column(modifier = Modifier.weight(1f)) { + Text(mentor.name, color = Color.Black, fontWeight = FontWeight.Bold, fontSize = 18.sp) + Text(mentor.role, color = Color(0xFF20E0D0), fontSize = 14.sp) + Spacer(Modifier.height(4.dp)) + Text(mentor.bio, color = Color.DarkGray, fontSize = 13.sp, maxLines = 2) + } + } + } +} + +@Composable +fun ExploreMentorsScreen(mentors: List = sampleMentors) { + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + .padding(16.dp) + ) { + Text( + "Explore Mentors", + color = Color.Black, + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold + ) + Spacer(Modifier.height(12.dp)) + + LazyColumn( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.fillMaxSize() + ) { + items(mentors) { mentor -> + MentorCard(mentor = mentor, onClick = { + // TODO: mentor section can be expanded but will do later + }) + } + } + } +} diff --git a/app/src/main/java/com/absar/cataliftapp/Footer.kt b/app/src/main/java/com/absar/cataliftapp/Footer.kt new file mode 100644 index 0000000..408271c --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/Footer.kt @@ -0,0 +1,34 @@ +package com.absar.cataliftapp + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState + +@Composable +fun BottomNavBar(navController: NavHostController) { + val items = listOf( + Screen.Home to Icons.Default.Home, + Screen.Explore to Icons.Default.Search, + Screen.Courses to Icons.Default.Menu + ) + + NavigationBar { + val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route + items.forEach { (screen, icon) -> + NavigationBarItem( + selected = currentRoute == screen.route, + onClick = { navController.navigate(screen.route) }, + icon = { Icon(imageVector = icon, contentDescription = null) }, + label = { Text(screen.route.capitalize()) } + ) + } + } +} diff --git a/app/src/main/java/com/absar/cataliftapp/Header.kt b/app/src/main/java/com/absar/cataliftapp/Header.kt new file mode 100644 index 0000000..0ca8343 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/Header.kt @@ -0,0 +1,59 @@ +package com.absar.cataliftapp + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AccountCircle +import androidx.compose.material.icons.filled.ArrowForward +import androidx.compose.material.icons.filled.Chat +import androidx.compose.material.icons.filled.ChatBubble +import androidx.compose.material.icons.filled.Notifications +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import androidx.navigation.NavController + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TopBar(navController: NavController) { + TopAppBar( + title = { + Text( + buildAnnotatedString { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { append("CATA") } + withStyle(SpanStyle(fontWeight = FontWeight.Light)) { append("LIFT") } + }, + color = Color.White + ) + }, + actions = { + IconButton(onClick = { navController.navigate(Screen.Profile.route) }) { + Icon(Icons.Default.AccountCircle, contentDescription = null, tint = Color.White) + } + IconButton(onClick = { + navController.navigate(Screen.Notifications.route) + }) { + Icon(Icons.Default.Notifications, contentDescription = null, tint = Color.White) + } + IconButton(onClick = { + navController.navigate(Screen.Chat.route) + }) { + Icon(Icons.Default.Chat, contentDescription = null, tint = Color.White) + } + }, + colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color(0xFF001E6C)) + ) +} diff --git a/app/src/main/java/com/absar/cataliftapp/HomeScreen.kt b/app/src/main/java/com/absar/cataliftapp/HomeScreen.kt new file mode 100644 index 0000000..0897c52 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/HomeScreen.kt @@ -0,0 +1,80 @@ +package com.absar.cataliftapp + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController + +@Composable +fun HomeScreen(navController: NavController) { + var posts by remember { mutableStateOf(samplePosts) } + var searchQuery by remember { mutableStateOf("") } + var showAddDialog by remember { mutableStateOf(false) } + val snackbarHostState = remember { SnackbarHostState() } + + Scaffold( + snackbarHost = { SnackbarHost(snackbarHostState) }, + content = { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + .background(MaterialTheme.colorScheme.background) + .padding(16.dp) + ) { + SearchAndAddBar( + onSearch = { query -> + searchQuery = query + posts = if (query.isBlank()) { + samplePosts + } else { + samplePosts.filter { + it.title.contains(query, ignoreCase = true) || + it.summary.contains(query, ignoreCase = true) + } + } + }, + onAddClick = { showAddDialog = true } + ) + + Spacer(Modifier.height(16.dp)) + + PostFeed(posts = posts, snackbarHostState = snackbarHostState) + + if (showAddDialog) { + AddPostDialog( + onDismiss = { showAddDialog = false }, + onAdd = { title, summary, details -> + val newId = (posts.maxOfOrNull { it.id } ?: 0) + 1 + posts = listOf( + Post( + id = newId, + title = title, + summary = summary, + details = details, + imageRes = R.drawable.placeholder2, + author = "Absar" + ) + ) + posts + } + ) + } + } + } + ) +} diff --git a/app/src/main/java/com/absar/cataliftapp/MainActivity.kt b/app/src/main/java/com/absar/cataliftapp/MainActivity.kt new file mode 100644 index 0000000..6f0e74c --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/MainActivity.kt @@ -0,0 +1,20 @@ +package com.absar.cataliftapp + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.compose.setContent +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + CataliftApp() + } + } +} diff --git a/app/src/main/java/com/absar/cataliftapp/Navigation.kt b/app/src/main/java/com/absar/cataliftapp/Navigation.kt new file mode 100644 index 0000000..42a35ad --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/Navigation.kt @@ -0,0 +1,31 @@ +package com.absar.cataliftapp + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import com.absar.cataliftapp.Chat.ChatScreen + +sealed class Screen(val route: String) { + object Home : Screen("home") + object Explore : Screen("explore") + object Courses : Screen("courses") + object Chat: Screen("chat_screen") + object Notifications : Screen("notifications_screen") + object Profile : Screen("profile") +} + + +@Composable +fun NavigationGraph(navController: NavHostController, modifier: Modifier = Modifier) { + NavHost(navController, startDestination = Screen.Home.route, modifier = modifier) { + composable(Screen.Home.route) { HomeScreen(navController) } + composable(Screen.Explore.route) { ExploreMentorsScreen() } + composable(Screen.Courses.route) { CoursesScreen() } + composable(Screen.Chat.route) { ChatScreen() } + composable(Screen.Notifications.route) { NotificationScreen() } + composable(Screen.Profile.route) { MyProfileScreen() } + + } +} diff --git a/app/src/main/java/com/absar/cataliftapp/Notification.kt b/app/src/main/java/com/absar/cataliftapp/Notification.kt new file mode 100644 index 0000000..c013e35 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/Notification.kt @@ -0,0 +1,195 @@ +package com.absar.cataliftapp + +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.RoundedCornerShape + import androidx.compose.material.icons.Icons + import androidx.compose.material.icons.filled.ThumbUp + import androidx.compose.material.icons.filled.Archive + import androidx.compose.material.icons.outlined.ChatBubbleOutline + import androidx.compose.material3.* + import androidx.compose.runtime.* + import androidx.compose.ui.Alignment + import androidx.compose.ui.Modifier + import androidx.compose.ui.graphics.Color + import androidx.compose.ui.text.font.FontWeight + import androidx.compose.ui.unit.dp + import androidx.compose.ui.unit.sp + import androidx.lifecycle.ViewModel + import androidx.lifecycle.viewmodel.compose.viewModel + +data class Notification( + val id: Int, + val icon: @Composable () -> Unit, + val title: String, + val source: String, + val time: String, + var isRead: Boolean, + val content: String + ) + +class NotificationViewModel : ViewModel() { + private val _notifications = mutableStateListOf( + Notification( + id = 1, + icon = { + Icon( + imageVector = Icons.Filled.ThumbUp, + contentDescription = "Like", + tint = Color(0xFF245cb5), + modifier = Modifier.size(24.dp) + ) + }, + title = "New Feature Update", + source = "System", + time = "5 minutes ago", + isRead = false, + content = "We've released a new dashboard feature..." + ), + Notification( + id = 2, + icon = { + Icon( + imageVector = Icons.Outlined.ChatBubbleOutline, + contentDescription = "Comment", + tint = Color(0xFF245cb5), + modifier = Modifier.size(24.dp) + ) + }, + title = "Task Assigned", + source = "Jira", + time = "30 minutes ago", + isRead = false, + content = "New task in Jira dashboard" + ), + Notification( + id = 3, + icon = { + Icon( + imageVector = Icons.Filled.Archive, + contentDescription = "Archive", + tint = Color(0xFF245cb5), + modifier = Modifier.size(24.dp) + ) + }, + title = "Weekly Report", + source = "Confluence", + time = "2 hours ago", + isRead = true, + content = "Q3 performance report published" + ) + ) + + val notifications: List get() = _notifications + val unreadCount get() = _notifications.count { !it.isRead } + + fun markAsRead(id: Int) { + val index = _notifications.indexOfFirst { it.id == id } + if (index != -1) { + _notifications[index] = _notifications[index].copy(isRead = true) + } + } + + fun markAllRead() { + _notifications.replaceAll { it.copy(isRead = true) } + } + } + +@Composable +fun NotificationPanel(viewModel: NotificationViewModel = viewModel()) { + val notifications by remember { derivedStateOf { viewModel.notifications } } + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFF181A20)) + .padding(24.dp) + ) { + Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + Text( + "Notifications", + fontWeight = FontWeight.Bold, + fontSize = 28.sp, + color = Color.White, + modifier = Modifier.weight(1f) + ) + Button( + onClick = viewModel::markAllRead, + colors = ButtonDefaults.outlinedButtonColors(contentColor = Color(0xFFffffff)) + ) { + Text("Mark all read (${viewModel.unreadCount})") + } + } + + Spacer(Modifier.height(24.dp)) + + LazyColumn(verticalArrangement = Arrangement.spacedBy(24.dp)) { + items(notifications) { notification -> + NotificationCard( + notification = notification, + onMarkRead = { viewModel.markAsRead(notification.id) } + ) + } + } + } +} + +@Composable +fun NotificationCard(notification: Notification, onMarkRead: () -> Unit) { + Card( + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color(0xFF20232A)), + modifier = Modifier.fillMaxWidth() + ) { + Column(Modifier.padding(24.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { notification.icon() + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f)) { + Text( + text = notification.title, + color = if (notification.isRead) Color.Gray else Color.White, + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + Text( + text = "${notification.source} • ${notification.time}", + color = Color.Gray, + fontSize = 13.sp + ) + } + if (!notification.isRead) { + Badge(containerColor = Color(0xFF20E0D0)) { + Text("NEW", color = Color.Black, fontSize = 10.sp) + } + } + } + + Spacer(Modifier.height(12.dp)) + Text(notification.content, color = Color.White, fontSize = 15.sp) + + Spacer(Modifier.height(16.dp)) + + Row { + OutlinedButton( + onClick = onMarkRead, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.outlinedButtonColors( + containerColor = Color.Transparent, + contentColor = Color.White + ) + ) { + Text("Mark Read") + } + } + } + } +} + + @Composable +fun NotificationScreen() { + val viewModel: NotificationViewModel = viewModel() + NotificationPanel(viewModel = viewModel) +} diff --git a/app/src/main/java/com/absar/cataliftapp/PostCard.kt b/app/src/main/java/com/absar/cataliftapp/PostCard.kt new file mode 100644 index 0000000..8188998 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/PostCard.kt @@ -0,0 +1,318 @@ +package com.absar.cataliftapp + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Comment +import androidx.compose.material.icons.filled.PersonAdd +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.outlined.Star +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +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.unit.dp +import androidx.compose.ui.unit.sp +import com.absar.cataliftapp.CircleAvatar +import com.absar.cataliftapp.R +import kotlinx.coroutines.launch + + +data class Post( + val id: Int, + val author: String, + val title: String, + val summary: String, + val details: String, + val imageRes: Int +) + +val samplePosts = listOf( + Post( + id = 1, + author = "Akhilesh Yadav", + title = "The Briggs–Rauscher Reaction: A Mesmerizing Chemical Dance 🌈", + summary = "This captivating process uses hydrogen peroxide...", + details = "This captivating process uses hydrogen peroxide, iodate, malonic acid, manganese sulfate, and starch to create oscillating color changes. The reaction is a classic example of a chemical oscillator and is often used in chemistry demonstrations to illustrate non-equilibrium thermodynamics and reaction kinetics. The solution cycles between colorless, amber, and deep blue in a mesmerizing dance, captivating audiences and illustrating the beauty of chemistry in action.\n💡 Follow @Science for more", + imageRes = R.drawable.placeholder2 + ), + Post( + id = 2, + author = "Priya Sharma", + title = "AI in Healthcare: Transforming the Future", + summary = "AI is revolutionizing diagnostics and patient care...", + details = "AI is revolutionizing diagnostics and patient care by enabling faster, more accurate analysis and personalized medicine. From predictive analytics to robotic surgery, the possibilities are endless.\n💡 Follow @HealthTech for more", + imageRes = R.drawable.placeholder2 + ), +) + +@Composable +fun PostFeed(posts: List, snackbarHostState: SnackbarHostState) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFffffff)), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + items(posts, key = { it.id }) { post -> + PostCardWithState(post, snackbarHostState) + } + } +} + + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PostCardWithState(post: Post, snackbarHostState: SnackbarHostState) { + var expanded by rememberSaveable(post.id) { mutableStateOf(false) } + var showComments by remember { mutableStateOf(false) } + var commentInput by remember { mutableStateOf("") } + var comments by rememberSaveable(post.id) { + mutableStateOf(listOf("Great post!", "Wow, I didn’t know that!", "Awesome 🤯")) + } + var isStarred by rememberSaveable(post.id) { mutableStateOf(false) } + var starCount by rememberSaveable(post.id) { mutableStateOf(1546) } + + val snackbarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + + Card( + shape = RoundedCornerShape(20.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), + elevation = CardDefaults.cardElevation(6.dp), + modifier = Modifier + .fillMaxWidth() + .clickable { expanded = !expanded } + ) { + if (expanded) { + Column( + modifier = Modifier + .fillMaxWidth() + .heightIn(max = 500.dp) + .verticalScroll(rememberScrollState()) + .padding(16.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + CircleAvatar() + Spacer(Modifier.width(8.dp)) + Column { + Text(post.author, fontWeight = FontWeight.Bold) + Text("1d • Edited", fontSize = 12.sp) + } + Spacer(Modifier.weight(1f)) + IconButton(onClick = { + coroutineScope.launch { + snackbarHostState.showSnackbar("Friend request sent!") + } + }) { + Icon(Icons.Default.PersonAdd, contentDescription = "Add Friend") + } + } + + Spacer(Modifier.height(8.dp)) + + Text(post.title, fontWeight = FontWeight.SemiBold) + Text(post.details) + + Spacer(Modifier.height(8.dp)) + + Image( + painter = painterResource(id = post.imageRes), + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .height(180.dp) + .clip(RoundedCornerShape(12.dp)), + contentScale = ContentScale.Crop + ) + + Spacer(Modifier.height(8.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = { + isStarred = !isStarred + starCount += if (isStarred) 1 else -1 + }) { + Icon( + imageVector = if (isStarred) Icons.Filled.Star else Icons.Outlined.Star, + contentDescription = "Star", + tint = if (isStarred) Color.Yellow else Color.Gray + ) + } + Spacer(Modifier.width(4.dp)) + Text("$starCount Stars") + } + + Spacer(modifier = Modifier.weight(1f)) + + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = { showComments = true }) { + Icon(Icons.Filled.Comment, contentDescription = "Comments") + } + Spacer(Modifier.width(4.dp)) + Text("${comments.size} comments") + } + } + + Spacer(Modifier.height(12.dp)) + Text("Comments", fontWeight = FontWeight.Bold, fontSize = 18.sp) + Spacer(Modifier.height(8.dp)) + comments.forEach { + Text("• $it", modifier = Modifier.padding(vertical = 2.dp)) + } + Spacer(Modifier.height(12.dp)) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedTextField( + value = commentInput, + onValueChange = { commentInput = it }, + label = { Text("Write a comment...") }, + modifier = Modifier.weight(1f) + ) + Spacer(Modifier.width(8.dp)) + Button( + onClick = { + if (commentInput.isNotBlank()) { + comments = comments + commentInput + commentInput = "" + } + } + ) { + Text("Post") + } + } + } + } else { + Column(modifier = Modifier.padding(16.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + CircleAvatar() + Spacer(Modifier.width(8.dp)) + Column { + Text(post.author, fontWeight = FontWeight.Bold) + Text("1d • Edited", fontSize = 12.sp) + } + Spacer(Modifier.weight(1f)) + IconButton(onClick = { + coroutineScope.launch { + snackbarHostState.showSnackbar("Friend request sent!") + } + }) { + Icon(Icons.Default.PersonAdd, contentDescription = "Add Friend") + } + } + + Spacer(Modifier.height(8.dp)) + + Text(post.title, fontWeight = FontWeight.SemiBold) + Text(post.summary) + + Spacer(Modifier.height(8.dp)) + + Image( + painter = painterResource(id = post.imageRes), + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .height(180.dp) + .clip(RoundedCornerShape(12.dp)), + contentScale = ContentScale.Crop + ) + + Spacer(Modifier.height(8.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = { + isStarred = !isStarred + starCount += if (isStarred) 1 else -1 + }) { + Icon( + imageVector = if (isStarred) Icons.Filled.Star else Icons.Outlined.Star, + contentDescription = "Star", + tint = if (isStarred) Color.Yellow else Color.Gray + ) + } + Spacer(Modifier.width(4.dp)) + Text("$starCount Stars") + } + + Spacer(modifier = Modifier.weight(1f)) + + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = { showComments = true }) { + Icon(Icons.Filled.Comment, contentDescription = "Comments") + } + Spacer(Modifier.width(4.dp)) + Text("${comments.size} comments") + } + } + } + } + + if (showComments && !expanded) { + ModalBottomSheet( + onDismissRequest = { showComments = false }, + sheetState = sheetState, + dragHandle = { BottomSheetDefaults.DragHandle() } + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text("Comments", fontWeight = FontWeight.Bold, fontSize = 20.sp) + Spacer(Modifier.height(8.dp)) + comments.forEach { + Text("• $it", modifier = Modifier.padding(vertical = 2.dp)) + } + Spacer(Modifier.height(16.dp)) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedTextField( + value = commentInput, + onValueChange = { commentInput = it }, + label = { Text("Write a comment...") }, + modifier = Modifier.weight(1f) + ) + Spacer(Modifier.width(8.dp)) + Button( + onClick = { + if (commentInput.isNotBlank()) { + comments = comments + commentInput + commentInput = "" + } + } + ) { + Text("Post") + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/absar/cataliftapp/ProfileScreen.kt b/app/src/main/java/com/absar/cataliftapp/ProfileScreen.kt new file mode 100644 index 0000000..c87395d --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/ProfileScreen.kt @@ -0,0 +1,157 @@ +package com.absar.cataliftapp + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Email +import androidx.compose.material.icons.filled.Person +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.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.TextButton + +@Composable +fun MyProfileScreen() { + var user by remember { + mutableStateOf( + UserProfile( + name = "Absar Ali", + role = "Product Manager", + email = "absar@example.com", + bio = "Passionate about building products that matter. 🚀\nLoves Kotlin, Compose, and great coffee." + ) + ) + } + + var showEditDialog by remember { mutableStateOf(false) } + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFF181A20)) + .padding(horizontal = 24.dp, vertical = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + + Box( + modifier = Modifier + .size(96.dp) + .clip(CircleShape) + .background(Color(0xFF245CB5)), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.Person, + contentDescription = "Avatar", + tint = Color.White, + modifier = Modifier.size(64.dp) + ) + } + + Spacer(Modifier.height(16.dp)) + + Text(user.name, color = Color.White, fontWeight = FontWeight.Bold, fontSize = 24.sp) + Text(user.role, color = Color(0xFF20E0D0), fontSize = 16.sp, fontWeight = FontWeight.Medium) + Spacer(Modifier.height(8.dp)) + + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.Email, contentDescription = null, tint = Color.Gray, modifier = Modifier.size(18.dp)) + Spacer(Modifier.width(8.dp)) + Text(user.email, color = Color.Gray, fontSize = 14.sp) + } + + Spacer(Modifier.height(24.dp)) + + Card( + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color(0xFF23262F)), + modifier = Modifier.fillMaxWidth() + ) { + Text( + user.bio, + color = Color.White, + fontSize = 15.sp, + modifier = Modifier.padding(20.dp) + ) + } + + Spacer(Modifier.height(32.dp)) + + Button( + onClick = { showEditDialog = true }, + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF245CB5)) + ) { + Icon(Icons.Default.Edit, contentDescription = "Edit", tint = Color.White) + Spacer(Modifier.width(8.dp)) + Text("Edit Profile", color = Color.White) + } + } + + if (showEditDialog) { + EditProfileDialog( + user = user, + onDismiss = { showEditDialog = false }, + onSave = { updatedUser -> + user = updatedUser + showEditDialog = false + } + ) + } +} + +@Composable +fun EditProfileDialog( + user: UserProfile, + onDismiss: () -> Unit, + onSave: (UserProfile) -> Unit +) { + var name by remember { mutableStateOf(user.name) } + var role by remember { mutableStateOf(user.role) } + var email by remember { mutableStateOf(user.email) } + var bio by remember { mutableStateOf(user.bio) } + + AlertDialog( + onDismissRequest = onDismiss, + confirmButton = { + TextButton(onClick = { + onSave(UserProfile(name, role, email, bio)) + }) { Text("Save") } + }, + dismissButton = { + TextButton(onClick = onDismiss) { Text("Cancel") } + }, + title = { Text("Edit Profile") }, + text = { + Column { + OutlinedTextField( + value = name, onValueChange = { name = it }, + label = { Text("Name") }, singleLine = true + ) + OutlinedTextField( + value = role, onValueChange = { role = it }, + label = { Text("Role") }, singleLine = true + ) + OutlinedTextField( + value = email, onValueChange = { email = it }, + label = { Text("Email") }, singleLine = true + ) + OutlinedTextField( + value = bio, onValueChange = { bio = it }, + label = { Text("Bio") }, maxLines = 4 + ) + } + } + ) +} diff --git a/app/src/main/java/com/absar/cataliftapp/SearchBar.kt b/app/src/main/java/com/absar/cataliftapp/SearchBar.kt new file mode 100644 index 0000000..ea192f7 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/SearchBar.kt @@ -0,0 +1,90 @@ +package com.absar.cataliftapp + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.foundation.layout.* +import androidx.compose.material3.IconButton +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment + +@Composable +fun SearchAndAddBar( + onSearch: (String) -> Unit, + onAddClick: () -> Unit +) { + var query by remember { mutableStateOf("") } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + SearchBar( + query = query, + onQueryChange = { + query = it + onSearch(it) + }, + modifier = Modifier.weight(1f) + ) + + AddPostButton(onClick = onAddClick) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SearchBar( + query: String, + onQueryChange: (String) -> Unit, + modifier: Modifier = Modifier +) { + TextField( + value = query, + onValueChange = onQueryChange, + placeholder = { Text("Search", color = Color.Gray) }, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = "Search") }, + modifier = modifier + .background(Color(0xFFF0F0F0), shape = RoundedCornerShape(18.dp)), + colors = TextFieldDefaults.textFieldColors( + containerColor = Color(0xFFF0F0F0), + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent + ) + ) +} + +@Composable +fun AddPostButton(onClick: () -> Unit) { + IconButton( + onClick = onClick, + modifier = Modifier + .background( + Color(0xFF001E6C), + shape = RoundedCornerShape(12.dp) + ) + .size(48.dp) + ) { + Icon( + Icons.Default.Add, + contentDescription = "Add Post", + tint = Color.White + ) + } +} diff --git a/app/src/main/java/com/absar/cataliftapp/UserProfile.kt b/app/src/main/java/com/absar/cataliftapp/UserProfile.kt new file mode 100644 index 0000000..aeb6b48 --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/UserProfile.kt @@ -0,0 +1,9 @@ +package com.absar.cataliftapp + +data class UserProfile( + val name: String, + val role: String, + val email: String, + val bio: String, + val avatarUrl: String? = null +) diff --git a/app/src/main/java/com/absar/cataliftapp/ui/theme/Color.kt b/app/src/main/java/com/absar/cataliftapp/ui/theme/Color.kt new file mode 100644 index 0000000..d920ecb --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.absar.cataliftapp.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/app/src/main/java/com/absar/cataliftapp/ui/theme/Theme.kt b/app/src/main/java/com/absar/cataliftapp/ui/theme/Theme.kt new file mode 100644 index 0000000..86632ed --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/ui/theme/Theme.kt @@ -0,0 +1,84 @@ +package com.absar.cataliftapp.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +import androidx.compose.material3.* +import androidx.compose.ui.graphics.Color + + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun CataliftAppTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} + + + +private val LightColors = lightColorScheme( + primary = Color(0xFF001E6C), + secondary = Color(0xFF0269FF), + background = Color(0xFFF4F4F6), + surface = Color.White, + onPrimary = Color.White, + onSecondary = Color.White, + onBackground = Color.Black, + onSurface = Color.Black +) + +@Composable +fun CataliftTheme(content: @Composable () -> Unit) { + MaterialTheme( + colorScheme = LightColors, + typography = Typography(), + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/absar/cataliftapp/ui/theme/Type.kt b/app/src/main/java/com/absar/cataliftapp/ui/theme/Type.kt new file mode 100644 index 0000000..2bd884c --- /dev/null +++ b/app/src/main/java/com/absar/cataliftapp/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.absar.cataliftapp.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/placeholder.png/Vector.png b/app/src/main/res/drawable/placeholder.png/Vector.png new file mode 100644 index 0000000..090e662 Binary files /dev/null and b/app/src/main/res/drawable/placeholder.png/Vector.png differ diff --git a/app/src/main/res/drawable/placeholder2.png b/app/src/main/res/drawable/placeholder2.png new file mode 100644 index 0000000..090e662 Binary files /dev/null and b/app/src/main/res/drawable/placeholder2.png differ diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..bbc4e74 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + CataliftApp + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..653104c --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +