diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b165bd5..3c11880 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,6 +16,9 @@
android:supportsRtl="true"
android:theme="@style/Theme.NeatCalendar"
tools:targetApi="31">
+
diff --git a/app/src/main/java/com/p_vacho/neat_calendar/activities/CreateEventActivity.kt b/app/src/main/java/com/p_vacho/neat_calendar/activities/CreateEventActivity.kt
new file mode 100644
index 0000000..b78b839
--- /dev/null
+++ b/app/src/main/java/com/p_vacho/neat_calendar/activities/CreateEventActivity.kt
@@ -0,0 +1,195 @@
+package com.p_vacho.neat_calendar.activities
+
+import android.app.DatePickerDialog
+import android.app.TimePickerDialog
+import android.content.Intent
+import android.graphics.Color
+import android.os.Bundle
+import android.view.View
+import android.widget.EditText
+import android.widget.Toast
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.button.MaterialButtonToggleGroup
+import com.google.android.material.textfield.TextInputEditText
+import com.p_vacho.neat_calendar.R
+import com.p_vacho.neat_calendar.api.RetrofitClient
+import com.p_vacho.neat_calendar.api.models.EventRequest
+import com.p_vacho.neat_calendar.api.models.EventResponse
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import retrofit2.HttpException
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.ZoneId
+
+class CreateEventActivity : AppCompatActivity() {
+ private lateinit var etEventTitle: EditText
+ private lateinit var etEventDescription: EditText
+ private lateinit var eventTypeToggleGroup: MaterialButtonToggleGroup
+ private lateinit var btnInstantEvent: MaterialButton
+ private lateinit var btnDurationEvent: MaterialButton
+ private lateinit var txtStartTime: TextInputEditText
+ private lateinit var txtEndTime: TextInputEditText
+ private lateinit var btnCreateEvent: MaterialButton
+
+ private lateinit var defaultDate: LocalDate
+
+ private var instantEvent: Boolean = true
+ private var startDateTime: LocalDateTime? = null
+ private var endDateTime: LocalDateTime? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContentView(R.layout.activity_create_event)
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+
+ // The getParcelableExtra wants the class as a second argument to
+ // be more type-safe, but this is only supported since api 33, which is over
+ // our min api version, so we can ignore this deprecation for now.
+ @Suppress("DEPRECATION")
+ val dateString = intent.getStringExtra("date")!!
+ defaultDate = LocalDate.parse(dateString)
+
+ initializeViews()
+ setupListeners()
+ }
+
+ private fun initializeViews() {
+ etEventTitle = findViewById(R.id.etEventTitle)
+ etEventDescription = findViewById(R.id.etEventDescription)
+ eventTypeToggleGroup = findViewById(R.id.eventTypeToggleGroup)
+ btnInstantEvent = findViewById(R.id.btnInstantEvent)
+ btnDurationEvent = findViewById(R.id.btnDurationEvent)
+ txtStartTime = findViewById(R.id.txtStartTime)
+ txtEndTime = findViewById(R.id.txtEndTime)
+ btnCreateEvent = findViewById(R.id.btnCreateEvent)
+ }
+
+ private fun setupListeners() {
+ eventTypeToggleGroup.addOnButtonCheckedListener { group, checkedId, isChecked ->
+ if (isChecked) { // Triggered only for the newly selected button
+ when (checkedId) {
+ R.id.btnInstantEvent -> {
+ txtEndTime.visibility = View.GONE
+ txtEndTime.setText("")
+ endDateTime = null
+ instantEvent = true
+ }
+ R.id.btnDurationEvent -> {
+ txtEndTime.visibility = View.VISIBLE
+ instantEvent = false
+ }
+ }
+ }
+ }
+
+ txtStartTime.setOnClickListener {
+ val defaultDateTime = startDateTime ?: defaultDate.atTime(12, 0)
+ showDateTimePicker(defaultDateTime) { selectedDateTime ->
+ startDateTime = selectedDateTime
+ txtStartTime.setText(formatDateTime(selectedDateTime))
+ }
+ }
+
+ txtEndTime.setOnClickListener {
+ val defaultDateTime = endDateTime ?: startDateTime ?: defaultDate.atTime(12, 0)
+ showDateTimePicker(defaultDateTime) { selectedDateTime ->
+ if (!selectedDateTime.isAfter(startDateTime)) {
+ Toast.makeText(this, "End time must be after start time", Toast.LENGTH_SHORT).show()
+ } else {
+ endDateTime = selectedDateTime
+ txtEndTime.setText(formatDateTime(selectedDateTime))
+ }
+ }
+ }
+
+ btnCreateEvent.setOnClickListener { createEvent() }
+ }
+
+ private fun createEvent() {
+ val title = etEventTitle.text.toString()
+ val description = etEventDescription.text.toString()
+
+ if (title.isEmpty() || startDateTime == null) {
+ Toast.makeText(this, "Please provide a title and start time", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ if (!instantEvent && endDateTime == null) {
+ Toast.makeText(this, "Please provide an end time or use an instant event", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ val eventRequest = EventRequest(
+ title = title,
+ description = description,
+ category_ids = emptyList(), // Categories will be added later
+ start_time = startDateTime!!.atZone(ZoneId.systemDefault()).toOffsetDateTime(),
+ end_time = endDateTime?.atZone(ZoneId.systemDefault())?.toOffsetDateTime()
+ ?: startDateTime!!.atZone(ZoneId.systemDefault()).toOffsetDateTime(),
+ color = Color.valueOf(0xFF33AABB.toInt()) // Placeholder color
+ )
+
+ lifecycleScope.launch(Dispatchers.IO) {
+ try {
+ val createdEvent = RetrofitClient.eventsService.createEvent(eventRequest)
+ withContext(Dispatchers.Main) { handleEventCreated(createdEvent) }
+ } catch (e: HttpException) {
+ if (e.code() != 400) {
+ throw e
+ }
+
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ this@CreateEventActivity,
+ "Failed to create event",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+ }
+
+ private fun handleEventCreated(createdEvent: EventResponse) {
+ Toast.makeText(this, "Event Created: ${createdEvent.title}", Toast.LENGTH_SHORT).show()
+
+ val intent = Intent().apply {
+ putExtra("newEvent", createdEvent)
+ }
+ setResult(RESULT_OK, intent) // Pass the event back
+
+ finish() // Close the activity and return
+ }
+
+ private fun showDateTimePicker(
+ initialDateTime: LocalDateTime?,
+ onDateTimeSelected: (LocalDateTime) -> Unit
+ ) {
+ val now = initialDateTime ?: LocalDateTime.now()
+
+ val datePicker = DatePickerDialog(this, { _, year, month, dayOfMonth ->
+ val timePicker = TimePickerDialog(this, { _, hourOfDay, minute ->
+ val selectedDateTime = LocalDateTime.of(year, month + 1, dayOfMonth, hourOfDay, minute)
+ onDateTimeSelected(selectedDateTime)
+ }, now.hour, now.minute, true)
+ timePicker.show()
+ }, now.year, now.monthValue - 1, now.dayOfMonth)
+ datePicker.show()
+ }
+
+ private fun formatDateTime(dateTime: LocalDateTime?): String {
+ return dateTime?.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) ?: "Select Time"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/p_vacho/neat_calendar/activities/DayViewActivity.kt b/app/src/main/java/com/p_vacho/neat_calendar/activities/DayViewActivity.kt
index bfe03b0..1bd2a43 100644
--- a/app/src/main/java/com/p_vacho/neat_calendar/activities/DayViewActivity.kt
+++ b/app/src/main/java/com/p_vacho/neat_calendar/activities/DayViewActivity.kt
@@ -1,5 +1,7 @@
package com.p_vacho.neat_calendar.activities
+import android.annotation.SuppressLint
+import android.content.Intent
import android.os.Bundle
import android.widget.ImageButton
import android.widget.TextView
@@ -11,6 +13,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.p_vacho.neat_calendar.R
import com.p_vacho.neat_calendar.adapters.EventCardAdapter
+import com.p_vacho.neat_calendar.api.models.EventResponse
import com.p_vacho.neat_calendar.models.CalendarDay
class DayViewActivity : AppCompatActivity() {
@@ -19,6 +22,20 @@ class DayViewActivity : AppCompatActivity() {
private lateinit var tvDate: TextView
private lateinit var rvEvents: RecyclerView
+ private lateinit var calendarDay: CalendarDay
+ private lateinit var events: MutableList
+
+ private val createEventLauncher = registerForActivityResult(
+ androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == RESULT_OK) {
+ @Suppress("DEPRECATION")
+ val newEvent: EventResponse? = result.data?.getParcelableExtra("newEvent")
+
+ newEvent?.let { addNewEvent(it) }
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
@@ -33,32 +50,39 @@ class DayViewActivity : AppCompatActivity() {
// be more type-safe, but this is only supported since api 33, which is over
// our min api version, so we can ignore this deprecation for now.
@Suppress("DEPRECATION")
- val calendarDay = intent.getParcelableExtra("calendarDay")!!
+ calendarDay = intent.getParcelableExtra("calendarDay")!!
- // It's possible that events will be removed by the event card adapter, use a
- // mutable list to keep track of them from here. We don't currently propagate
- // these changes anywhere, going back to calendar activity will just trigger
- // an api refetch, which is sufficient for now, but in the future, we could.
- val events = calendarDay.events.toMutableList()
+ events = calendarDay.events.sortedBy { it.start_time }.toMutableList()
- // Setup the UI
+ // Initialize Views
tvDate = findViewById(R.id.tvDate)
rvEvents = findViewById(R.id.rvEvents)
btnBack = findViewById(R.id.btnBack)
btnAddEvent = findViewById(R.id.btnAddEvent)
- btnBack.setOnClickListener {
- // Handle back navigation
- finish()
- }
-
- btnAddEvent.setOnClickListener {
- TODO("Implement adding events")
- }
-
+ // Setup UI
tvDate.text = calendarDay.date.toString()
rvEvents.layoutManager = LinearLayoutManager(this)
rvEvents.adapter = EventCardAdapter(events, this)
+
+ btnBack.setOnClickListener { finish() }
+ btnAddEvent.setOnClickListener { navigateToCreateEventActivity() }
+ }
+
+ fun navigateToCreateEventActivity() {
+ val intent = Intent(this, CreateEventActivity::class.java).apply {
+ putExtra("date", calendarDay.date.toString())
+ }
+ createEventLauncher.launch(intent)
+ }
+
+ private fun addNewEvent(newEvent: EventResponse) {
+ events.add(newEvent)
+ events.sortBy { it.start_time }
+
+ // We can't be more specific, we don't know the id due to sorting
+ @Suppress("NotifyDataSetChanged")
+ rvEvents.adapter!!.notifyDataSetChanged()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/p_vacho/neat_calendar/api/models/EventModels.kt b/app/src/main/java/com/p_vacho/neat_calendar/api/models/EventModels.kt
index 3c6328d..8e71c67 100644
--- a/app/src/main/java/com/p_vacho/neat_calendar/api/models/EventModels.kt
+++ b/app/src/main/java/com/p_vacho/neat_calendar/api/models/EventModels.kt
@@ -20,4 +20,13 @@ data class EventResponse(
val owner_user_id: String,
val attendee_ids: List,
val created_at: OffsetDateTime
-): Parcelable
\ No newline at end of file
+): Parcelable
+
+data class EventRequest(
+ val title: String,
+ val description: String,
+ val category_ids: List,
+ val start_time: OffsetDateTime,
+ val end_time: OffsetDateTime,
+ val color: Color,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/p_vacho/neat_calendar/api/services/EventsService.kt b/app/src/main/java/com/p_vacho/neat_calendar/api/services/EventsService.kt
index 38531ee..7b8944d 100644
--- a/app/src/main/java/com/p_vacho/neat_calendar/api/services/EventsService.kt
+++ b/app/src/main/java/com/p_vacho/neat_calendar/api/services/EventsService.kt
@@ -1,8 +1,11 @@
package com.p_vacho.neat_calendar.api.services
+import com.p_vacho.neat_calendar.api.models.EventRequest
import com.p_vacho.neat_calendar.api.models.EventResponse
+import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
+import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
import java.time.OffsetDateTime
@@ -31,4 +34,7 @@ interface EventsService {
@DELETE("events/{event_id}")
suspend fun deleteEvent(@Path("event_id") eventId: String): Unit
+
+ @POST("events")
+ suspend fun createEvent(@Body eventData: EventRequest): EventResponse
}
\ No newline at end of file
diff --git a/app/src/main/res/color/toggle_button_background_selector.xml b/app/src/main/res/color/toggle_button_background_selector.xml
new file mode 100644
index 0000000..8ff8d43
--- /dev/null
+++ b/app/src/main/res/color/toggle_button_background_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/toggle_button_stroke_selector.xml b/app/src/main/res/color/toggle_button_stroke_selector.xml
new file mode 100644
index 0000000..7abc020
--- /dev/null
+++ b/app/src/main/res/color/toggle_button_stroke_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_create_event.xml b/app/src/main/res/layout/activity_create_event.xml
new file mode 100644
index 0000000..855de25
--- /dev/null
+++ b/app/src/main/res/layout/activity_create_event.xml
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9cf2f12..d8dae1b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -33,4 +33,11 @@
Edit this event
Add a new event
Go to the previous page
+ Event Title
+ Event Description
+ Instant Event
+ Duration Event
+ Start Time
+ End Time
+ Create Event
\ No newline at end of file