diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/ChatHistory_schema_v2.xml b/.idea/ChatHistory_schema_v2.xml
new file mode 100644
index 0000000..ed00cfe
--- /dev/null
+++ b/.idea/ChatHistory_schema_v2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 0897082..639c779 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -4,6 +4,7 @@
-
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8978d23..b2c751a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,6 @@
-
+
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/README.md b/README.md
index b1ceb26..03ffe14 100644
--- a/README.md
+++ b/README.md
@@ -15,14 +15,8 @@ Event Tracker is a user-friendly app designed to help individuals discover, mana
## Technologies Used
- **Kotlin:** For building a robust Android application with excellent performance and stability.
- **Jetpack Compose:** To create modern, responsive, and visually appealing user interfaces.
-- **Firebase:** For backend services such as authentication, real-time database management, and cloud storage.
-
-
-## Technologies Used
-- **Kotlin:** For robust Android application development, enhancing app stability and performance.
-- **Jetpack Compose:** To design modern, reactive UIs that are responsive and intuitive.
-- **Firebase:** For backend services such as authentication, database management, and cloud storage.
-
+- **SpringBoot:** For backend services such as authentication, real-time database management, and cloud storage.
+ link - https://github.com/yuvraj-coder1/EvenetTracker-Backend
## Demo-Video
[https://youtu.be/i6nAK03j8w8?si=yLfDeuTRFHS_z03o](https://github.com/yuvraj-coder1/EventTracker/assets/142040957/377dbac7-74c4-4059-8006-6b2ed5f32989)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 89a6f2d..7ccdcff 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -54,7 +54,8 @@ android {
}
dependencies {
-
+ implementation(libs.retrofit)
+ implementation(libs.retrofit.gson)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
@@ -78,6 +79,12 @@ dependencies {
implementation(libs.firebase.auth)
implementation(libs.firebase.database)
implementation(libs.firebase.firestore)
+ // Import the BoM for the Firebase platform
+ implementation(platform(libs.firebase.bom))
+
+ // Add the dependencies for the App Check libraries
+ // When using the BoM, you don't specify versions in Firebase library dependencies
+ implementation(libs.firebase.appcheck.playintegrity)
//coil
implementation(libs.coil.compose)
@@ -90,5 +97,12 @@ dependencies {
//serialization
implementation(libs.jetbrains.kotlinx.serialization.json)
+ implementation ("com.google.code.gson:gson:2.10.1")
+
+
+ //encrypted Shared Prefernces
+ implementation (libs.androidx.security.crypto)
+ //http interceptor
+ implementation ("com.squareup.okhttp3:logging-interceptor:4.9.3")
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 25c7b53..437bf51 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,8 +2,10 @@
+
+
=3) {
+ utils.logOutUser.value = true
+ return null;
+ }
+ return TODO("Provide the return value")
+ }
+ fun responseCount(response:Response):Int {
+ var prev = response.priorResponse
+ var count=0;
+ while(prev!=null){
+ count++;
+ prev = prev.priorResponse
+ if(count>3)
+ break;
+ }
+ return count;
+ }
+}
diff --git a/app/src/main/java/com/example/eventtracker/model/EventData.kt b/app/src/main/java/com/example/eventtracker/model/EventData.kt
index d9da9f3..713f462 100644
--- a/app/src/main/java/com/example/eventtracker/model/EventData.kt
+++ b/app/src/main/java/com/example/eventtracker/model/EventData.kt
@@ -9,7 +9,9 @@ data class EventData(
val description:String = "THis is the description of the event",
val category:String = "Technical",
val userId:String = "",
- val eventId:String = ""
+ val eventId:String = "",
+ val eventLink:String ="",
+ val eventImageUrl:String =""
) {
fun doesMatchSearchQuery(query: String):Boolean {
return name.contains(query, ignoreCase = true)
diff --git a/app/src/main/java/com/example/eventtracker/model/GetEventResponse.kt b/app/src/main/java/com/example/eventtracker/model/GetEventResponse.kt
new file mode 100644
index 0000000..117443d
--- /dev/null
+++ b/app/src/main/java/com/example/eventtracker/model/GetEventResponse.kt
@@ -0,0 +1,9 @@
+package com.example.eventtracker.model
+
+import com.example.eventtracker.dto.EventDto
+
+data class GetEventResponse(
+ val success: Boolean,
+ val message: String,
+ val data: List?
+)
diff --git a/app/src/main/java/com/example/eventtracker/model/SignUpResponse.kt b/app/src/main/java/com/example/eventtracker/model/SignUpResponse.kt
new file mode 100644
index 0000000..381360d
--- /dev/null
+++ b/app/src/main/java/com/example/eventtracker/model/SignUpResponse.kt
@@ -0,0 +1,6 @@
+package com.example.eventtracker.model
+
+data class SignUpResponse(
+ val success: Boolean,
+ val message: String,
+)
diff --git a/app/src/main/java/com/example/eventtracker/model/UserLogInRequest.kt b/app/src/main/java/com/example/eventtracker/model/UserLogInRequest.kt
new file mode 100644
index 0000000..8422bba
--- /dev/null
+++ b/app/src/main/java/com/example/eventtracker/model/UserLogInRequest.kt
@@ -0,0 +1,6 @@
+package com.example.eventtracker.model
+
+data class UserLogInRequest (
+ val username: String,
+ val password: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/eventtracker/model/UserLogInResponse.kt b/app/src/main/java/com/example/eventtracker/model/UserLogInResponse.kt
new file mode 100644
index 0000000..4c2b09a
--- /dev/null
+++ b/app/src/main/java/com/example/eventtracker/model/UserLogInResponse.kt
@@ -0,0 +1,17 @@
+package com.example.eventtracker.model
+
+import com.google.gson.annotations.SerializedName
+import kotlinx.serialization.Serializer
+
+data class UserLogInResponse(
+ val success: Boolean,
+ val message: String,
+ val data: LogInResponseData
+)
+data class LogInResponseData(
+ @SerializedName("access_token")
+ val jwt: String,
+ @SerializedName("refresh_token")
+ val refreshToken: String,
+ val userId:String
+)
diff --git a/app/src/main/java/com/example/eventtracker/model/UserSIgnUpRequest.kt b/app/src/main/java/com/example/eventtracker/model/UserSIgnUpRequest.kt
new file mode 100644
index 0000000..751b099
--- /dev/null
+++ b/app/src/main/java/com/example/eventtracker/model/UserSIgnUpRequest.kt
@@ -0,0 +1,8 @@
+package com.example.eventtracker.model
+
+data class UserSIgnUpRequest(
+ val username: String,
+ val password: String,
+ val email: String,
+ val collegeId: String
+)
diff --git a/app/src/main/java/com/example/eventtracker/ui/home/EventDetailScreen.kt b/app/src/main/java/com/example/eventtracker/ui/home/EventDetailScreen.kt
index 1795ce9..82d62ca 100644
--- a/app/src/main/java/com/example/eventtracker/ui/home/EventDetailScreen.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/home/EventDetailScreen.kt
@@ -1,5 +1,8 @@
package com.example.eventtracker.ui.home
+import android.content.Intent
+import android.net.Uri
+import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
@@ -18,6 +21,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.outlined.LocationOn
import androidx.compose.material3.Button
@@ -29,25 +33,44 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
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.max
+import androidx.core.content.ContextCompat.startActivity
+import androidx.hilt.navigation.compose.hiltViewModel
import coil.compose.AsyncImage
import com.example.eventtracker.R
import com.example.eventtracker.model.EventData
import com.example.eventtracker.ui.theme.EventTrackerTheme
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import androidx.core.net.toUri
@Composable
-fun EventDetailScreen(modifier: Modifier = Modifier, event: EventData) {
+fun EventDetailScreen(
+ modifier: Modifier = Modifier,
+ event: EventData,
+ viewModel: HomeScreenViewModel = hiltViewModel()
+) {
- EventDetailScreenContent(modifier = Modifier.padding(), event = event)
+ EventDetailScreenContent(modifier = Modifier.padding(), event = event, viewModel = viewModel)
}
@OptIn(ExperimentalMaterial3Api::class)
@@ -72,87 +95,139 @@ fun EventDetailScreenTopBar(modifier: Modifier = Modifier) {
}
@Composable
-fun EventDetailScreenContent(modifier: Modifier = Modifier, event: EventData) {
+fun EventDetailScreenContent(
+ modifier: Modifier = Modifier,
+ event: EventData,
+ viewModel: HomeScreenViewModel
+) {
+ val context = LocalContext.current
Column(
modifier = modifier
.verticalScroll(rememberScrollState())
) {
- AsyncImage(
- model = event.image,
- error = painterResource(id = R.drawable.default_image),
- contentDescription = null,
- modifier = Modifier.fillMaxWidth(),
+ AsyncImage(
+ model = event.eventImageUrl,
+ error = painterResource(id = R.drawable.default_image),
+ contentDescription = null,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
+ ) {
+ val bookmarked = viewModel.bookmarkedEvents.collectAsState()
+ val isBookmarked = bookmarked.value.any { it.eventId == event.eventId }
+ Text(
+ text = event.name,
+ fontWeight = FontWeight.Bold,
+ style = MaterialTheme.typography.titleLarge
)
- Column(
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(
+ text = "${event.date}, ${event.time}",
modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp)
- ) {
- Text(
- text = event.name,
- fontWeight = FontWeight.Bold,
- style = MaterialTheme.typography.titleLarge
- )
- Spacer(modifier = Modifier.height(8.dp))
- Text(
- text = "${event.date}, ${event.time}",
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ Row(modifier = Modifier.fillMaxWidth()) {
+ Icon(
+ imageVector = Icons.Outlined.LocationOn,
+ contentDescription = "Event Location",
+ tint = Color.Black,
modifier = Modifier
- )
- Spacer(modifier = Modifier.height(8.dp))
- Row(modifier = Modifier.fillMaxWidth()) {
- Icon(
- imageVector = Icons.Outlined.LocationOn,
- contentDescription = "Event Location",
- tint = Color.Black,
- modifier = Modifier
- .clip(RoundedCornerShape(8.dp))
- .background(Color(176, 183, 192, 70))
- .padding(4.dp)
+ .clip(RoundedCornerShape(8.dp))
+ .background(Color(176, 183, 192, 70))
+ .padding(4.dp)
- )
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(text = event.location, modifier = Modifier.weight(1f))
+ }
+ Spacer(modifier = Modifier.height(16.dp))
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ Button(
+ onClick = {
+ CoroutineScope(Dispatchers.IO).launch {
+ viewModel.onInterestedClicked(event, onSuccess = {
+ Toast.makeText(
+ context,
+ "Event Bookmarked",
+ Toast.LENGTH_SHORT
+ ).show()
+ }, onFailure = {
+ Toast.makeText(
+ context,
+ "Failed try again later",
+ Toast.LENGTH_SHORT
+ ).show()
+ })
+ }
+ },
+ modifier = Modifier.weight(1f),
+ colors = ButtonDefaults.buttonColors(Color(176, 183, 192, 70)),
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ text = if (!isBookmarked) "Save" else "Saved",
+ color = Color.Black,
+ fontWeight = FontWeight.SemiBold
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ if (isBookmarked)
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = "Event Detail",
+ tint = Color.Black
+ )
+ }
- Spacer(modifier = Modifier.width(8.dp))
- Text(text = event.location, modifier = Modifier.weight(1f))
}
- Spacer(modifier = Modifier.height(16.dp))
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(10.dp)
+ Button(
+ onClick = {
+ if (event.eventLink.isNotEmpty()) {
+ val link = if (event.eventLink.startsWith("http")) {
+ event.eventLink
+ } else {
+ "https://${event.eventlink}/"
+ }
+ val intent = Intent(Intent.ACTION_VIEW, link.toUri())
+ startActivity(
+ context,
+ intent,
+ null
+ )
+ } else {
+ Toast.makeText(context, "No link found", Toast.LENGTH_SHORT).show()
+ }
+ },
+ modifier = Modifier.weight(1f),
+ colors = ButtonDefaults.buttonColors(Color(13, 125, 242))
) {
- Button(
- onClick = { /*TODO*/ },
- modifier = Modifier.weight(1f),
- colors = ButtonDefaults.buttonColors(Color(176, 183, 192, 70)),
- ) {
- Text(text = "Save", color = Color.Black, fontWeight = FontWeight.SemiBold)
- }
- Button(
- onClick = { /*TODO*/ },
- modifier = Modifier.weight(1f),
- colors = ButtonDefaults.buttonColors(Color(13, 125, 242))
- ) {
- Text(text = "Attend", fontWeight = FontWeight.SemiBold)
- }
+ Text(text = "Attend", fontWeight = FontWeight.SemiBold)
}
- Spacer(modifier = Modifier.height(16.dp))
- Text(
- text = "About the Event",
- style = MaterialTheme.typography.titleLarge,
- fontWeight = FontWeight.Bold
- )
- Spacer(modifier = Modifier.height(8.dp))
- Text(text = event.description)
}
+ Spacer(modifier = Modifier.height(16.dp))
+ Text(
+ text = "About the Event",
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(text = event.description)
}
}
+}
- @Preview(showBackground = true, showSystemUi = true)
- @Composable
- fun EventDetailScreenPreview(modifier: Modifier = Modifier) {
- EventTrackerTheme {
- EventDetailScreen(event = EventData())
- }
+@Preview(showBackground = true, showSystemUi = true)
+@Composable
+fun EventDetailScreenPreview(modifier: Modifier = Modifier) {
+ EventTrackerTheme {
+ EventDetailScreen(event = EventData())
}
+}
diff --git a/app/src/main/java/com/example/eventtracker/ui/home/HomeScreen.kt b/app/src/main/java/com/example/eventtracker/ui/home/HomeScreen.kt
index 24b2741..88f1ddc 100644
--- a/app/src/main/java/com/example/eventtracker/ui/home/HomeScreen.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/home/HomeScreen.kt
@@ -1,6 +1,7 @@
package com.example.eventtracker.ui.home
import android.util.Log
+import android.widget.Toast
import androidx.compose.animation.VectorConverter
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
@@ -80,11 +81,12 @@ fun HomeScreenTopBar(
if (isSearchOn.value) {
CenterAlignedTopAppBar(
title = {
- TextField(value = searchQuery.value, onValueChange = {
- viewModel.updateSearchQuery(
- it
- )
- }, modifier = Modifier.fillMaxWidth(),
+ TextField(
+ value = searchQuery.value, onValueChange = {
+ viewModel.updateSearchQuery(
+ it
+ )
+ }, modifier = Modifier.fillMaxWidth(),
placeholder = { Text(text = "Search events") },
singleLine = true,
trailingIcon = {
@@ -127,6 +129,7 @@ fun HomeBody(
viewModel: HomeScreenViewModel,
onEventClick: (EventData) -> Unit
) {
+
Column(modifier = modifier.padding(horizontal = 16.dp)) {
if (viewModel.isSearchOn.collectAsState().value)
@@ -150,7 +153,8 @@ fun HomeBody(
eventList = viewModel.eventList.collectAsState().value,
onEventClick = onEventClick,
onInterestedAction = viewModel::onInterestedClicked,
- checkIfBookmarked = viewModel::checkIfBookmarked
+ checkIfBookmarked = viewModel::checkIfBookmarked,
+ viewModel = viewModel
)
}
@@ -162,8 +166,9 @@ fun EventList(
modifier: Modifier = Modifier,
eventList: List,
onEventClick: (EventData) -> Unit,
- onInterestedAction: suspend (EventData, Boolean) -> Boolean,
- checkIfBookmarked: (event: EventData) -> Boolean
+ onInterestedAction: suspend (EventData, onSuccess: () -> Unit, onFailure: () -> Unit) -> Unit,
+ checkIfBookmarked: (event: EventData) -> Boolean,
+ viewModel: HomeScreenViewModel
) {
LazyColumn(modifier = modifier) {
itemsIndexed(eventList) { index, item ->
@@ -180,7 +185,8 @@ fun EventList(
onClickAction = onEventClick,
event = item,
onInterestedAction = onInterestedAction,
- checkIfBookmarked = checkIfBookmarked
+ checkIfBookmarked = checkIfBookmarked,
+ viewModel = viewModel
)
}
}
@@ -196,15 +202,19 @@ fun EventListItem(
onClickAction: (EventData) -> Unit = {},
eventTime: String,
eventLocation: String,
- onInterestedAction: suspend (EventData, Boolean) -> Boolean,
+ onInterestedAction: suspend (EventData,() -> Unit, () -> Unit) -> Unit,
event: EventData,
+ viewModel: HomeScreenViewModel,
+
checkIfBookmarked: (event: EventData) -> Boolean = { false }
) {
- var isBookmarked by rememberSaveable { mutableStateOf(checkIfBookmarked(event)) }
+ val bookmarked = viewModel.bookmarkedEvents.collectAsState()
+ val isBookmarked = bookmarked.value.any { it.eventId == event.eventId }
+ val context = LocalContext.current
// isBookmarked = checkIfBookmarked(event)
Column(modifier = modifier.clickable { onClickAction(event) }) {
AsyncImage(
- model = eventImage,
+ model = event.eventImageUrl,
error = painterResource(id = R.drawable.default_image),
contentDescription = "Event Image",
contentScale = ContentScale.Crop,
@@ -224,17 +234,32 @@ fun EventListItem(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
- Text(text = eventLocation)
+ Text(text = eventLocation, modifier = Modifier.weight(0.7f))
Button(
onClick =
- {
- CoroutineScope(Dispatchers.IO).launch {
- isBookmarked = onInterestedAction(event, isBookmarked)
- }
+ {
+ CoroutineScope(Dispatchers.IO).launch {
+ viewModel.onInterestedClicked(
+ event,
+ onSuccess = {
+ Toast.makeText(
+ context,
+ "Event Bookmarked",
+ Toast.LENGTH_SHORT
+ ).show()
+ }, onFailure = {
+ Toast.makeText(
+ context,
+ "Event UnBookmarked",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ )
+ }
// Update state after Firebase task}
// isBookmarked = onInterestedAction(event, isBookmarked)
// Log.d("isBookmarked", isBookmarked.toString())
- },
+ },
colors = ButtonDefaults.buttonColors(
if (isBookmarked)
@@ -244,8 +269,9 @@ fun EventListItem(
192,
70
) else Color.Unspecified
- )
- ) {
+ ),
+
+ ) {
Text(
text = if (isBookmarked) "Unbookmark" else "Bookmark",
diff --git a/app/src/main/java/com/example/eventtracker/ui/home/HomeScreenViewModel.kt b/app/src/main/java/com/example/eventtracker/ui/home/HomeScreenViewModel.kt
index 68a6014..141f313 100644
--- a/app/src/main/java/com/example/eventtracker/ui/home/HomeScreenViewModel.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/home/HomeScreenViewModel.kt
@@ -1,18 +1,26 @@
package com.example.eventtracker.ui.home
+import android.content.Context
import android.util.Log
+import android.widget.Toast
import androidx.collection.emptyLongSet
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.example.eventtracker.data.event.EventApiService
+import com.example.eventtracker.data.event.NetworkEventRepository
+import com.example.eventtracker.dto.toEventData
import com.example.eventtracker.model.EventData
+import com.example.eventtracker.model.GetEventResponse
import com.google.apphosting.datastore.testing.DatastoreTestTrace.FirestoreV1Action.Listen
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.ListenerRegistration
import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
@@ -23,13 +31,17 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
+import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
class HomeScreenViewModel @Inject constructor(
private val db: FirebaseFirestore,
- private val auth: FirebaseAuth
+ private val auth: FirebaseAuth,
+ private val eventRepository: NetworkEventRepository,
+ @ApplicationContext private val context: Context
) : ViewModel() {
private val _isSearchOn = MutableStateFlow(false)
val isSearchOn = _isSearchOn.asStateFlow()
@@ -37,10 +49,12 @@ class HomeScreenViewModel @Inject constructor(
val searchQuery = _searchQuery.asStateFlow()
private val _isSearching = MutableStateFlow(false)
val isSearching = _isSearching.asStateFlow()
- var eventListener: ListenerRegistration? = null
- var bookmarkEventListener: ListenerRegistration? = null
- private val _bookmarkedEvents = MutableStateFlow(emptyList())
+ private val _bookmarkedEventsId = MutableStateFlow>(emptyList())
+ val bookmarkedEventsId = _bookmarkedEventsId.asStateFlow()
+ private val _bookmarkedEvents = MutableStateFlow>(emptyList())
val bookmarkedEvents = _bookmarkedEvents.asStateFlow()
+ private val _hostedEvents = MutableStateFlow>(emptyList())
+ val hostedEvents = _hostedEvents.asStateFlow()
fun updateSearchQuery(query: String) {
_searchQuery.value = query
@@ -72,147 +86,124 @@ class HomeScreenViewModel @Inject constructor(
)
fun getEvents() {
- eventListener = db.collection("events").addSnapshotListener { value, error ->
- if (error != null) {
- Log.d("TAG", "populateMessages: $error")
- return@addSnapshotListener
- }
- val events = value?.toObjects(EventData::class.java)
- Log.d("events fetched", events.toString())
- if (events != null) {
- _eventList.value = events
+ viewModelScope.launch {
+ try {
+ val events = eventRepository.getEvents()
+ _eventList.value = events.data?.map {
+ it.toEventData()
+ }?:emptyList()
+// _eventList.value = _allEvents.value
+ } catch (e: Exception) {
+ Toast.makeText(context, "Error getting events", Toast.LENGTH_SHORT).show()
+ Log.d("TAG", "getEvents: $e")
}
+
}
- bookmarkEventListener = auth.currentUser?.let {
- db.collection("users")
- .document(it.uid)
- .collection("Bookmarked Events")
- .addSnapshotListener { value, error ->
- if (error != null) {
- Log.d("TAG", "populateMessages: $error")
- return@addSnapshotListener
- }
- val events = value?.toObjects(EventData::class.java)
- if (events != null) {
- _bookmarkedEvents.value = events
- }
+ getBookmarkedEvents()
+ getUserEvents()
- }
+ }
+
+ fun getBookmarkedEvents() {
+ viewModelScope.launch(Dispatchers.IO) {
+ try {
+ val events = eventRepository.getBookmarkedEvents()
+ _bookmarkedEvents.value = events.data?.map {
+ it.toEventData()
+ }?:emptyList()
+
+ } catch (e: Exception) {
+// Toast.makeText(context, "Error getting bookmarked events", Toast.LENGTH_SHORT)
+// .show()
+ Log.e("getbookmarkedEventsId", "getbookmarkedEventsId: $e")
+ }
}
}
- override fun onCleared() {
- super.onCleared()
- eventListener?.remove()
- bookmarkEventListener?.remove()
+ fun getUserEvents() {
+ viewModelScope.launch(Dispatchers.IO) {
+ try {
+ val events = eventRepository.getUserEvents()
+ _hostedEvents.value = events.data?.map {
+ it.toEventData()
+ }?:emptyList()
+
+ } catch (e: Exception) {
+// Toast.makeText(context, "Error getting events", Toast.LENGTH_SHORT).show()
+ Log.e("getEvents", "getEvents: $e")
+ }
+ }
}
- init {
- getEvents()
+ fun onInterestedClicked(event: EventData, onSuccess: () -> Unit, onFailure: () -> Unit): Unit {
+ Log.d("event", event.eventId.toString())
+ val isBookmarked = bookmarkedEventsId.value.any { it == event.eventId }
+ Log.d("isBookmarked", isBookmarked.toString())
+ try {
+ if (isBookmarked)
+ unBookmarkEvent(event.eventId, {
+ Toast.makeText(context, "Event UnBookmarked", Toast.LENGTH_SHORT).show()
+ }, onFailure)
+ else
+ bookmarkEvent(event.eventId, onSuccess, onFailure)
+ } catch (
+ e: Exception
+ ) {
+ Log.d("onInterestedClicked", "onInterestedClicked: $e")
+ onFailure()
+ }
+ Log.d("Bookmark List","${bookmarkedEventsId.value}")
}
-// suspend fun onInterestedClicked(event: EventData, isBookmarked: Boolean):Boolean {
-// val currentUserId = auth.currentUser?.uid
-// var wasSuccess:Boolean = false
-// if (currentUserId != null) {
-// if (!isBookmarked) {
-// db.collection("users")
-// .document(currentUserId)
-// .collection("Bookmarked Events")
-// .add(event)
-// .addOnSuccessListener {
-// Log.d("TAG", "onInterestedClicked: Success")
-// wasSuccess = true
-//// Log.d("TAG", "onInterestedClicked: $wasSuccess")
-// }
-// .addOnFailureListener {
-// Log.d("TAG", "onInterestedClicked: Failure")
-// }
-// } else {
-// db.collection("users")
-// .document(currentUserId)
-// .collection("Bookmarked Events").whereEqualTo("eventId", event.eventId)
-// .get()
-// .addOnSuccessListener { querySnapshot ->
-// if (querySnapshot.documents.isNotEmpty()) {
-// val documentId = querySnapshot.documents[0].id
-// db.collection("users")
-// .document(currentUserId)
-// .collection("Bookmarked Events")
-// .document(documentId)
-// .delete()
-// .addOnSuccessListener {
-// Log.d("TAG", "Event Deleted Successful")
-// wasSuccess = true
-// }
-// .addOnFailureListener {
-// Log.d("TAG", "Event Deletion Failed")
-// }
-// }
-// else {
-// Log.d("TAG", "Could not find event")
-// }
-// }
-// .addOnFailureListener {
-// Log.d("TAG", "Could not find event")
-// }
-// }
-// }
-// Log.d("TAG", "wasSuccess: $wasSuccess")
-// return if(wasSuccess) !isBookmarked else isBookmarked
-// }
-
- suspend fun onInterestedClicked(event: EventData, isBookmarked: Boolean): Boolean {
- val currentUserId = auth.currentUser?.uid
- return if (currentUserId != null) {
- if (!isBookmarked) {
- try {
- db.collection("users")
- .document(currentUserId)
- .collection("Bookmarked Events")
- .add(event)
- .await() // Use await() to suspend until the operation completes
- Log.d("TAG", "onInterestedClicked: Success")
- true // Return true if successful
- } catch (e: Exception) {
- Log.d("TAG", "onInterestedClicked: Failure", e)
- false // Return false on failure
+ fun bookmarkEvent(eventId: String, onSuccess: () -> Unit, onFailure: () -> Unit) {
+ viewModelScope.launch(Dispatchers.IO) {
+ try {
+ val resp = eventRepository.bookmarkEvent(eventId)
+ withContext(Dispatchers.Main) {
+ if (resp.success) {
+ _bookmarkedEventsId.update {
+ it + eventId
+ }
+ onSuccess()
+ } else
+ onFailure()
}
- } else {
- try {
- val querySnapshot = db.collection("users")
- .document(currentUserId)
- .collection("Bookmarked Events")
- .whereEqualTo("eventId", event.eventId)
- .get()
- .await()
- if (querySnapshot.documents.isNotEmpty()) {
- val documentId = querySnapshot.documents[0].id
- db.collection("users")
- .document(currentUserId)
- .collection("Bookmarked Events")
- .document(documentId)
- .delete()
- .await()
- Log.d("TAG", "Event Deleted Successful")
- false
- } else {
- Log.d("TAG", "Could not find event")
- true}
- } catch (e: Exception) {
- Log.d("TAG", "Could not find event", e)
- true
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ Log.e("bookmarkEvent", "Error: ${e.message}", e)
+ onFailure()
}
}
- } else {
- false // Return false if no user is logged in
+
}
}
+ fun unBookmarkEvent(eventId: String, onSuccess: () -> Unit, onFailure: () -> Unit) {
+ viewModelScope.launch(Dispatchers.IO) {
+ try {
+ val resp = eventRepository.unBookmarkEvent(eventId)
+ withContext(Dispatchers.Main) {
+ if (resp.success) {
+ _bookmarkedEventsId.update {
+ it - eventId
+ }
+ onSuccess()
+ } else
+ onFailure()
+ }
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ Log.e("unBookmarkEvent", "Error: ${e.message}", e)
+ onFailure()
+ }
+ }
+
+ }
+ }
fun checkIfBookmarked(event: EventData): Boolean {
- if(bookmarkedEvents.value.isEmpty())
+ if (bookmarkedEventsId.value.isEmpty())
return false
- return bookmarkedEvents.value.contains(event)
+ return bookmarkedEventsId.value.any { it == event.eventId }
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/example/eventtracker/ui/navigation/EventTrackerApp.kt b/app/src/main/java/com/example/eventtracker/ui/navigation/EventTrackerApp.kt
index 7ddf365..7fdc4e0 100644
--- a/app/src/main/java/com/example/eventtracker/ui/navigation/EventTrackerApp.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/navigation/EventTrackerApp.kt
@@ -1,6 +1,10 @@
package com.example.eventtracker.ui.navigation
+import android.content.SharedPreferences
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
@@ -17,7 +21,10 @@ import com.example.eventtracker.ui.profile.ProfileScreen
import com.example.eventtracker.ui.signIn.SignInScreen
import com.example.eventtracker.ui.signIn.SignInViewModel
import com.example.eventtracker.ui.userEventsScreen.UserEventsScreen
+import com.example.eventtracker.utils
+import retrofit2.http.Field
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun EventTrackerApp(
modifier: Modifier = Modifier,
@@ -26,10 +33,16 @@ fun EventTrackerApp(
homeScreenViewModel: HomeScreenViewModel,
signInViewModel: SignInViewModel,
) {
+ utils.logOutUser.observeForever {
+ if(it) {
+ signInViewModel.signOut()
+ navController.navigate(LogInScreen)
+ }
+ }
val scope = rememberCoroutineScope()
NavHost(
navController = navController,
- startDestination = LogInScreen,
+ startDestination = startScreen(signInViewModel),
modifier = modifier
) {
composable {
@@ -46,9 +59,11 @@ fun EventTrackerApp(
}
composable {
+ homeScreenViewModel.getEvents()
+// homeScreenViewModel.getBookmarkedEvents()
+// homeScreenViewModel.getUserEvents()
onBottomBarVisibilityChanged(true)
com.example.eventtracker.ui.home.HomeScreen(
-
onEventClick = {
navController.navigate(
EventDetailsScreen(
@@ -58,7 +73,10 @@ fun EventTrackerApp(
location = it.location,
time = it.time,
image = it.image,
- category = it.category
+ category = it.category,
+ eventLink = it.eventLink,
+ eventId = it.eventId,
+ eventImageUrl = it.eventImageUrl
)
)
},
@@ -75,10 +93,14 @@ fun EventTrackerApp(
location = args.location,
time = args.time,
image = args.image,
- category = args.category
+ category = args.category,
+ eventLink = args.eventLink,
+ eventId = args.eventId,
+ eventImageUrl = args.eventImageUrl
)
EventDetailScreen(
event = event,
+ viewModel = homeScreenViewModel
)
}
composable {
@@ -105,12 +127,22 @@ fun EventTrackerApp(
location = it.location,
time = it.time,
image = it.image,
- category = it.category
+ category = it.category,
+ eventLink = it.eventLink,
+ eventId = it.eventId
)
)
},
+ bookmarkedEvents = homeScreenViewModel.bookmarkedEvents.collectAsState().value,
+ hostedEvents = homeScreenViewModel.hostedEvents.collectAsState().value
)
}
}
+}
+fun startScreen(signInViewModel: SignInViewModel): Any {
+ if(signInViewModel.isUserLoggedIn())
+ return HomeScreen
+ else
+ return LogInScreen
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/eventtracker/ui/navigation/Routes.kt b/app/src/main/java/com/example/eventtracker/ui/navigation/Routes.kt
index 9a973c0..45f4b78 100644
--- a/app/src/main/java/com/example/eventtracker/ui/navigation/Routes.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/navigation/Routes.kt
@@ -1,5 +1,6 @@
package com.example.eventtracker.ui.navigation
+import android.annotation.SuppressLint
import kotlinx.serialization.Serializable
import com.example.eventtracker.model.EventData
import kotlinx.serialization.Contextual
@@ -7,6 +8,7 @@ import kotlinx.serialization.Contextual
@Serializable
data object HomeScreen
+@SuppressLint("UnsafeOptInUsageError")
@Serializable
data class EventDetailsScreen(
val name:String="",
@@ -16,6 +18,9 @@ data class EventDetailsScreen(
val location:String = "",
val description:String = "",
val category:String = "",
+ val eventLink:String = "",
+ val eventId:String = "",
+ val eventImageUrl:String = ""
)
@Serializable
diff --git a/app/src/main/java/com/example/eventtracker/ui/postNewEvent/PostNewEventScreen.kt b/app/src/main/java/com/example/eventtracker/ui/postNewEvent/PostNewEventScreen.kt
index 5ea44c7..bd9c641 100644
--- a/app/src/main/java/com/example/eventtracker/ui/postNewEvent/PostNewEventScreen.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/postNewEvent/PostNewEventScreen.kt
@@ -81,7 +81,6 @@ fun PostNewEventScreen(modifier: Modifier = Modifier, getEvents: () -> Unit) {
modifier = Modifier.padding(),
uiState = uiState,
viewModel = viewModel,
- getEvents = getEvents
)
}
@@ -104,7 +103,6 @@ fun PostNewEventBody(
modifier: Modifier = Modifier,
viewModel: PostNewEventViewModel = viewModel(),
uiState: PostNewEventUiState,
- getEvents: () -> Unit = {}
) {
var selectedImageUri by remember { mutableStateOf(null) }
@@ -173,6 +171,24 @@ fun PostNewEventBody(
singleLine = true,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
)
+ OutlinedTextField(
+ value = uiState.eventLink,
+ onValueChange = { viewModel.updateEventLink(it) },
+ shape = RoundedCornerShape(8.dp),
+ label = { Text("Event Register Link", color = Color.Gray) },
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = 16.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedBorderColor = Color.Black,
+ unfocusedBorderColor = Color.Transparent,
+ unfocusedContainerColor = Color(176, 183, 192, 70),
+ focusedContainerColor = Color(176, 183, 192, 70),
+
+ ),
+ singleLine = true,
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
+ )
var date by rememberSaveable { mutableStateOf("") }
date = pickDate()
viewModel.updateEventDate(date)
@@ -233,7 +249,15 @@ fun PostNewEventBody(
"Event added successfully",
Toast.LENGTH_SHORT
).show()
- })
+ },
+ onFail = {
+ Toast.makeText(
+ context,
+ "Event not added Please try again",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ )
},
modifier = Modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.small,
diff --git a/app/src/main/java/com/example/eventtracker/ui/postNewEvent/PostNewEventViewModel.kt b/app/src/main/java/com/example/eventtracker/ui/postNewEvent/PostNewEventViewModel.kt
index 9e8aa9d..d19e5ff 100644
--- a/app/src/main/java/com/example/eventtracker/ui/postNewEvent/PostNewEventViewModel.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/postNewEvent/PostNewEventViewModel.kt
@@ -1,6 +1,7 @@
package com.example.eventtracker.ui.postNewEvent
import android.content.ContentValues.TAG
+import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.compose.runtime.getValue
@@ -8,6 +9,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.example.eventtracker.data.event.NetworkEventRepository
+import com.example.eventtracker.dto.CreateEventRequest
+import com.example.eventtracker.dto.EventDto
import com.example.eventtracker.model.EventData
import com.google.android.gms.tasks.Tasks
import com.google.firebase.Firebase
@@ -15,6 +20,7 @@ import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.storage.FirebaseStorage
import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@@ -22,18 +28,25 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
+import kotlinx.coroutines.withContext
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
+import okhttp3.RequestBody.Companion.toRequestBody
import javax.inject.Inject
@HiltViewModel
class PostNewEventViewModel @Inject constructor(
private val db: FirebaseFirestore,
private val storage: FirebaseStorage,
- private val auth: FirebaseAuth
+ private val auth: FirebaseAuth,
+ private val eventRepository: NetworkEventRepository,
+ @ApplicationContext private val context: Context
) : ViewModel() {
private val _uiState = MutableStateFlow(PostNewEventUiState())
val uiState: StateFlow = _uiState.asStateFlow()
var uri by mutableStateOf(null)
-
+ val initialUiState = PostNewEventUiState()
fun updateEventName(eventName: String) {
_uiState.value = _uiState.value.copy(eventName = eventName)
}
@@ -62,6 +75,10 @@ class PostNewEventViewModel @Inject constructor(
_uiState.value = _uiState.value.copy(id = id)
}
+ fun updateEventLink(eventLink: String) {
+ _uiState.value = _uiState.value.copy(eventLink = eventLink)
+ }
+
private suspend fun uploadEventImage(eventImageUri: Uri?): String {
val storageRef = storage.reference.child("images/${_uiState.value.id}")
if (eventImageUri != null) {
@@ -78,50 +95,66 @@ class PostNewEventViewModel @Inject constructor(
return ""
}
- fun addEventToDatabase(onSuccess: () -> Unit) {
- val id = db.collection("events").document().id
- updateId(id)
- CoroutineScope(Dispatchers.Main).launch {
- val url = uploadEventImage(uri)
- _uiState.value = _uiState.value.copy(eventImage = url)
- val event = EventData(
- name = _uiState.value.eventName,
- description = _uiState.value.eventDescription,
- category = _uiState.value.eventCategory,
- date = _uiState.value.eventDate,
- time = _uiState.value.eventTime,
- location = _uiState.value.location,
- image = _uiState.value.eventImage,
- userId = auth.currentUser?.uid.toString(),
- eventId = id
- )
- Log.d("url update", "addEventToDatabase: $url")
- db.collection("events").document(id).set(event).addOnSuccessListener {
- onSuccess()
- Log.d(TAG, "addEventToDatabase: Suceess")
- updateEventName("")
- updateEventDescription("")
- updateEventCategory("")
- updateEventDate("")
- updateEventTime("")
- updateLocation("")
- uri = null
- }
- .addOnFailureListener {
- Log.d(TAG, "addEventToDatabase: Failed To Add Event`")
+ fun addEventToDatabase(onSuccess: () -> Unit, onFail: () -> Unit = {}) {
+ val namePart = _uiState.value.eventName.toString().toPart()
+ val descriptionPart = _uiState.value.eventDescription.toString().toPart()
+ val categoryPart = _uiState.value.eventCategory.toString().toPart()
+ val datePart = _uiState.value.eventDate.toString().toPart()
+ val timePart = _uiState.value.eventTime.toString().toPart()
+ val locationPart = _uiState.value.location.toString().toPart()
+ val imageLinkPart = _uiState.value.eventLink.toString().toPart()
+ val image: MultipartBody.Part? = uri?.let {uriToMultiPartFile(context = context, uri = uri!!, partname = "image")}
+ viewModelScope.launch(Dispatchers.IO) {
+ try {
+ val resp = eventRepository.createEvent(
+ namePart = namePart,
+ descriptionPart = descriptionPart,
+ datePart = datePart,
+ timePart = timePart,
+ locationPart = locationPart,
+ imagePart = image,
+ categoryPart = categoryPart,
+ eventLinkPart = imageLinkPart
+ )
+ withContext(context = Dispatchers.Main) {
+ if (resp.success) {
+ onSuccess()
+ _uiState.value = initialUiState
+ uri = null
+ } else {
+ onFail()
+ }
}
- val userCollection = db.collection("users")
- val userId = auth.currentUser?.uid
- val userDocument = userId?.let { userCollection.document(it) }
- val nestedCollection = userDocument?.collection("Hosted Events")
- nestedCollection?.add(event)?.addOnSuccessListener { documentReference ->
- Log.d(TAG, "addEventToDatabase: Hosted Event Added ${documentReference}")
- }
- ?.addOnFailureListener {
- Log.d(TAG, "addEventToDatabase: Failed To Add Hosted Event")
+ } catch (exception: Exception) {
+ withContext(context = Dispatchers.Main) {
+ onFail()
}
+ Log.e("addEventToDatabase", "addEventToDatabase: $exception")
+ }
}
}
+
+ fun uriToMultiPartFile(
+ context: Context,
+ uri: Uri,
+ partname: String
+ ): MultipartBody.Part {
+ val inputStream = context.contentResolver.openInputStream(uri)
+ ?: throw IllegalArgumentException("Unable to open URI:$uri")
+ val bytes = inputStream.readBytes()
+ inputStream.close()
+ val mimeType = context.contentResolver.getType(uri)
+ ?: "application/octet-stream"
+ val requestFile = bytes.toRequestBody(mimeType.toMediaTypeOrNull())
+ val filename = "${System.currentTimeMillis()}.jpg"
+ return MultipartBody.Part.createFormData(
+ partname,
+ filename,
+ requestFile
+ )
+ }
+ fun String.toPart() =
+ toRequestBody("text/plain".toMediaType())
}
data class PostNewEventUiState(
@@ -133,4 +166,5 @@ data class PostNewEventUiState(
val eventTime: String = "",
val location: String = "",
val eventImage: String = "",
+ val eventLink: String = ""
)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/eventtracker/ui/profile/ProfileScreen.kt b/app/src/main/java/com/example/eventtracker/ui/profile/ProfileScreen.kt
index 6a943f0..76d5c17 100644
--- a/app/src/main/java/com/example/eventtracker/ui/profile/ProfileScreen.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/profile/ProfileScreen.kt
@@ -65,7 +65,6 @@ fun ProfileScreen(modifier: Modifier = Modifier,navigateToLogin: () -> Unit) {
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleLarge
)
-
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "USN - ${viewModel.userUsn.collectAsState().value}",
@@ -74,13 +73,7 @@ fun ProfileScreen(modifier: Modifier = Modifier,navigateToLogin: () -> Unit) {
)
Spacer(modifier = Modifier.height(4.dp))
Text(
- text = "Branch - ISE",
- style = MaterialTheme.typography.bodyLarge,
- color = Color.Gray
- )
- Spacer(modifier = Modifier.height(4.dp))
- Text(
- text = "Section - E",
+ text = "email - ${viewModel.userEmail.collectAsState().value}",
style = MaterialTheme.typography.bodyLarge,
color = Color.Gray
)
@@ -116,12 +109,6 @@ fun EventList(modifier: Modifier = Modifier, events: List) {
EventItem(event = event)
}
}
-// EventItem(eventName = "Introduction to Android Development")
-// EventItem(eventName = "Introduction to Android Development")
-// EventItem(eventName = "Introduction to Android Development")
-// EventItem(eventName = "Introduction to Android Development")
-// EventItem(eventName = "Introduction to Android Development")
-
}
@Composable
diff --git a/app/src/main/java/com/example/eventtracker/ui/profile/ProfileScreenViewModel.kt b/app/src/main/java/com/example/eventtracker/ui/profile/ProfileScreenViewModel.kt
index 9a64f71..75f0377 100644
--- a/app/src/main/java/com/example/eventtracker/ui/profile/ProfileScreenViewModel.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/profile/ProfileScreenViewModel.kt
@@ -1,22 +1,27 @@
package com.example.eventtracker.ui.profile
+import android.content.SharedPreferences
import android.util.Log
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.example.eventtracker.data.login.NetworkLogInRepository
import com.example.eventtracker.model.EventData
import com.example.eventtracker.model.UserData
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.ListenerRegistration
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
@HiltViewModel
class ProfileScreenViewModel @Inject constructor(
- private val auth: FirebaseAuth,
- private val db: FirebaseFirestore
+ private val loginRepository: NetworkLogInRepository,
+ private val sharedPreferences: SharedPreferences
) : ViewModel() {
val currentUser = UserData()
private val _userName = MutableStateFlow(currentUser.name)
@@ -27,36 +32,22 @@ class ProfileScreenViewModel @Inject constructor(
val userUsn = _userUsn.asStateFlow()
private val _userEvents = MutableStateFlow(listOf())
val userEvents = _userEvents.asStateFlow()
- var eventListener: ListenerRegistration? = null
init {
- db.collection("users")
- .document(auth.currentUser!!.uid)
- .get()
- .addOnSuccessListener {
- _userName.value = it.get("name") as String
- _userEmail.value = it.get("email") as String
- _userUsn.value = it.get("collegeId") as String
- }
- val user = auth.currentUser
- if (user != null) {
- eventListener = db.collection("users")
- .document(user.uid)
- .collection("Hosted Events")
- .addSnapshotListener { value, error ->
- if (error != null) {
- return@addSnapshotListener
- }
- if (value != null) {
- val events = value.toObjects(EventData::class.java)
- _userEvents.value = events
- }
+ viewModelScope.launch(Dispatchers.IO) {
+ try {
+ val userid = sharedPreferences.getString("userId", null)
+ ?: throw IllegalStateException("User ID not found in SharedPreferences")
+ Log.d("ProfileScreenViewModel", "User ID: $userid")
+ loginRepository.getUserById(userid).let {
+ _userName.value = it.username
+ _userEmail.value = it.email
+ _userUsn.value = it.collegeId
}
-
+ } catch (e: Exception) {
+ Log.e("ProfileScreenViewModel", "Error getting user data: ${e.message}", e)
+ }
}
+
}
- fun logOut() {
- auth.signOut()
- Log.d("TAG", auth.currentUser.toString())
- }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/example/eventtracker/ui/signIn/SignInScreen.kt b/app/src/main/java/com/example/eventtracker/ui/signIn/SignInScreen.kt
index 6727553..12481ae 100644
--- a/app/src/main/java/com/example/eventtracker/ui/signIn/SignInScreen.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/signIn/SignInScreen.kt
@@ -1,7 +1,9 @@
package com.example.eventtracker.ui.signIn
+import android.os.Build
import android.util.Log
import android.widget.Toast
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -55,6 +57,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.eventtracker.R
import com.example.eventtracker.ui.theme.EventTrackerTheme
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun SignInScreen(
modifier: Modifier = Modifier,
@@ -64,10 +67,10 @@ fun SignInScreen(
navigateToSignIn: () -> Unit = {},
) {
LaunchedEffect(Unit) {
- if(viewModel.checkIfLoggedIn()){
- navigateToHome()
- Log.d("TAG", "SignInScreen: ${viewModel.checkIfLoggedIn()}")
- }
+// if(viewModel.checkIfLoggedIn()){
+// navigateToHome()
+// Log.d("TAG", "SignInScreen: ${viewModel.checkIfLoggedIn()}")
+// }
}
val context = LocalContext.current
@@ -77,8 +80,8 @@ fun SignInScreen(
modifier = Modifier.padding(16.dp),
onSignIn =
{
- if (uiState.email.isEmpty() || uiState.password.isEmpty()) {
- Toast.makeText(context, "Please enter email and password", Toast.LENGTH_SHORT)
+ if (uiState.username.isEmpty() || uiState.password.isEmpty()) {
+ Toast.makeText(context, "Please enter username and password", Toast.LENGTH_SHORT)
.show()
} else {
viewModel.signIn(
@@ -210,13 +213,13 @@ fun SignInScreenContent(
Spacer(modifier = Modifier.height(10.dp))
}
OutlinedTextField(
- value = uiState.email,
- onValueChange = { viewModel.updateEmail(it) },
- label = { Text(text = "Email") },
+ value = uiState.username,
+ onValueChange = { viewModel.updateName(it) },
+ label = { Text(text = "Username") },
leadingIcon = {
Icon(
- imageVector = Icons.Outlined.Mail,
- contentDescription = "email"
+ imageVector = Icons.Outlined.Person,
+ contentDescription = "username"
)
},
modifier = Modifier.fillMaxWidth(),
diff --git a/app/src/main/java/com/example/eventtracker/ui/signIn/SignInViewModel.kt b/app/src/main/java/com/example/eventtracker/ui/signIn/SignInViewModel.kt
index ce8d6f6..57c0602 100644
--- a/app/src/main/java/com/example/eventtracker/ui/signIn/SignInViewModel.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/signIn/SignInViewModel.kt
@@ -1,11 +1,20 @@
package com.example.eventtracker.ui.signIn
+import android.content.SharedPreferences
+import android.os.Build
import android.util.Log
+import androidx.annotation.RequiresApi
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.security.crypto.EncryptedSharedPreferences
+import com.example.eventtracker.data.login.NetworkLogInRepository
+import com.example.eventtracker.model.SignUpResponse
import com.example.eventtracker.model.UserData
+import com.example.eventtracker.model.UserLogInResponse
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -13,94 +22,109 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
import javax.inject.Inject
+import androidx.core.content.edit
@HiltViewModel
class SignInViewModel @Inject constructor(
- private val auth: FirebaseAuth,
- private val db: FirebaseFirestore
-) :ViewModel() {
+ private val loginRepository: NetworkLogInRepository,
+ private val encryptedSharedPreferences: SharedPreferences
+) : ViewModel() {
private val _uiState = MutableStateFlow(SignInUiState())
- val uiState:StateFlow = _uiState.asStateFlow()
+ val uiState: StateFlow = _uiState.asStateFlow()
var inProcess by mutableStateOf(false)
-
- var currentUser = auth.currentUser
- fun updateEmail(email:String){
+ fun updateEmail(email: String) {
_uiState.value = _uiState.value.copy(email = email)
}
- fun updatePassword(password:String){
+
+ fun updatePassword(password: String) {
_uiState.value = _uiState.value.copy(password = password)
}
- fun updateConfirmPassword(confirmPassword:String){
+
+ fun updateConfirmPassword(confirmPassword: String) {
_uiState.value = _uiState.value.copy(confirmPassword = confirmPassword)
}
- fun updateName(name:String){
+
+ fun updateName(name: String) {
_uiState.value = _uiState.value.copy(username = name)
}
- fun updateIsSignIn(isSignIn:Boolean){
+
+ fun updateIsSignIn(isSignIn: Boolean) {
_uiState.value = _uiState.value.copy(isSignIn = isSignIn)
}
- fun updateCollegeId(collegeId:String){
+
+ fun updateCollegeId(collegeId: String) {
_uiState.value = _uiState.value.copy(collegeId = collegeId)
}
- fun signIn(
- onSuccess:()->Unit,
- onFailure:()->Unit
- ){
- inProcess = true
- auth.signInWithEmailAndPassword(uiState.value.email,uiState.value.password)
- .addOnSuccessListener {
- currentUser = auth.currentUser
- inProcess = false
- onSuccess()
- }
- .addOnFailureListener{
- inProcess = false
+ fun signIn(
+ onSuccess: () -> Unit,
+ onFailure: () -> Unit
+ ) {
+ viewModelScope.launch {
+ inProcess = true
+ try {
+ val response: UserLogInResponse = loginRepository.signInUser(
+ username = uiState.value.username,
+ password = uiState.value.password
+ )
+ if (response.success) {
+ encryptedSharedPreferences.edit() { putString("jwt", response.data.jwt) }
+ encryptedSharedPreferences.edit() { putString("refreshToken", response.data.refreshToken) }
+ encryptedSharedPreferences.edit() { putString("userId", response.data.userId) }
+ Log.d("jwt", "signIn: ${response.data.jwt}")
+ onSuccess()
+ } else {
+ onFailure()
+ }
+ } catch (
+ e: Exception
+ ) {
+ Log.e("SignIn", e.message.toString())
onFailure()
+ } finally {
+ inProcess = false
}
+ }
}
+
+ @RequiresApi(Build.VERSION_CODES.O)
fun signUp(
- onSuccess:()->Unit,
- onFailure:()->Unit
- ){
- if(uiState.value.password != uiState.value.confirmPassword)
+ onSuccess: () -> Unit,
+ onFailure: () -> Unit
+ ) {
+ if (uiState.value.password != uiState.value.confirmPassword)
return
- auth.createUserWithEmailAndPassword(uiState.value.email,uiState.value.password)
- .addOnSuccessListener {result ->
- val user: UserData? = result.user?.uid?.let {
- UserData(
- name = uiState.value.username,
- email = uiState.value.email,
- collegeId = uiState.value.collegeId,
- password = uiState.value.password,
- id = it,
- )
- }
- if (user != null) {
- db.collection("users").document(user.id).set(user)
- .addOnSuccessListener {
- onSuccess()
- Log.d("Sign", "onSignUp: Success")
- _uiState.update { it.copy(isSignIn = true) }
- }
- .addOnFailureListener {
- onFailure()
- Log.d("Sign", "onSignUp: ${it.message}")
- }
+ viewModelScope.launch {
+ inProcess = true
+ try {
+ val response: SignUpResponse = loginRepository.signUpUser(
+ username = uiState.value.username,
+ password = uiState.value.password,
+ collegeId = uiState.value.collegeId,
+ email = uiState.value.email
+ )
+ if (response.success) {
+ onSuccess()
+ } else {
+ onFailure()
}
- }
- .addOnFailureListener {
+ } catch (e: Exception) {
+ Log.e("SignUp", e.message.toString())
onFailure()
- Log.d("Sign", "onSignUp: ${it.message}")
+ } finally {
+ inProcess = false
}
+
+ }
}
- fun checkIfLoggedIn():Boolean {
- return currentUser != null
+ fun signOut() {
+ encryptedSharedPreferences.edit() { putString("jwt", null) }
+ encryptedSharedPreferences.edit() { putString("refreshToken", null) }
+ encryptedSharedPreferences.edit() { putString("userId", null) }
}
-
- fun signOut(){
- auth.signOut()
- currentUser = null
+ fun isUserLoggedIn(): Boolean {
+ return !encryptedSharedPreferences.getString("refreshToken",null).isNullOrEmpty()
}
}
diff --git a/app/src/main/java/com/example/eventtracker/ui/theme/Theme.kt b/app/src/main/java/com/example/eventtracker/ui/theme/Theme.kt
index 06ada97..1a1ff82 100644
--- a/app/src/main/java/com/example/eventtracker/ui/theme/Theme.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/theme/Theme.kt
@@ -35,7 +35,7 @@ private val LightColorScheme = lightColorScheme(
@Composable
fun EventTrackerTheme(
- darkTheme: Boolean = isSystemInDarkTheme(),
+ darkTheme: Boolean = false,
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
diff --git a/app/src/main/java/com/example/eventtracker/ui/userEventsScreen/UserEventsScreen.kt b/app/src/main/java/com/example/eventtracker/ui/userEventsScreen/UserEventsScreen.kt
index 6062786..caa6e5b 100644
--- a/app/src/main/java/com/example/eventtracker/ui/userEventsScreen/UserEventsScreen.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/userEventsScreen/UserEventsScreen.kt
@@ -43,30 +43,38 @@ import com.example.eventtracker.R
import com.example.eventtracker.model.EventData
@Composable
-fun UserEventsScreen(modifier: Modifier = Modifier,onEventClick: (EventData) -> Unit) {
+fun UserEventsScreen(
+ modifier: Modifier = Modifier,
+ onEventClick: (EventData) -> Unit,
+ bookmarkedEvents: List = emptyList(),
+ hostedEvents: List = emptyList()
+) {
val viewModel: UserEventsScreenViewModel = hiltViewModel()
val uiState by viewModel.uiState.collectAsState()
Column(
modifier = modifier,
- ) {
+ ) {
EventsToShow(
isBookmarksSelected = uiState.isBookmarksSelected,
onClick = { viewModel.updateIsBookmarkedSelected(it) })
Spacer(modifier = Modifier.height(16.dp))
EventList(
eventList = if (uiState.isBookmarksSelected)
- uiState.bookmarkedEvents else
- uiState.hostedEvents,
+ bookmarkedEvents else
+ hostedEvents,
onEventClick = onEventClick
)
}
}
@Composable
-fun EventList(modifier: Modifier = Modifier, eventList: List = emptyList(),onEventClick: (EventData) -> Unit) {
+fun EventList(
+ modifier: Modifier = Modifier,
+ eventList: List = emptyList(),
+ onEventClick: (EventData) -> Unit
+) {
LazyColumn {
- itemsIndexed(eventList) {
- _, item ->
+ itemsIndexed(eventList) { _, item ->
EventListItem(
eventTitle = item.name,
eventImage = item.image,
@@ -89,7 +97,12 @@ fun EventListItem(
onEventClick: (EventData) -> Unit = {},
event: EventData
) {
- Row(modifier = modifier.padding(12.dp).clickable { onEventClick(event) }, verticalAlignment = Alignment.CenterVertically) {
+ Row(
+ modifier = modifier
+ .padding(12.dp)
+ .clickable { onEventClick(event) },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
AsyncImage(
model = eventImage,
error = painterResource(id = R.drawable.default_image),
@@ -125,8 +138,20 @@ fun EventsToShow(
) {
val bookmarkWeight by animateFloatAsState(if (isBookmarksSelected) 1.4f else 1f)
val hostedWeight by animateFloatAsState(if (isBookmarksSelected) 1f else 1.4f)
- val bookmarkColor by animateColorAsState(if (isBookmarksSelected) Color.White else Color(240, 242, 245))
- val hostedColor by animateColorAsState(if (!isBookmarksSelected) Color.White else Color(240, 242, 245))
+ val bookmarkColor by animateColorAsState(
+ if (isBookmarksSelected) Color.White else Color(
+ 240,
+ 242,
+ 245
+ )
+ )
+ val hostedColor by animateColorAsState(
+ if (!isBookmarksSelected) Color.White else Color(
+ 240,
+ 242,
+ 245
+ )
+ )
val bookmarkElevation by animateDpAsState(if (isBookmarksSelected) 4.dp else 0.dp)
val hostedElevation by animateDpAsState(if (!isBookmarksSelected) 4.dp else 0.dp)
val bookmarkTextColor by animateColorAsState(if (isBookmarksSelected) Color.Black else Color.Gray)
diff --git a/app/src/main/java/com/example/eventtracker/ui/userEventsScreen/UserEventsScreenViewModel.kt b/app/src/main/java/com/example/eventtracker/ui/userEventsScreen/UserEventsScreenViewModel.kt
index 8e0b446..006e3c4 100644
--- a/app/src/main/java/com/example/eventtracker/ui/userEventsScreen/UserEventsScreenViewModel.kt
+++ b/app/src/main/java/com/example/eventtracker/ui/userEventsScreen/UserEventsScreenViewModel.kt
@@ -20,58 +20,7 @@ class UserEventsScreenViewModel @Inject constructor(
) : ViewModel() {
private val _uiState = MutableStateFlow(UserEventsScreenUiState())
val uiState: StateFlow = _uiState.asStateFlow()
- private var hostedEventListener: ListenerRegistration?=null
- private var bookmarkedEventListener: ListenerRegistration?=null
fun updateIsBookmarkedSelected(isBookmarkedSelected:Boolean) {
_uiState.value = _uiState.value.copy(isBookmarksSelected = isBookmarkedSelected)
}
- private fun fetchEvents() {
- val user = auth.currentUser
- if (user != null) {
- val userId = user.uid
- hostedEventListener = db.collection("users")
- .document(userId)
- .collection("Hosted Events")
- .addSnapshotListener{
- snapshot, error ->
- if (error != null) {
- // Handle error
- Log.d("TAG", "populateMessages: $error")
- return@addSnapshotListener
- }
- if (snapshot != null && !snapshot.isEmpty) {
- val hostedEvents = snapshot.toObjects(EventData::class.java)
- _uiState.value = _uiState.value.copy(hostedEvents = hostedEvents)
- }
-
- }
- bookmarkedEventListener = db.collection("users")
- .document(userId)
- .collection("Bookmarked Events")
- .addSnapshotListener {
- snapshot, error ->
- if (error != null) {
- // Handle error
- Log.d("TAG", "populateMessages: $error")
- return@addSnapshotListener
- }
- if (snapshot != null && !snapshot.isEmpty) {
- val bookmarkedEvents = snapshot.toObjects(EventData::class.java)
- _uiState.value = _uiState.value.copy(bookmarkedEvents = bookmarkedEvents)
- }
- else {
- _uiState.value = _uiState.value.copy(bookmarkedEvents = emptyList())
- }
- }
- }
- }
- override fun onCleared() {
- super.onCleared()
- hostedEventListener?.remove()
- bookmarkedEventListener?.remove()
- }
- init {
- fetchEvents()
- }
-
}
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 6f3b755..4ae7d12 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,6 +1,5 @@
-
-
-
+
+
\ 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
index 6f3b755..4ae7d12 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,6 +1,5 @@
-
-
-
+
+
\ 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
index c209e78..84c01bc 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_background.webp
new file mode 100644
index 0000000..1ff6a35
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_background.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..9858321
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.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
index b2dfe3d..3796629 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp 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
index 4f0f1d6..253c471 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_background.webp
new file mode 100644
index 0000000..2c627e1
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_background.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..74222d1
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.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
index 62b611d..5308606 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp 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
index 948a307..e1612dd 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp
new file mode 100644
index 0000000..6634b11
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..a536412
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.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
index 1b9a695..c0691ca 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp 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
index 28d4b77..bec15cc 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp
new file mode 100644
index 0000000..b470432
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..875f565
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.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
index 9287f50..8fa657d 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp 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
index aa7d642..a33241f 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp
new file mode 100644
index 0000000..7c55a19
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..78f6c03
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.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
index 9126ae3..ca250ce 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a373675..fbfd9d7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,6 +1,7 @@
[versions]
agp = "8.5.0"
coilCompose = "2.5.0"
+firebaseBom = "33.1.1"
generativeai = "0.7.0"
kotlin = "1.9.0"
coreKtx = "1.13.1"
@@ -25,12 +26,18 @@ lifecycleViewmodelCompose = "2.8.1"
firebaseStorageKtx = "21.0.0"
secretsGradlePlugin = "2.0.1"
composePreviewRenderer = "0.0.1-alpha01"
+securityCrypto = "1.1.0-alpha07"
uiTextGoogleFonts = "1.6.7"
-
+retrofit="2.9.0"
+retrofit-gson="2.9.0"
+retrofit2KotlinxSerializationConverter = "1.0.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-material-icons-extended-android = { module = "androidx.compose.material:material-icons-extended-android", version.ref = "materialIconsExtendedAndroid" }
+androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCrypto" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
+firebase-appcheck-playintegrity = { module = "com.google.firebase:firebase-appcheck-playintegrity" }
+firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
generativeai = { module = "com.google.ai.client.generativeai:generativeai", version.ref = "generativeai" }
jetbrains-kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -60,6 +67,9 @@ firebase-storage-ktx = { group = "com.google.firebase", name = "firebase-storage
secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secretsGradlePlugin" }
compose-preview-renderer = { group = "com.android.tools.compose", name = "compose-preview-renderer", version.ref = "composePreviewRenderer" }
androidx-ui-text-google-fonts = { group = "androidx.compose.ui", name = "ui-text-google-fonts", version.ref = "uiTextGoogleFonts" }
+retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" }
+retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
+retrofit-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit-gson" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }