feat: Add support for inviting users to join events
This commit is contained in:
parent
f51a25d46f
commit
86f4e66215
|
@ -5,7 +5,9 @@ import android.os.Bundle
|
|||
import android.util.Log
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
|
@ -17,6 +19,8 @@ import com.p_vacho.neat_calendar.R
|
|||
import com.p_vacho.neat_calendar.adapters.EventCardAdapter
|
||||
import com.p_vacho.neat_calendar.api.RetrofitClient
|
||||
import com.p_vacho.neat_calendar.api.models.EventResponse
|
||||
import com.p_vacho.neat_calendar.api.models.InvitationRequest
|
||||
import com.p_vacho.neat_calendar.api.models.UserResponse
|
||||
import com.p_vacho.neat_calendar.models.CalendarDay
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -86,7 +90,10 @@ class DayViewActivity : AppCompatActivity() {
|
|||
return
|
||||
}
|
||||
rvEvents.layoutManager = LinearLayoutManager(this)
|
||||
rvEvents.adapter = EventCardAdapter(events, userId, this, ::onEventEdit, ::onEventDelete, ::onEventLeave)
|
||||
rvEvents.adapter = EventCardAdapter(
|
||||
events, userId, this,
|
||||
::onEventEdit, ::onEventDelete, ::onEventLeave, ::onEventInvite,
|
||||
)
|
||||
|
||||
btnBack.setOnClickListener { finish() }
|
||||
btnAddEvent.setOnClickListener { onEventCreate() }
|
||||
|
@ -144,6 +151,41 @@ class DayViewActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is triggered on the invite button click from the event card adapter.
|
||||
*/
|
||||
private fun onEventInvite(event: EventResponse, position: Int) {
|
||||
lifecycleScope.launch {
|
||||
val users = withContext(Dispatchers.IO) {
|
||||
RetrofitClient.usersService.getUsers()
|
||||
}
|
||||
|
||||
// Remove the event owner (current user) and the existing event attendees
|
||||
// from the selection.
|
||||
val filteredUsers = users.filter {
|
||||
it.user_id != event.owner_user_id && it.user_id !in event.attendee_ids
|
||||
}
|
||||
|
||||
showUserSelectionDialog(filteredUsers) { user ->
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val data = InvitationRequest(
|
||||
event_id=event.id,
|
||||
invitee_id = user.user_id
|
||||
)
|
||||
RetrofitClient.invitationService.createInvitation(data)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
this@DayViewActivity,
|
||||
"Invitation sent to ${user.username}",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered with the CreateEventActivity return value.
|
||||
*/
|
||||
|
@ -172,4 +214,20 @@ class DayViewActivity : AppCompatActivity() {
|
|||
@Suppress("NotifyDataSetChanged")
|
||||
rvEvents.adapter!!.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun showUserSelectionDialog(
|
||||
users: List<UserResponse>,
|
||||
onUserSelected: (UserResponse) -> Unit
|
||||
) {
|
||||
val userNames = users.map { it.username }.toTypedArray()
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Select a user to invite")
|
||||
.setItems(userNames) { dialog, which ->
|
||||
// Pass the selected user back
|
||||
onUserSelected(users[which])
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
|
@ -33,7 +33,8 @@ class EventCardAdapter(
|
|||
private val onEditEvent: (EventResponse, Int) -> Unit,
|
||||
private val onDeleteEvent: (EventResponse, Int) -> Unit,
|
||||
private val onLeaveEvent: (EventResponse, Int) -> Unit,
|
||||
) :
|
||||
private val onInviteEvent: (EventResponse, Int) -> Unit,
|
||||
) :
|
||||
RecyclerView.Adapter<EventCardAdapter.EventViewHolder>() {
|
||||
|
||||
inner class EventViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
@ -45,6 +46,7 @@ class EventCardAdapter(
|
|||
val btnEdit: ImageButton = view.findViewById(R.id.btnEditEvent)
|
||||
val btnDelete: ImageButton = view.findViewById(R.id.btnDeleteEvent)
|
||||
val btnLeave: ImageButton = view.findViewById(R.id.btnLeaveEvent)
|
||||
val btnInvite: ImageButton = view.findViewById(R.id.btnInviteEvent)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EventViewHolder {
|
||||
|
@ -72,6 +74,7 @@ class EventCardAdapter(
|
|||
holder.btnEdit.setOnClickListener { onEditEvent(event, position) }
|
||||
holder.btnDelete.setOnClickListener { onDeleteEvent(event, position) }
|
||||
holder.btnLeave.setOnClickListener { onLeaveEvent(event, position) }
|
||||
holder.btnInvite.setOnClickListener { onInviteEvent(event, position) }
|
||||
|
||||
// Initialize empty state for categories
|
||||
holder.rvCategories.layoutManager =
|
||||
|
@ -84,6 +87,7 @@ class EventCardAdapter(
|
|||
if (event.owner_user_id != userId) {
|
||||
holder.btnEdit.visibility = View.GONE
|
||||
holder.btnDelete.visibility = View.GONE
|
||||
holder.btnInvite.visibility = View.GONE
|
||||
holder.btnLeave.visibility = View.VISIBLE
|
||||
} else {
|
||||
// Fetch categories dynamically
|
||||
|
|
|
@ -15,6 +15,8 @@ import com.fatboyindustrial.gsonjavatime.OffsetDateTimeConverter
|
|||
import com.p_vacho.neat_calendar.api.converters.ColorConverter
|
||||
import com.p_vacho.neat_calendar.api.services.CategoryService
|
||||
import com.p_vacho.neat_calendar.api.services.EventsService
|
||||
import com.p_vacho.neat_calendar.api.services.InvitationsService
|
||||
import com.p_vacho.neat_calendar.api.services.UsersService
|
||||
|
||||
object RetrofitClient {
|
||||
private const val DEFAULT_BASE_URL = "http://10.0.2.2:8000"
|
||||
|
@ -37,7 +39,11 @@ object RetrofitClient {
|
|||
val categoryService: CategoryService
|
||||
get() = retrofitClient!!.create(CategoryService::class.java)
|
||||
|
||||
val invitationService: InvitationsService
|
||||
get() = retrofitClient!!.create(InvitationsService::class.java)
|
||||
|
||||
val usersService: UsersService
|
||||
get() = retrofitClient!!.create(UsersService::class.java)
|
||||
|
||||
fun initialize(context: Context) {
|
||||
appContext = context
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package com.p_vacho.neat_calendar.api.models
|
||||
|
||||
data class InvitationRequest(
|
||||
val event_id: String,
|
||||
val invitee_id: String,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package com.p_vacho.neat_calendar.api.models
|
||||
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
data class UserResponse(
|
||||
val user_id: String,
|
||||
val username: String,
|
||||
val email: String,
|
||||
val created_at: OffsetDateTime,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package com.p_vacho.neat_calendar.api.services
|
||||
|
||||
import com.p_vacho.neat_calendar.api.models.InvitationRequest
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface InvitationsService {
|
||||
@POST("invitations")
|
||||
suspend fun createInvitation(@Body invitationData: InvitationRequest)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.p_vacho.neat_calendar.api.services
|
||||
|
||||
import com.p_vacho.neat_calendar.api.models.UserResponse
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface UsersService {
|
||||
@GET("users")
|
||||
suspend fun getUsers(): List<UserResponse>
|
||||
|
||||
@GET("users/{user_id}")
|
||||
suspend fun getUser(@Path("user_id") userId: String): UserResponse
|
||||
}
|
9
app/src/main/res/drawable/ic_person_add.xml
Normal file
9
app/src/main/res/drawable/ic_person_add.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:pathData="M720,560v-120L600,440v-80h120v-120h80v120h120v80L800,440v120h-80ZM360,480q-66,0 -113,-47t-47,-113q0,-66 47,-113t113,-47q66,0 113,47t47,113q0,66 -47,113t-113,47ZM40,800v-112q0,-34 17.5,-62.5T104,582q62,-31 126,-46.5T360,520q66,0 130,15.5T616,582q29,15 46.5,43.5T680,688v112L40,800ZM120,720h480v-32q0,-11 -5.5,-20T580,654q-54,-27 -109,-40.5T360,600q-56,0 -111,13.5T140,654q-9,5 -14.5,14t-5.5,20v32ZM360,400q33,0 56.5,-23.5T440,320q0,-33 -23.5,-56.5T360,240q-33,0 -56.5,23.5T280,320q0,33 23.5,56.5T360,400ZM360,320ZM360,720Z"
|
||||
android:fillColor="#e8eaed"/>
|
||||
</vector>
|
|
@ -52,6 +52,17 @@
|
|||
android:maxLines="2"
|
||||
tools:text="Event Title" />
|
||||
|
||||
<!-- Invite button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnInviteEvent"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="4dp"
|
||||
android:src="@drawable/ic_person_add"
|
||||
android:contentDescription="@string/leave_invited_event"
|
||||
app:tint="?android:attr/textColorSecondary" />
|
||||
|
||||
<!-- Edit Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnEditEvent"
|
||||
|
|
Loading…
Reference in a new issue