FRE-4738: Implement mark-as-read and mark-all-read actions
- Extract NotificationItem/NotificationType to Models/Notification.swift - Create NotificationsServiceProtocol with testable service layer - Implement markAsRead(id:) and markAllAsRead() with HTTP calls - Add NotificationError enum with localized descriptions - Update NotificationsViewModel to use protocol-based service - Add 18 unit tests (12 ViewModel + 6 Model) with mock service - Update README with architecture documentation
This commit is contained in:
@@ -6,59 +6,90 @@ SwiftUI implementation of the notifications feature for the Lendair iOS app.
|
||||
## Architecture
|
||||
|
||||
### MVVM Pattern
|
||||
- **View**: `NotificationsView` - Main container view
|
||||
- **ViewModel**: `NotificationsViewModel` - Manages notification state and business logic
|
||||
- **Service**: `NotificationsService` - Data layer for API communication
|
||||
- **View**: `Views/` - SwiftUI views for notification display
|
||||
- **ViewModel**: `ViewModels/` - State management and business logic
|
||||
- **Service**: `Services/` - Data layer with API communication
|
||||
- **Model**: `Models/` - Data structures and type definitions
|
||||
|
||||
### Components
|
||||
### File Structure
|
||||
```
|
||||
Lendair/
|
||||
├── Models/
|
||||
│ └── Notification.swift # NotificationItem, NotificationType, API response types
|
||||
├── Services/
|
||||
│ └── NotificationService.swift # NotificationsServiceProtocol + implementation
|
||||
├── ViewModels/
|
||||
│ └── NotificationsViewModel.swift # State management, mark-as-read actions
|
||||
├── Views/
|
||||
│ ├── NotificationsView.swift # Main notifications list screen
|
||||
│ └── NotificationRowView.swift # Individual notification row
|
||||
└── README.md
|
||||
```
|
||||
|
||||
#### NotificationsView (`Views/NotificationsView.swift`)
|
||||
## Components
|
||||
|
||||
### NotificationsView (`Views/NotificationsView.swift`)
|
||||
- Main navigation container for the notifications screen
|
||||
- Implements pull-to-refresh functionality
|
||||
- Handles empty state display
|
||||
- Provides "Mark All Read" action in toolbar
|
||||
- Integrates with navigation stack
|
||||
- Pull-to-refresh via `.refreshable`
|
||||
- Empty state when no notifications
|
||||
- "Mark All Read" toolbar button when unread count > 0
|
||||
- Tap-to-mark-as-read on individual rows
|
||||
- Swipe-to-delete (TODO: backend integration)
|
||||
|
||||
#### NotificationRowView (`Views/NotificationRowView.swift`)
|
||||
### NotificationRowView (`Views/NotificationRowView.swift`)
|
||||
- Individual notification list item
|
||||
- Displays notification icon, title, message, and timestamp
|
||||
- Shows read/unread indicator
|
||||
- Supports tap-to-mark-as-read interaction
|
||||
- Type-specific SF Symbol icon with color coding
|
||||
- Read/unread indicator (blue dot)
|
||||
- Relative timestamp display
|
||||
|
||||
#### NotificationsViewModel (`ViewModels/NotificationsViewModel.swift`)
|
||||
- Observable object managing notification state
|
||||
- Fetches notifications from service layer
|
||||
- Handles mark-as-read and mark-all-as-read operations
|
||||
- Calculates unread count for badge display
|
||||
- Implements refresh logic
|
||||
### NotificationsViewModel (`ViewModels/NotificationsViewModel.swift`)
|
||||
- `@Published notifications` — sorted by createdAt descending
|
||||
- `@Published isLoading` — loading state for UI feedback
|
||||
- `@Published error` — typed error state (NotificationError)
|
||||
- `fetchNotifications()` — loads from service
|
||||
- `markAsRead(id:)` — marks single notification, updates local state
|
||||
- `markAllAsRead()` — marks all unread, updates local state
|
||||
- `unreadCount` — computed property for badge display
|
||||
|
||||
### NotificationsService (`Services/NotificationService.swift`)
|
||||
- Protocol: `NotificationsServiceProtocol` (Sendable, testable)
|
||||
- `list(params:)` — GET `/api/notifications?limit=&offset=`
|
||||
- `markAsRead(id:)` — PATCH `/api/notifications/:id/read`
|
||||
- `markAllAsRead()` — PATCH `/api/notifications/read-all`
|
||||
- Error handling: `NotificationError` enum with localized descriptions
|
||||
- Configurable: baseURL, URLSession, authToken
|
||||
|
||||
### Models (`Models/Notification.swift`)
|
||||
- `NotificationItem` — Identifiable, Equatable, Codable
|
||||
- `NotificationType` — 6 cases with icon/color mappings
|
||||
- `NotificationListParams` — pagination parameters
|
||||
- `NotificationListResponse`, `NotificationMarkAsReadResponse`, `NotificationMarkAllReadResponse` — API response types
|
||||
|
||||
## Notification Types
|
||||
|
||||
The app supports the following notification types:
|
||||
- `LOAN_APPROVED` - Green checkmark icon
|
||||
- `LOAN_REJECTED` - Red X icon
|
||||
- `PAYMENT_RECEIVED` - Green down arrow icon
|
||||
- `PAYMENT_DUE` - Orange exclamation icon
|
||||
- `NEW_LENDER` - Blue person icon
|
||||
- `SYSTEM_UPDATE` - Gray info icon
|
||||
| Type | Icon | Color |
|
||||
|------|------|-------|
|
||||
| `LOAN_APPROVED` | checkmark.circle.fill | Green |
|
||||
| `LOAN_REJECTED` | xmark.circle.fill | Red |
|
||||
| `PAYMENT_RECEIVED` | arrow.down.circle.fill | Green |
|
||||
| `PAYMENT_DUE` | exclamationmark.circle.fill | Orange |
|
||||
| `NEW_LENDER` | person.circle.fill | Blue |
|
||||
| `SYSTEM_UPDATE` | info.circle.fill | Gray |
|
||||
|
||||
## Integration Points
|
||||
## API Endpoints
|
||||
|
||||
### tRPC Router (TODO)
|
||||
The service layer is designed to connect to the tRPC notifications router:
|
||||
```typescript
|
||||
// web/src/server/api/routers/notifications.ts
|
||||
notifications: router({
|
||||
list: protectedQuery(...),
|
||||
markAsRead: protectedMutation(...),
|
||||
markAllAsRead: protectedMutation(...),
|
||||
})
|
||||
```
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/notifications?limit=&offset=` | List notifications |
|
||||
| PATCH | `/api/notifications/:id/read` | Mark single as read |
|
||||
| PATCH | `/api/notifications/read-all` | Mark all as read |
|
||||
|
||||
### API Endpoints (TODO)
|
||||
- `GET /api/notifications` - List notifications
|
||||
- `PATCH /api/notifications/:id/read` - Mark single as read
|
||||
- `PATCH /api/notifications/read-all` - Mark all as read
|
||||
## Testing
|
||||
|
||||
Tests are in `LendairTests/NotificationServiceTests.swift`:
|
||||
- 12 ViewModel tests (fetch, mark-as-read, mark-all-read, unread count, refresh, error handling)
|
||||
- 6 Model tests (icons, colors, equality, raw values, params)
|
||||
- Uses `MockNotificationsService` conforming to `NotificationsServiceProtocol`
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -69,15 +100,6 @@ NavigationStack {
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run the preview in Xcode to see the notification row designs:
|
||||
```swift
|
||||
#Preview {
|
||||
NotificationRowView(notification: sampleNotification)
|
||||
}
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Push Notifications**: Integrate with UNUserNotificationCenter
|
||||
|
||||
Reference in New Issue
Block a user