Skip to content

CalendarView con Google Calendar API #4

@GVQ-uwu

Description

@GVQ-uwu

🧭 OBJETIVO GENERAL

Agregar un módulo “Calendario sincronizado” que:

  1. Muestre un calendario (CalendarView)
  2. Liste los eventos del día (RecyclerView)
  3. Se autentique con Google (Sign-In)
  4. Permita leer o agregar eventos de Google Calendar

🧩 1️⃣ ESTRUCTURA DE CARPETAS Y ARCHIVOS

📁 com.example.api_calendar

├─ model/
│   ├─ AcademicEvent.kt           → Modelo base de eventos
│
├─ ui/
│   ├─ CalendarActivity.kt        → Control principal del calendario
│   ├─ GoogleSignInActivity.kt    → Maneja autenticación con Google
│   ├─ adapters/
│   │   └─ EventAdapter.kt        → Adaptador para mostrar eventos del día
│   └─ google/
│       ├─ GoogleCalendarService.kt → Clase helper para llamadas API REST
│       └─ GoogleAuthHelper.kt      → Helper para autenticación y tokens
│
└─ res/
    ├─ layout/
    │   ├─ activity_calendar_google.xml
    │   ├─ item_event.xml
    │   └─ activity_google_sign_in.xml
    ├─ values/
    │   ├─ strings.xml
    │   ├─ colors.xml
    │   └─ themes.xml
    └─ drawable/
        ├─ ic_calendar_month_24.xml
        ├─ ic_event_24.xml
        └─ ic_google_24.xml

⚙️ 2️⃣ CONFIGURACIÓN DE DEPENDENCIAS

Abre tu archivo build.gradle.kts (:app) y agrega las siguientes líneas:

dependencies {
    // Google Sign-In
    implementation("com.google.android.gms:play-services-auth:21.2.0")

    // HTTP Client (para llamadas a la API de Google Calendar)
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    implementation("com.squareup.moshi:moshi:1.15.1") // JSON parser opcional
}

🔁 Luego haz Sync Project with Gradle Files.


🔐 3️⃣ AUTENTICACIÓN CON GOOGLE

🧱 GoogleSignInActivity.kt

package com.example.api_calendar.ui

import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.api_calendar.databinding.ActivityGoogleSignInBinding
import com.google.android.gms.auth.api.signin.*
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.Task

class GoogleSignInActivity : AppCompatActivity() {

    private lateinit var binding: ActivityGoogleSignInBinding
    private lateinit var googleSignInClient: GoogleSignInClient
    private val RC_SIGN_IN = 1001

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityGoogleSignInBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestEmail()
            .requestScopes(Scope("https://www.googleapis.com/auth/calendar"))
            .requestServerAuthCode("YOUR_CLIENT_ID.apps.googleusercontent.com") // Obtén de Google Cloud Console
            .build()

        googleSignInClient = GoogleSignIn.getClient(this, gso)

        binding.btnGoogleSignIn.setOnClickListener {
            startActivityForResult(googleSignInClient.signInIntent, RC_SIGN_IN)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == RC_SIGN_IN) {
            val task: Task<GoogleSignInAccount> = GoogleSignIn.getSignedInAccountFromIntent(data)
            try {
                val account = task.getResult(ApiException::class.java)
                // 🔹 Redirigir a CalendarActivity una vez logueado
                val intent = Intent(this, CalendarActivity::class.java)
                intent.putExtra("ACCOUNT_EMAIL", account?.email)
                startActivity(intent)
                finish()
            } catch (e: ApiException) {
                e.printStackTrace()
            }
        }
    }
}

🗓️ 4️⃣ INTERFAZ PRINCIPAL DEL CALENDARIO

📄 activity_calendar_google.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="12dp"
    android:background="@color/md_black">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:title="@string/menu_calendar"
        android:background="@color/md_surface"
        android:titleTextColor="@color/md_on"/>

    <CalendarView
        android:id="@+id/calendarView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvEventsDay"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:paddingTop="8dp"/>
</LinearLayout>

🧱 CalendarActivity.kt

package com.example.api_calendar.ui

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.api_calendar.databinding.ActivityCalendarGoogleBinding
import com.example.api_calendar.model.AcademicEvent
import com.example.api_calendar.ui.adapters.EventAdapter
import com.example.api_calendar.ui.google.GoogleCalendarService
import kotlinx.coroutines.*

class CalendarActivity : AppCompatActivity() {

    private lateinit var binding: ActivityCalendarGoogleBinding
    private val googleService = GoogleCalendarService()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityCalendarGoogleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setSupportActionBar(binding.toolbar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        binding.toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }

        binding.rvEventsDay.layoutManager = LinearLayoutManager(this)

        binding.calendarView.setOnDateChangeListener { _, year, month, dayOfMonth ->
            val date = "$year-${month + 1}-$dayOfMonth"
            loadEventsFromGoogle(date)
        }
    }

    private fun loadEventsFromGoogle(date: String) {
        CoroutineScope(Dispatchers.IO).launch {
            val events = googleService.fetchEvents(date)
            withContext(Dispatchers.Main) {
                binding.rvEventsDay.adapter = EventAdapter(events)
            }
        }
    }
}

🌐 5️⃣ SERVICIO DE GOOGLE CALENDAR API

📄 GoogleCalendarService.kt

package com.example.api_calendar.ui.google

import com.example.api_calendar.model.AcademicEvent
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject

class GoogleCalendarService {

    private val http = OkHttpClient()

    suspend fun fetchEvents(date: String): List<AcademicEvent> {
        val events = mutableListOf<AcademicEvent>()
        // Simulación de token (deberás obtenerlo desde GoogleAuthHelper)
        val token = "ACCESS_TOKEN"

        val url = "https://www.googleapis.com/calendar/v3/calendars/primary/events?" +
                "timeMin=${date}T00:00:00Z&timeMax=${date}T23:59:59Z&singleEvents=true&orderBy=startTime"

        val request = Request.Builder()
            .url(url)
            .addHeader("Authorization", "Bearer $token")
            .build()

        val response = http.newCall(request).execute()
        if (response.isSuccessful) {
            val json = JSONObject(response.body?.string() ?: "")
            val items = json.getJSONArray("items")
            for (i in 0 until items.length()) {
                val event = items.getJSONObject(i)
                val title = event.getString("summary")
                val start = event.getJSONObject("start").optString("dateTime", "")
                events.add(AcademicEvent(title, start, ""))
            }
        }
        return events
    }
}

🧠 6️⃣ MODELO DE EVENTO

📄 model/AcademicEvent.kt

package com.example.api_calendar.model
data class AcademicEvent(
    val title: String,
    val time: String,
    val detail: String
)

🧩 7️⃣ ADAPTADOR PARA EVENTOS

📄 ui/adapters/EventAdapter.kt

package com.example.api_calendar.ui.adapters

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.api_calendar.databinding.ItemEventBinding
import com.example.api_calendar.model.AcademicEvent

class EventAdapter(private val items: List<AcademicEvent>) :
    RecyclerView.Adapter<EventAdapter.VH>() {

    inner class VH(val vb: ItemEventBinding) : RecyclerView.ViewHolder(vb.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        val vb = ItemEventBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return VH(vb)
    }

    override fun onBindViewHolder(holder: VH, position: Int) {
        val e = items[position]
        holder.vb.tvTitle.text = e.title
        holder.vb.tvWhen.text = e.time
        holder.vb.tvDetail.text = e.detail
    }

    override fun getItemCount() = items.size
}

🗂️ 8️⃣ LAYOUT DE ITEM

📄 item_event.xml

<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="8dp"
    app:cardBackgroundColor="@color/md_card"
    app:cardCornerRadius="16dp">

    <LinearLayout
        android:orientation="vertical"
        android:padding="12dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/tvTitle"
            android:textColor="@color/md_on"
            android:textStyle="bold"
            android:textSize="16sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/tvWhen"
            android:textColor="@color/md_on"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/tvDetail"
            android:textColor="@color/md_on"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</com.google.android.material.card.MaterialCardView>

✅ RESULTADO FINAL

Con esta estructura tendrás:

✅ Autenticación de usuario con Google
✅ CalendarView para seleccionar fechas
✅ Llamadas a Google Calendar API
✅ Listado de eventos del día con diseño Material
✅ Base lista para sincronización bidireccional


Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions