Skip to content

Commit

Permalink
Event reporting (#43)
Browse files Browse the repository at this point in the history
* create blog home screen UI

* feat create post and list post on the blog home Screen

* Feat multiple image selector ad event titles selector

* done post details screen and like feature

* modified listing post components on the log main screen

* removed unused dependencies

* Setting up test

* Set Up Test

* removed unused imports

* reorganized ui components
  • Loading branch information
geekPastor authored Dec 20, 2024
1 parent c8595cd commit 7095671
Show file tree
Hide file tree
Showing 52 changed files with 1,983 additions and 9 deletions.
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET"/>

<application
android:name=".EventCademyApplication"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import com.google.firebase.storage.FirebaseStorage
import com.yveskalume.eventcademy.core.data.firebase.repository.AdvertisementRepositoryImpl
import com.yveskalume.eventcademy.core.data.firebase.repository.EventBookingRepositoryImpl
import com.yveskalume.eventcademy.core.data.firebase.repository.EventRepositoryImpl
import com.yveskalume.eventcademy.core.data.firebase.repository.PostLikesRepositoryImpl
import com.yveskalume.eventcademy.core.data.firebase.repository.UserRepositoryImpl
import com.yveskalume.eventcademy.core.data.firebase.repository.PostRepositoryImpl
import com.yveskalume.eventcademy.core.domain.repository.AdvertisementRepository
import com.yveskalume.eventcademy.core.domain.repository.EventBookingRepository
import com.yveskalume.eventcademy.core.domain.repository.EventRepository
import com.yveskalume.eventcademy.core.domain.repository.PostLikesRepository
import com.yveskalume.eventcademy.core.domain.repository.PostRepository
import com.yveskalume.eventcademy.core.domain.repository.UserRepository
import dagger.Module
import dagger.Provides
Expand Down Expand Up @@ -52,4 +56,20 @@ object RepositoryModule {
return EventBookingRepositoryImpl(fireStore, firebaseAuth)
}

@Provides
fun providePostRepository(
fireStore: FirebaseFirestore,
firebaseAuth: FirebaseAuth,
firebaseStorage: FirebaseStorage
): PostRepository {
return PostRepositoryImpl(fireStore, firebaseAuth, firebaseStorage)
}

@Provides
fun providePostLikesRepository(
fireStore: FirebaseFirestore,
firebaseAuth: FirebaseAuth,
): PostLikesRepository{
return PostLikesRepositoryImpl(fireStore, firebaseAuth)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.yveskalume.eventcademy.core.data.firebase.repository

import android.util.Log
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import com.yveskalume.eventcademy.core.data.firebase.util.FirestoreCollections
import com.yveskalume.eventcademy.core.domain.model.Post
import com.yveskalume.eventcademy.core.domain.model.PostLike
import com.yveskalume.eventcademy.core.domain.repository.PostLikesRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext
import java.util.Date
import javax.inject.Inject

class PostLikesRepositoryImpl @Inject constructor(
private val firestore: FirebaseFirestore,
private val firebaseAuth: FirebaseAuth

):PostLikesRepository {

override fun getAllLikesByPostUid(postUid: String) = callbackFlow<List<PostLike>> {
val listener = firestore.collection(FirestoreCollections.LIKES)
.whereEqualTo(PostLike::postUid.name, postUid)
.addSnapshotListener{value, error->
if (error != null || value == null){
close(error)
return@addSnapshotListener
}
value.toObjects(PostLike::class.java).also {
trySend(it)
}
}
awaitClose{ listener.remove() }
}.flowOn(Dispatchers.IO)

override fun getAllUserPostLikes() = callbackFlow<List<PostLike>> {
val currentUser = firebaseAuth.currentUser
val listener = firestore.collection(FirestoreCollections.LIKES)
.whereEqualTo(PostLike::userUid.name, currentUser?.uid)
.addSnapshotListener{value, error->
if (error != null || value == null){
close(error)
return@addSnapshotListener
}
value.toObjects(PostLike::class.java).also {data->
trySend(data.sortedBy { it.createdAt })
}
}
awaitClose{listener.remove()}
}.flowOn(Dispatchers.IO)

override suspend fun checkIfUserHasLiked(postUid: String) = callbackFlow {
val currentUser = firebaseAuth.currentUser
val listener = firestore
.document("${FirestoreCollections.LIKES}/${currentUser?.uid}-$postUid")
.addSnapshotListener{value, error ->
if (error != null || value == null){
Log.e("checkIfUserHasLiked","error",error)
trySend(false)
return@addSnapshotListener
}
trySend(value.exists())
}
awaitClose{listener.remove()}
}.flowOn(Dispatchers.IO)

override suspend fun createLike(post: Post) {
withContext(Dispatchers.IO){
val currentUser = firebaseAuth.currentUser ?: return@withContext
val postLike = PostLike(
uid = "${currentUser.uid}-${post.uid}",
postUid = post.uid,
postImageUrl = post.imageUrls,
userUid = currentUser.uid,
userName = currentUser.displayName ?: "",
email = currentUser.email ?: "",
userPhotoUrl = currentUser.photoUrl.toString(),
createdAt = Date().toString()
)
firestore.document("${FirestoreCollections.LIKES}/${postLike.uid}")
.set(postLike)
.await()
}
}

override suspend fun deleteLike(postUid: String) {
withContext(Dispatchers.IO){
val current = firebaseAuth.currentUser ?: return@withContext
firestore.document("${FirestoreCollections.LIKES}/${current.uid}-$postUid")
.delete()
.await()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.yveskalume.eventcademy.core.data.firebase.repository

import android.net.Uri
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.storage.FirebaseStorage
import com.yveskalume.eventcademy.core.data.firebase.util.FirebaseStorageFolders
import com.yveskalume.eventcademy.core.data.firebase.util.FirestoreCollections
import com.yveskalume.eventcademy.core.domain.model.Event
import com.yveskalume.eventcademy.core.domain.model.Post
import com.yveskalume.eventcademy.core.domain.repository.PostRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext
import java.util.Date
import javax.inject.Inject

class PostRepositoryImpl @Inject constructor(
private val firestore: FirebaseFirestore,
private val firebaseAuth: FirebaseAuth,
private val firebaseStorage: FirebaseStorage
): PostRepository {

/**
* Create a post
* @param post the post to create
* @throws IllegalStateException if the user is not connected
*/
override suspend fun createPost(post: Post) {
withContext(Dispatchers.IO){
val user = firebaseAuth.currentUser
if (user == null){
throw IllegalStateException("Vous devez être connecté pour créer un Post")
} else {
val imageUrl = uploadPostImage(post)

val postToCreate = post.copy(
userUid = user.uid,
createdAt = Date(),
updatedAt = Date(),
imageUrls = imageUrl
)

val task = firestore.document("${FirestoreCollections.POSTS}/${postToCreate.uid}")
.set(postToCreate)
task.await()
}
}
}


/**
* Get all the post created by the current user
* @return a list of posts
*/

private suspend fun uploadPostImage(post: Post): List<String>{
return withContext(Dispatchers.IO){
val urls = mutableListOf<String>()
for ((index, imageUri) in post.imageUrls.withIndex()){
val imageUrl = Uri.parse(imageUri)
val imageRef = firebaseStorage
.reference.child("${FirebaseStorageFolders.posts}/${post.uid}/image_$index")

val uploadTask = imageRef.putFile(imageUrl)
uploadTask.await()
urls.add(imageRef.downloadUrl.await().toString())
}

return@withContext urls
}
}

override fun getAllPosts()= callbackFlow<List<Post>> {
val listener = firestore.collection(FirestoreCollections.POSTS)
.addSnapshotListener { value, error ->
if (error != null || value == null) {
close(error)
return@addSnapshotListener
}
value.toObjects(Post::class.java).also { data ->
trySend(data.sortedBy { it.createdAt })
}
}
awaitClose { listener.remove() }
}.flowOn(Dispatchers.IO)


/**
* Delete a post
* @param postUid the uid of the post to delete
*/
override suspend fun deletePost(postUid: String) {
withContext(Dispatchers.IO){
firestore.document("${FirestoreCollections.POSTS}/$postUid").delete().await()
firebaseStorage.reference.child("${FirebaseStorageFolders.posts}/$postUid")
.delete()
.await()
}
}

override suspend fun getPostByUid(postUid: String): Post? {
return withContext(Dispatchers.IO){
val task = firestore.document("${FirestoreCollections.POSTS}/$postUid").get()
val post = task.await().toObject(Post::class.java)
return@withContext post
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ package com.yveskalume.eventcademy.core.data.firebase.util

object FirebaseStorageFolders {
const val events: String = "events"
const val posts: String = "posts"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ object FirestoreCollections {
const val EVENTS = "events"
const val BOOKINGS = "bookings"
const val ADVERTISEMENT = "advertisement"
const val POSTS = "posts"
const val LIKES = "likes"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.yveskalume.eventcademy.core.designsystem.components

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.KeyboardArrowDown
import androidx.compose.material3.Divider
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import com.yveskalume.eventcademy.core.domain.model.Event

@Composable
fun EventSelector(
modifier: Modifier = Modifier,
value: String,
eventList: List<Event>,
showSelector: Boolean,
onValueChange: (String) -> Unit,
onIconClicked: () -> Unit,
onEventClicked: () -> Unit
){
Column(
modifier = modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {

OutlinedTextField(
modifier = modifier
.fillMaxWidth().clickable(
onClick = {
onIconClicked()
}
),
readOnly = true,
placeholder = {
Text(text = "Selectionnez un événement")
},
value = value,
onValueChange = { onValueChange },
trailingIcon = {
IconButton(onClick = onIconClicked) {
Icon(imageVector = Icons.Outlined.KeyboardArrowDown, contentDescription = null)
}
}
)

DropdownMenu(expanded = showSelector, onDismissRequest = onIconClicked) {
eventList.forEach {
DropdownMenuItem(
text = {
Text(text = it.name)
},
onClick = {
onValueChange(it.name)
onEventClicked()
}
)
Divider()
}
}
}
}
Loading

0 comments on commit 7095671

Please sign in to comment.