Skip to content

Latest commit

 

History

History
533 lines (412 loc) · 19.1 KB

File metadata and controls

533 lines (412 loc) · 19.1 KB

📐 Seguimiento Jetpack Compose — DailyMath (iOS / SwiftUI)

Nota: Este proyecto fue desarrollado en Swift + SwiftUI (iOS) como equivalente funcional a la guía de clase basada en Jetpack Compose (Android). A continuación se explica punto por punto cómo se implementó cada requerimiento, junto con los fragmentos de código relevantes y un espacio para capturas de pantalla.


📋 Tabla de Contenido

  1. Repositorio en GitHub
  2. Estructura de Carpetas del Proyecto
  3. Pantalla HomeScreen (Descripción + Logo)
  4. Ícono de la Aplicación (AppIcon)
  5. Pantalla de Login + ViewModel
  6. Pantalla de Registro + ViewModel
  7. Pantallas de Olvido y Recuperación de Contraseña
  8. Uso de Snackbar (Alertas y Retroalimentación)
  9. Funcionalidades Adicionales

1. Repositorio en GitHub

El proyecto se encuentra alojado en un repositorio público de GitHub:

🔗 Repositorio: github.com/anju2246/DailyMath

  • Se utiliza Git para control de versiones.
  • El archivo .gitignore está configurado para excluir archivos de Xcode innecesarios (build artifacts, DerivedData, etc.).
  • Se incluye un .gitattributes para gestión de archivos binarios.

📸 Captura de pantalla del repositorio en GitHub:

(Insertar captura aquí)


2. Estructura de Carpetas del Proyecto

La estructura del proyecto sigue el patrón MVVM (Model-View-ViewModel), equivalente a la arquitectura recomendada en Jetpack Compose:

DailyMath/
├── App/
│   └── AppState.swift              ← Estado global de la app (equivalente a Application class)
├── Assets.xcassets/
│   ├── AccentColor.colorset/       ← Color de acento (equivalente a themes.xml)
│   └── AppIcon.appiconset/         ← Ícono de la app (equivalente a mipmap)
├── Config/
│   └── Secrets.swift               ← Claves y configuración sensible
├── Models/
│   ├── UserProfile.swift           ← Modelo de usuario
│   ├── Exercise.swift              ← Modelo de ejercicio
│   ├── Flashcard.swift             ← Modelo de flashcard (SM-2)
│   ├── Community.swift             ← Modelos de comunidad (comentarios, votos, badges)
│   └── Duel.swift                  ← Modelos de duelos y torneos
├── Services/
│   ├── AuthService.swift           ← Servicio de autenticación (equivalente a ViewModel de Auth)
│   ├── FlashcardStore.swift        ← Store de flashcards con persistencia local
│   └── NotificationService.swift   ← Servicio de notificaciones push
├── Utilities/
│   ├── Constants.swift             ← Constantes de la app (categorías, niveles, badges)
│   ├── Extensions.swift            ← Extensiones de UI (botones, colores, validaciones)
│   └── SM2Algorithm.swift          ← Implementación del algoritmo SM-2 (repaso espaciado)
├── ViewModels/                     ← (Vacío: la lógica de ViewModel vive en Services/)
├── Views/
│   ├── Auth/
│   │   ├── LoginView.swift         ← Pantalla de Login
│   │   ├── RegisterView.swift      ← Pantalla de Registro
│   │   └── ForgotPasswordView.swift← Pantalla de recuperación de contraseña
│   ├── App/
│   │   └── MainTabView.swift       ← Navegación principal por tabs
│   ├── Today/
│   │   ├── TodayView.swift         ← Vista "Hoy" (flashcards pendientes)
│   │   ├── FlashcardQuizView.swift ← Quiz interactivo de flashcards
│   │   └── CreateFlashcardView.swift← Crear nueva flashcard
│   ├── Explore/
│   │   └── ExploreView.swift       ← Explorar ejercicios
│   ├── Create/
│   │   └── CreateExerciseView.swift← Crear ejercicios
│   ├── Agility/
│   │   ├── AgilityView.swift       ← Modo agilidad mental
│   │   └── NumericKeyboardView.swift← Teclado numérico custom
│   └── Profile/
│       └── ProfileView.swift       ← Perfil del usuario
└── DailyMathApp.swift              ← Punto de entrada de la app (@main)

Equivalencias Android ↔ iOS

Concepto Android (Jetpack Compose) Equivalente iOS (SwiftUI)
Activity / @Composable struct View: View
ViewModel (Jetpack) ObservableObject (AuthService, AppState)
LiveData / StateFlow @Published + @StateObject / @EnvironmentObject
Navigation Component NavigationStack + .navigationDestination / .sheet
res/mipmap/ Assets.xcassets/AppIcon.appiconset/
AndroidManifest.xml Info.plist + Xcode project settings
themes.xml / colors.xml AccentColor.colorset + Extensions.swift (colores custom)

📸 Captura de la estructura de carpetas en Xcode:

(Insertar captura aquí)


3. Pantalla HomeScreen (Descripción + Logo)

La pantalla de HomeScreen se presenta como la pantalla de Login cuando el usuario no está autenticado y como la vista TodayView (vista principal) una vez autenticado.

Splash Screen (Pantalla de carga)

La app muestra un splash screen con el logo y nombre de la aplicación mientras se verifica el estado de autenticación:

// DailyMathApp.swift
ZStack {
    Color(.systemBackground).ignoresSafeArea()
    VStack(spacing: 16) {
        Image(systemName: "function")            // Logo: ícono de función matemática
            .font(.system(size: 64))
            .foregroundStyle(.tint)
        Text("DailyMath")                        // Nombre de la app
            .font(.largeTitle.bold())
        ProgressView()                           // Indicador de carga
            .padding(.top, 8)
    }
}

Vista principal (TodayView) — HomeScreen post-login

Una vez autenticado, el usuario ve un saludo personalizado, estadísticas y sus flashcards:

// Views/Today/TodayView.swift
VStack(alignment: .leading, spacing: 8) {
    Text("¡Hola, \(appState.currentUser?.displayName ?? "")!")
        .font(.title.bold())
    Text("Tus tarjetas de repaso del día")
        .font(.subheadline)
        .foregroundStyle(.secondary)
}

Se incluyen estadísticas visuales con badges de color (pendientes, repasadas hoy, total) y un botón destacado para comenzar el quiz con gradiente verde.

Navegación principal (MainTabView)

La app usa un TabView con 5 secciones, equivalente a un BottomNavigationBar en Jetpack Compose:

Tab Vista Ícono
Hoy TodayView calendar.badge.clock
Explorar ExploreView magnifyingglass
Crear CreateExerciseView plus.circle.fill
Agilidad AgilityView brain
Perfil ProfileView person.circle

📸 Captura de la pantalla HomeScreen / pantalla principal:

(Insertar captura aquí)

📸 Captura del Splash Screen:

(Insertar captura aquí)


4. Ícono de la Aplicación (AppIcon)

En iOS, el ícono de la aplicación se configura a través de Assets.xcassets/AppIcon.appiconset/. Este directorio contiene un archivo Contents.json que define las dimensiones del ícono para cada tipo de dispositivo y contexto.

Equivalencia Android ↔ iOS

Android iOS
Ícono en res/mipmap-*/ Ícono en AppIcon.appiconset/
Configurar en AndroidManifest.xml (android:icon) Configurar en Xcode → Target → General → App Icons
Formatos: mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi Formatos: 1x, 2x, 3x (1024×1024 para App Store)

Cómo se configura

  1. Se genera un ícono representativo del proyecto (temática matemática).
  2. Se coloca el ícono dentro de Assets.xcassets/AppIcon.appiconset/.
  3. Se edita el archivo Contents.json para apuntar a la imagen del ícono con las resoluciones adecuadas.
  4. Xcode automáticamente usa este ícono como predeterminado — no se necesita editar un "manifiesto" como en Android.

📸 Captura del ícono de la app (AppIcon en Xcode):

(Insertar captura aquí)

📸 Captura del ícono visible en el simulador o dispositivo:

(Insertar captura aquí)


5. Pantalla de Login + ViewModel

Vista: LoginView.swift

La pantalla de Login incluye:

  • Logo con gradiente azul-púrpura (ícono function del sistema).
  • Título "DailyMath" y subtítulo "Tu plataforma de estudio matemático".
  • Campo de Email con validación de tipo de teclado (.emailAddress).
  • Campo de Contraseña (SecureField).
  • Botón "Iniciar Sesión" con estilo primario y estado de carga (ProgressView).
  • Enlace "¿Olvidaste tu contraseña?" que abre un modal (.sheet).
  • Botón "Crear cuenta" que navega a RegisterView.
  • Mensajes de error en rojo cuando la autenticación falla.
// Botón de Login con estado de carga
Button {
    Task {
        do {
            try await authService.signIn(email: email, password: password)
        } catch {
            await MainActor.run {
                authService.errorMessage = error.localizedDescription
            }
        }
    }
} label: {
    if authService.isLoading {
        ProgressView().tint(.white).primaryButton()
    } else {
        Text("Iniciar Sesión").primaryButton()
    }
}
.disabled(email.isEmpty || password.isEmpty || authService.isLoading)

ViewModel: AuthService.swift (equivalente a LoginViewModel)

En SwiftUI, el patrón ViewModel se implementa con clases que conforman ObservableObject. El AuthService actúa como ViewModel para Login, Registro y Recuperación de contraseña:

class AuthService: ObservableObject {
    @Published var currentUser: UserProfile?   // Usuario actual
    @Published var isAuthenticated = false      // Estado de autenticación
    @Published var isLoading = false            // Estado de carga
    @Published var errorMessage: String?        // Mensajes de error/feedback

    func signIn(email: String, password: String) async throws { ... }
    func signUp(email: String, password: String, ...) async throws { ... }
    func signOut() async throws { ... }
    func resetPassword(email: String) async throws { ... }
    func deleteAccount() async throws { ... }
}

Equivalencia ViewModel:

Jetpack Compose SwiftUI
class LoginViewModel : ViewModel() class AuthService: ObservableObject
val email = mutableStateOf("") @State private var email = "" (en View)
val isLoading: StateFlow<Boolean> @Published var isLoading = false
viewModelScope.launch { } Task { } con async/await
ViewModelProvider / Hilt injection @EnvironmentObject / @StateObject

📸 Captura de la pantalla de Login:

(Insertar captura aquí)

📸 Captura mostrando validación / error en Login:

(Insertar captura aquí)


6. Pantalla de Registro + ViewModel

Vista: RegisterView.swift

La pantalla de Registro incluye:

  • Ícono de person.badge.plus con el título "Crear Cuenta".
  • Subtítulo "Únete a la comunidad DailyMath".
  • Campos del formulario:
    • Nombre completo (displayName)
    • Email (con validación de formato)
    • Universidad
    • Contraseña (mínimo 6 caracteres)
    • Confirmar contraseña
  • Validación en tiempo real del formulario:
    private var isFormValid: Bool {
        !displayName.isEmpty &&
        !email.isEmpty &&
        email.isValidEmail &&        // Extensión con Regex
        password.count >= 6 &&
        password == confirmPassword
    }
  • Mensaje de error si las contraseñas no coinciden.
  • Botón deshabilitado hasta que el formulario sea válido.

ViewModel

Reutiliza el mismo AuthService con el método signUp():

func signUp(email: String, password: String, username: String, displayName: String) async throws {
    try await signIn(email: email, password: password) // Demo mode
}

Validación de email (Extensión):

// Utilities/Extensions.swift
extension String {
    var isValidEmail: Bool {
        let regex = /^[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/
        return self.wholeMatch(of: regex) != nil
    }
}

📸 Captura de la pantalla de Registro:

(Insertar captura aquí)

📸 Captura mostrando validación de campos en Registro:

(Insertar captura aquí)


7. Pantallas de Olvido y Recuperación de Contraseña

Vista: ForgotPasswordView.swift

Esta pantalla maneja dos estados en una sola vista (equivalente a tener pantalla de olvido + pantalla de recuperación):

Estado 1: Formulario de solicitud (Olvido de contraseña)

  • Ícono de lock.rotation.
  • Título "Recuperar Contraseña".
  • Instrucciones: "Ingresa tu email y te enviaremos un enlace para restablecer tu contraseña."
  • Campo de email con validación.
  • Botón "Enviar enlace" que llama a authService.resetPassword(email:).

Estado 2: Confirmación (Recuperación de contraseña)

  • Ícono de envelope.badge.shield.half.filled en verde.
  • Título "¡Correo enviado!".
  • Mensaje: "Revisa tu bandeja de entrada en {email} para restablecer tu contraseña."
  • Botón "Volver al login" para cerrar el modal.
// Transición entre estados
if emailSent {
    // ✅ Estado de éxito — "pantalla de recuperación"
    VStack(spacing: 16) {
        Image(systemName: "envelope.badge.shield.half.filled")
            .font(.system(size: 64))
            .foregroundStyle(.green)
        Text("¡Correo enviado!").font(.title2.bold())
        Text("Revisa tu bandeja de entrada en **\(email)**...")
        Button("Volver al login") { dismiss() }
    }
} else {
    // 📝 Formulario — "pantalla de olvido de contraseña"
    // ... campo de email + botón enviar
}

Equivalencia

Requerimiento de la guía Implementación en SwiftUI
Pantalla de olvido de contraseña ForgotPasswordView — Estado inicial (formulario)
Pantalla de recuperación de contraseña ForgotPasswordView — Estado emailSent = true (confirmación)
ViewModel correspondiente AuthService.resetPassword(email:) vía async/await

📸 Captura de la pantalla de Olvido de Contraseña (formulario):

(Insertar captura aquí)

📸 Captura de la pantalla de Recuperación (confirmación de envío):

(Insertar captura aquí)


8. Uso de Snackbar (Alertas y Retroalimentación)

En Android/Jetpack Compose, se usa Snackbar para dar retroalimentación al usuario. En SwiftUI/iOS, el equivalente se implementa mediante:

1. Mensajes de error inline (en los formularios)

Todos los formularios muestran mensajes de error en rojo directamente en la interfaz:

// LoginView.swift, RegisterView.swift, ForgotPasswordView.swift
if let error = authService.errorMessage {
    Text(error)
        .font(.caption)
        .foregroundStyle(.red)
        .frame(maxWidth: .infinity, alignment: .leading)
}

2. Validación de contraseñas (RegisterView)

if showPasswordMismatch {
    Text("Las contraseñas no coinciden")
        .font(.caption)
        .foregroundStyle(.red)
}

3. Alertas de confirmación (ProfileView)

Para acciones destructivas se usa un Alert nativo:

.alert("¿Eliminar cuenta?", isPresented: $showDeleteConfirmation) {
    Button("Cancelar", role: .cancel) { }
    Button("Eliminar", role: .destructive) {
        Task { try? await appState.authService.deleteAccount() }
    }
} message: {
    Text("Esta acción es permanente. Se eliminarán todos tus datos.")
}

4. Estados de carga (feedback visual)

Todos los botones de acción muestran un ProgressView animado mientras la operación está en curso:

if authService.isLoading {
    ProgressView().tint(.white).primaryButton()
} else {
    Text("Iniciar Sesión").primaryButton()
}

Equivalencia Snackbar Android ↔ iOS

Snackbar (Android) Equivalente iOS
Snackbar.make(view, "Mensaje", LENGTH_SHORT).show() Text(error).foregroundStyle(.red) (inline)
Snackbar con acción .alert() con botones de acción
Mensajes temporales (Toast) .overlay de confirmación / ProgressView animado
Validación de formularios @State variables + condicionales en la vista

📸 Captura mostrando un mensaje de error en un formulario:

(Insertar captura aquí)

📸 Captura mostrando el alert de confirmación:

(Insertar captura aquí)

📸 Captura mostrando el estado de carga (ProgressView):

(Insertar captura aquí)


9. Funcionalidades Adicionales

Además de los requerimientos del seguimiento, el proyecto DailyMath incluye funcionalidades extra que demuestran el uso avanzado de SwiftUI:

🧠 Sistema de Flashcards con Algoritmo SM-2

  • Repaso espaciado usando el algoritmo SuperMemo SM-2.
  • Las flashcards se almacenan localmente con UserDefaults.
  • Quiz interactivo con opciones múltiples.

📊 Sistema de Gamificación

  • Puntos y niveles de usuario (Novato → Estudiante → Tutor → Maestro).
  • Racha de estudio (streak).
  • Sistema de insignias (badges) por logros.
  • Sistema de reputación por contribuciones.

⚔️ Duelos Matemáticos

  • Modelo de duelos 1v1 con preguntas de agilidad.
  • Sistema de torneos con participantes y ranking.

👤 Perfil de Usuario Completo

  • Avatar generado dinámicamente con la inicial del nombre.
  • Grid de estadísticas (Puntos, Racha, Nivel).
  • Panel de moderador condicional.
  • Opciones de cerrar sesión y eliminar cuenta.

🔔 Notificaciones

  • Servicio de notificaciones push (NotificationService).
  • Solicitud de permisos al iniciar la app.

🎨 Design System Personalizado

  • Colores custom (dmPrimary, dmSecondary, dmAccent, etc.).
  • Modificadores reutilizables (.primaryButton(), .secondaryButton(), .cardStyle()).
  • Localización completa en español colombiano (es_CO).

📸 Capturas adicionales del proyecto (perfil, flashcards, quiz, etc.):

(Insertar capturas aquí)


✅ Resumen de Cumplimiento

# Requerimiento Estado Archivo(s) Clave
1 Repositorio en GitHub Repo anju2246/DailyMath
2 Estructura de carpetas Models/, Views/, Services/, Utilities/
3 HomeScreen (descripción + logo) DailyMathApp.swift, TodayView.swift
4 Ícono de la app en mipmap/AppIcon Assets.xcassets/AppIcon.appiconset/
5 Login + ViewModel LoginView.swift, AuthService.swift
6 Registro + ViewModel RegisterView.swift, AuthService.swift
7 Olvido + Recuperación de contraseña ForgotPasswordView.swift
8 Snackbar (retroalimentación) Mensajes inline + .alert() + ProgressView

📝 Elaborado por: Grupo de Trabajo — DailyMath
📅 Fecha: Marzo 2026
🍎 Tecnología: Swift 5.9 + SwiftUI (iOS 17+)
🤖 Equivalente Android: Jetpack Compose + Kotlin