Add push notification support for Android and iOS
- Integrated Firebase Cloud Messaging (FCM) for Android - Integrated Apple Push Notification Service (APNs) for iOS - Created shared notification models and API client - Added device registration and token management - Added notification permission handling for Android - Created PushNotificationManager for iOS with AppDelegate - Added placeholder google-services.json (needs to be replaced with actual config) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
package com.mycrib.shared.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DeviceRegistrationRequest(
|
||||
@SerialName("registration_id")
|
||||
val registrationId: String,
|
||||
val platform: String // "android" or "ios"
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DeviceRegistrationResponse(
|
||||
val id: Int,
|
||||
@SerialName("registration_id")
|
||||
val registrationId: String,
|
||||
val platform: String,
|
||||
val active: Boolean,
|
||||
@SerialName("date_created")
|
||||
val dateCreated: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class NotificationPreference(
|
||||
val id: Int,
|
||||
@SerialName("task_due_soon")
|
||||
val taskDueSoon: Boolean = true,
|
||||
@SerialName("task_overdue")
|
||||
val taskOverdue: Boolean = true,
|
||||
@SerialName("task_completed")
|
||||
val taskCompleted: Boolean = true,
|
||||
@SerialName("task_assigned")
|
||||
val taskAssigned: Boolean = true,
|
||||
@SerialName("residence_shared")
|
||||
val residenceShared: Boolean = true,
|
||||
@SerialName("warranty_expiring")
|
||||
val warrantyExpiring: Boolean = true,
|
||||
@SerialName("created_at")
|
||||
val createdAt: String,
|
||||
@SerialName("updated_at")
|
||||
val updatedAt: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateNotificationPreferencesRequest(
|
||||
@SerialName("task_due_soon")
|
||||
val taskDueSoon: Boolean? = null,
|
||||
@SerialName("task_overdue")
|
||||
val taskOverdue: Boolean? = null,
|
||||
@SerialName("task_completed")
|
||||
val taskCompleted: Boolean? = null,
|
||||
@SerialName("task_assigned")
|
||||
val taskAssigned: Boolean? = null,
|
||||
@SerialName("residence_shared")
|
||||
val residenceShared: Boolean? = null,
|
||||
@SerialName("warranty_expiring")
|
||||
val warrantyExpiring: Boolean? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Notification(
|
||||
val id: Int,
|
||||
@SerialName("notification_type")
|
||||
val notificationType: String,
|
||||
val title: String,
|
||||
val body: String,
|
||||
val data: Map<String, String> = emptyMap(),
|
||||
val sent: Boolean,
|
||||
@SerialName("sent_at")
|
||||
val sentAt: String? = null,
|
||||
val read: Boolean,
|
||||
@SerialName("read_at")
|
||||
val readAt: String? = null,
|
||||
@SerialName("created_at")
|
||||
val createdAt: String,
|
||||
@SerialName("task_id")
|
||||
val taskId: Int? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UnreadCountResponse(
|
||||
@SerialName("unread_count")
|
||||
val unreadCount: Int
|
||||
)
|
||||
@@ -0,0 +1,186 @@
|
||||
package com.mycrib.shared.network
|
||||
|
||||
import com.mycrib.shared.models.*
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.*
|
||||
|
||||
class NotificationApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
private val baseUrl = ApiClient.getBaseUrl()
|
||||
|
||||
/**
|
||||
* Register a device for push notifications
|
||||
*/
|
||||
suspend fun registerDevice(
|
||||
token: String,
|
||||
request: DeviceRegistrationRequest
|
||||
): ApiResult<DeviceRegistrationResponse> {
|
||||
return try {
|
||||
val response = client.post("$baseUrl/notifications/devices/register/") {
|
||||
header("Authorization", "Token $token")
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
val errorBody = try {
|
||||
response.body<Map<String, String>>()
|
||||
} catch (e: Exception) {
|
||||
mapOf("error" to "Device registration failed")
|
||||
}
|
||||
ApiResult.Error(errorBody["error"] ?: "Device registration failed", response.status.value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a device
|
||||
*/
|
||||
suspend fun unregisterDevice(
|
||||
token: String,
|
||||
registrationId: String
|
||||
): ApiResult<Unit> {
|
||||
return try {
|
||||
val response = client.post("$baseUrl/notifications/devices/unregister/") {
|
||||
header("Authorization", "Token $token")
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(mapOf("registration_id" to registrationId))
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(Unit)
|
||||
} else {
|
||||
ApiResult.Error("Device unregistration failed", response.status.value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's notification preferences
|
||||
*/
|
||||
suspend fun getNotificationPreferences(token: String): ApiResult<NotificationPreference> {
|
||||
return try {
|
||||
val response = client.get("$baseUrl/notifications/preferences/my_preferences/") {
|
||||
header("Authorization", "Token $token")
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to get preferences", response.status.value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update notification preferences
|
||||
*/
|
||||
suspend fun updateNotificationPreferences(
|
||||
token: String,
|
||||
request: UpdateNotificationPreferencesRequest
|
||||
): ApiResult<NotificationPreference> {
|
||||
return try {
|
||||
val response = client.put("$baseUrl/notifications/preferences/update_preferences/") {
|
||||
header("Authorization", "Token $token")
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to update preferences", response.status.value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification history
|
||||
*/
|
||||
suspend fun getNotificationHistory(token: String): ApiResult<List<Notification>> {
|
||||
return try {
|
||||
val response = client.get("$baseUrl/notifications/history/") {
|
||||
header("Authorization", "Token $token")
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to get notification history", response.status.value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a notification as read
|
||||
*/
|
||||
suspend fun markNotificationAsRead(
|
||||
token: String,
|
||||
notificationId: Int
|
||||
): ApiResult<Notification> {
|
||||
return try {
|
||||
val response = client.post("$baseUrl/notifications/history/$notificationId/mark_as_read/") {
|
||||
header("Authorization", "Token $token")
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to mark notification as read", response.status.value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all notifications as read
|
||||
*/
|
||||
suspend fun markAllNotificationsAsRead(token: String): ApiResult<Map<String, Int>> {
|
||||
return try {
|
||||
val response = client.post("$baseUrl/notifications/history/mark_all_as_read/") {
|
||||
header("Authorization", "Token $token")
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to mark all notifications as read", response.status.value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unread notification count
|
||||
*/
|
||||
suspend fun getUnreadCount(token: String): ApiResult<UnreadCountResponse> {
|
||||
return try {
|
||||
val response = client.get("$baseUrl/notifications/history/unread_count/") {
|
||||
header("Authorization", "Token $token")
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to get unread count", response.status.value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user