Application iOS pour la gestion et l'analyse d'horaires de travail avec importation OCR automatique, widgets interactifs et support Apple Watch
Shifter est une application iOS native spécifiquement conçue pour les employés utilisant WorkJam qui souhaitent une segmentation détaillée de leurs heures de travail. Particulièrement adaptée aux secteurs retail, restauration et services, elle permet d'importer automatiquement des captures d'écran de plannings WorkJam via OCR, d'analyser précisément la répartition horaire par type de shift (Shift 1, Shift 2, Shift 3, etc.), et de visualiser des statistiques détaillées (mensuel/trimestriel/annuel) via des widgets iOS natifs.
Les employés utilisant WorkJam pour leurs horaires reçoivent leurs plannings sous forme de captures d'écran, mais l'application ne propose aucune analyse détaillée par segment. Shifter élimine :
- ❌ La saisie manuelle fastidieuse des horaires depuis WorkJam
- ❌ L'impossibilité d'analyser la répartition horaire par type de shift (Shift 1, Shift 2, Shift 3, etc.)
- ❌ La difficulté à comparer ses performances entre périodes (mois/trimestre/année)
- ❌ L'absence de vision synthétique des heures par segment
- ❌ Le risque de perte de données lors des réinstallations
✅ Import OCR ultra-rapide depuis WorkJam : Capture d'écran → Reconnaissance texte → Shifts importés avec segments automatiquement
✅ Segmentation détaillée par type de shift : Analyse précise de la répartition horaire (Shift 1, Shift 2, Shift 3, Pause, etc.)
✅ Statistiques intelligentes : Analyse comparative par mois/trimestre/année avec % et delta par segment
✅ Widgets iOS natifs : Accès instantané aux 3 segments prioritaires depuis l'écran d'accueil et écran verrouillé
✅ Apple Watch Support : Synchronisation automatique iPhone ↔ Watch avec statistiques trimestrielles
✅ Backup automatique : Restauration des données après réinstallation
✅ Interface rétro-moderne : Inspirée de system.css (esthétique macOS classique)
✅ Performance optimisée : 0 logs en production, regex pré-compilées, cache intelligent
| Composant | Technologie | Rôle |
|---|---|---|
| Framework UI | SwiftUI | Interface déclarative native iOS + watchOS |
| Persistance | SwiftData | ORM moderne avec ModelContainer partagé |
| OCR | Vision Framework | Reconnaissance de texte dans les images |
| Widgets | WidgetKit | Widgets natifs iOS (Home + Lock Screen) |
| Apple Watch | WatchConnectivity | Synchronisation iPhone ↔ Watch |
| Partage de données | App Groups | Conteneur partagé app ↔ widget ↔ watch |
| Cache | UserDefaults + JSON | Backup automatique et restauration |
Shifter/
├── WorkScheduleApp/ # Application principale iOS
│ ├── WorkScheduleAppApp.swift # Point d'entrée avec ModelContainer + WatchConnectivity
│ ├── ContentView.swift # Vue principale (statistiques + filtres)
│ ├── WatchConnectivityManager.swift # Gestion synchronisation iPhone ↔ Watch
│ ├── Models/
│ │ ├── WorkSchedule.swift # Modèle SwiftData (collection de shifts)
│ │ └── Shift.swift # Modèle SwiftData (shift individuel)
│ ├── ViewModels/
│ │ └── ScheduleViewModel.swift # Logique métier (OCR, backup, export, sync Watch)
│ ├── Views/
│ │ ├── ManageDataView.swift # Gestion des shifts (liste/suppression)
│ │ ├── ShiftStatisticsView.swift # Statistiques détaillées par segment
│ │ ├── SystemCSSTheme.swift # Thème visuel system.css
│ │ └── AboutView.swift # Page À Propos avec logo rond
│ ├── Services/
│ │ └── OCRService.swift # Service de reconnaissance de texte (696 lignes)
│ └── Helpers/
│ ├── FiscalCalendarHelper.swift # Logique trimestres fiscaux (Q1-Q4)
│ └── DateFormatterCache.swift # Cache pour formatage de dates
│
├── ShifterWatch Watch App/ # Application Apple Watch
│ ├── WatchDataManager.swift # Réception données depuis iPhone
│ └── Top3View.swift # Interface Watch (Top 3 shifts trimestriels)
│
├── ShifterWidget/ # Widget iOS (WidgetKit)
│ ├── ShifterWidget.swift # Vues Home Screen + Lock Screen + TimelineProvider
│ ├── WidgetDataProvider.swift # Accès SwiftData depuis le widget
│ └── ShifterWidgetBundle.swift # Configuration du widget
│
└── ShifterShareExtension/ # Extension de partage (import images)
└── ShareViewController.swift # Controller pour partage d'images
┌─────────────────────────────────────────────────────────────────┐
│ IMPORT DE PLANNING │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. Capture d'écran (Photo Picker / Share Extension) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. OCRService.recognizeText(UIImage) → Vision Framework │
│ - Détection de texte dans l'image │
│ - Support multi-formats (WorkJam, PDF, etc.) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. OCRService.parseScheduleText(String) → Parsing Regex │
│ - Extraction dates (format: "lundi 25 novembre") │
│ - Extraction horaires (9h-17h, 10:00 AM–11:30 AM, etc.) │
│ - Extraction segments (Shift 1, Shift 2, etc.) │
│ - Gestion indicateurs temporels ("hier", "Il y a X jours") │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. ScheduleViewModel.importScheduleFromImage() │
│ - Création objets Shift avec SwiftData │
│ - Sauvegarde dans ModelContainer (App Group) │
│ - Backup JSON automatique (Documents/) │
│ - Actualisation widget (WidgetCenter.reloadAllTimelines()) │
│ - Synchronisation Apple Watch (WatchConnectivityManager) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ DONNÉES PERSISTÉES │
│ ┌────────────────────────┐ ┌────────────────────────┐ │
│ │ App Group Container │ │ Documents Backup │ │
│ │ shifter.sqlite │ │ JSON (auto-restore) │ │
│ │ (SwiftData partagé) │ │ │ │
│ └────────────────────────┘ └────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌───────────────────┐ ┌───────────────────────┐
│ Application iOS │ │ Widget iOS │
│ (Statistiques) │ │ (Top 3 Shifts) │
└───────────────────┘ └───────────────────────┘
│
▼
┌───────────────────────────────────────────┐
│ WatchConnectivityManager │
│ - Synchronisation iPhone ↔ Watch │
│ - Envoi Top 3 + trimestre actuel │
└───────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────┐
│ Apple Watch │
│ - Affichage Top 3 shifts │
│ - Statistiques trimestrielles │
│ - Design macOS Classic │
└───────────────────────────────────────────┘
Conteneur principal pour un ensemble d'horaires importés.
@Model
final class WorkSchedule: Identifiable {
var id: UUID
var title: String
var createdAt: Date
var imageData: Data? // Image originale (optionnel)
var rawOCRText: String? // Texte OCR brut (debug)
@Relationship(deleteRule: .cascade)
var shifts: [Shift] // Relation 1-N avec Shift
// Propriétés calculées
var totalHours: Double // Total d'heures décimales
var totalHoursFormatted: String // Format "42.5h"
var locations: [String] // Lieux uniques triés
var segments: [String] // Segments uniques triés
}Modèle représentant un shift individuel avec optimisation d'index.
@Model
final class Shift: Identifiable {
#Index<Shift>([\.date], [\.segment], [\.date, \.segment]) // Index composites
var id: UUID
var date: Date // Jour du shift
var startTime: Date // Heure de début
var endTime: Date // Heure de fin
var location: String // Lieu de travail
var segment: String // Type de shift (Shift 1, Shift 2, Shift 3...)
var notes: String
var isConfirmed: Bool
@Relationship(deleteRule: .nullify, inverse: \WorkSchedule.shifts)
var schedule: WorkSchedule?
// Propriété calculée
var duration: TimeInterval // Durée en secondes
var durationFormatted: String // Format "7h30"
}Pourquoi des index ?
Les index composites [\.date], [\.segment], [\.date, \.segment] accélèrent les requêtes fréquentes :
- Filtrage par période (mois/trimestre/année)
- Filtrage par segment
- Combinaisons pour statistiques
Service de reconnaissance de texte avec 636 lignes de logique complexe.
| Format Détecté | Exemple | Regex Utilisée |
|---|---|---|
| WorkJam dates | "lundi 25 novembre" |
workJamDateRegex |
| Temps relatif | "Il y a 3 jours", "hier" |
relativeTimeRegex |
| Horaires AM/PM | "10:00 AM–11:30 AM" |
timeRangeAMPMRegex |
| Horaires 24h | "9h-17h", "9:00-17:00" |
timeRange24HRegex1/2 |
- Regex pré-compilées (
static let) : Évite la recompilation à chaque parsing - Cache de parsing : Stocke jusqu'à 20 résultats récents
- Queue concurrente : Thread-safe avec
DispatchQueue.concurrent - Logs conditionnels :
#if DEBUGpour 0 logs en production - 20 segments OCR : Setup, Cycle Counts, GB On Point, Connection, Roundtable, Onboarding, Visuals, etc.
// Texte OCR brut :
"""
lundi 25 novembre
9h-17h Shift 1 Lieu de travail A
17h-18h Pause
"""
// Résultat parsé :
[
(date: 2024-11-25, start: 09:00, end: 17:00, location: "Lieu de travail A", segment: "Shift 1"),
(date: 2024-11-25, start: 17:00, end: 18:00, location: "Lieu de travail A", segment: "Pause")
]ViewModel central gérant la logique métier (535 lignes).
Import OCR
func importScheduleFromImage(_ image: UIImage) async {
// 1. OCR
let text = try await ocrService.recognizeText(from: image)
// 2. Parsing
let parsedShifts = ocrService.parseScheduleText(text)
// 3. Sauvegarde SwiftData
let schedule = WorkSchedule(title: "Import \(Date())")
for parsed in parsedShifts {
let shift = Shift(date: parsed.date, startTime: parsed.startTime, ...)
schedule.shifts.append(shift)
}
modelContext.insert(schedule)
// 4. Backup automatique
await createAutoBackup()
// 5. Actualisation widget
WidgetCenter.shared.reloadAllTimelines()
// 6. Synchronisation Apple Watch
syncToWatch()
}Backup/Restauration Automatique
- Backup : Fichier JSON dans
Documents/shifter_auto_backup.json - Avantage : Survit aux réinstallations via Xcode (certificat développeur)
- Déclencheurs : Après chaque import/modification de données
- Restauration : Automatique au lancement si SwiftData vide
Gestion des trimestres fiscaux personnalisés.
| Trimestre | Mois | Particularité |
|---|---|---|
| Q1 | Oct-Déc | Dernier trimestre année civile |
| Q2 | Jan-Mar | Premier trimestre année civile |
| Q3 | Avr-Juin | - |
| Q4 | Juil-Sept | - |
Pourquoi ces trimestres ?
Alignement avec l'année fiscale de l'entreprise (différent de l'année civile).
enum FiscalCalendarHelper {
static func fiscalQuarter(for date: Date) -> Int {
let month = Calendar.current.component(.month, from: date)
switch month {
case 1...3: return 2
case 4...6: return 3
case 7...9: return 4
default: return 1 // 10-12
}
}
static func quarterLabel(for date: Date) -> String {
"Q\(fiscalQuarter(for: date)) \(fiscalYear(for: date))"
}
}ShifterWidget.swift
├── Provider (TimelineProvider)
│ ├── placeholder() → Vue placeholder
│ ├── getSnapshot() → Aperçu instantané
│ └── getTimeline() → Entrées timeline (rafraîchissement horaire)
│
├── ShifterWidgetEntryView
│ ├── Home Screen Widgets
│ │ ├── SmallWidgetView → Segment #1 avec %
│ │ ├── MediumWidgetView → Top 3 segments (liste compacte)
│ │ └── LargeWidgetView → Top 3 segments (cartes détaillées)
│ │
│ └── Lock Screen Widgets
│ ├── CircularView → % segment principal
│ ├── RectangularView → Nom shift + %
│ └── InlineView → Label trimestre
│
└── WidgetDataProvider
├── getTop3ShiftsWithStats() → [(segment, heures, %, delta)]
└── getQuarterStats() → QuarterStats (heures totales, %)
Configuration App Group : group.com.davidguia.shifter
// WorkScheduleAppApp.swift (Application principale)
let appGroupURL = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.davidguia.shifter"
)!
let storeURL = appGroupURL.appendingPathComponent("shifter.sqlite")
let modelConfiguration = ModelConfiguration(url: storeURL)
let container = try ModelContainer(for: schema, configurations: [modelConfiguration])
// WidgetDataProvider.swift (Widget)
let containerURL = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.davidguia.shifter"
)!
let storeURL = containerURL.appendingPathComponent("shifter.sqlite")
modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration])Résultat : Base de données SwiftData partagée entre app et widget.
Small Widget (Compact)
┌───────────────────────┐
│ Q2 2025 46h / 160h │
│ │
│ Shift 1 │
│ 46% │ ← Pourcentage (priorité espace limité)
│ │
│ +2h vs Q1 │
└───────────────────────┘
Medium Widget (Liste Top 3)
┌─────────────────────────────────────────────────────────────┐
│ Q2 2025 46h / 160h │
│ │
│ 1. Shift 1 7h30 46% +2h vs Q1 │
│ 2. Shift 2 5h00 31% -1h vs Q1 │
│ 3. Shift 3 3h45 23% +0.5h vs Q1 │
└─────────────────────────────────────────────────────────────┘
Large Widget (Cartes Détaillées)
┌─────────────────────────────────────────────────────────────┐
│ Q2 2025 46h / 160h │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. Shift 1 │ │
│ │ 7h30 • 46% │ │
│ │ +2h vs Q1 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 2. Shift 2 │ │
│ │ 5h00 • 31% │ │
│ │ -1h vs Q1 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 3. Shift 3 │ │
│ │ 3h45 • 23% │ │
│ │ +0.5h vs Q1 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Déclencheurs de rafraîchissement :
- Import de nouveaux shifts →
WidgetCenter.shared.reloadAllTimelines() - Suppression de shift →
WidgetCenter.shared.reloadAllTimelines()+syncToWatch() - Suppression complète →
WidgetCenter.shared.reloadAllTimelines()+syncToWatch() - Timeline automatique → Toutes les heures
Filtrage Widget :
- Exclut segment
"Général"(comme l'app principale) - Garde uniquement les shifts du trimestre fiscal en cours
- Calcule delta vs trimestre précédent
Gestion de la synchronisation bidirectionnelle iPhone ↔ Apple Watch.
Fonctionnalités
- ✅ Synchronisation automatique : Top 3 shifts envoyés après chaque import/suppression
- ✅ Données trimestrielles : Label trimestre fiscal (Q1-Q4) + heures totales
- ✅ Optimisé pour Watch : Filtrage segments pertinents (exclut "Général")
- ✅ Logs conditionnels : Debug activé uniquement en mode développement
Architecture de Synchronisation
// iPhone → Watch
WatchConnectivityManager.shared.syncTop3FromShifts(allShifts)
↓
WCSession.default.updateApplicationContext([
"top3": [
["segment": "Shift 1", "hours": 46.5, "percentage": 42.3],
["segment": "Shift 2", "hours": 35.0, "percentage": 31.8],
["segment": "Shift 3", "hours": 28.5, "percentage": 25.9]
],
"quarterLabel": "Q2 2025",
"totalHours": 110.0
])
↓
// Watch → WatchDataManager.session(_:didReceiveApplicationContext:)Top3View.swift - Design macOS Classic adapté à watchOS
┌──────────────────────────────┐
│ Q2 2025 110.0h │
│ │
│ ┌────────────────────────┐ │
│ │ Shift 1 │ │
│ │ 46.5h 42.3% │ │
│ └────────────────────────┘ │
│ │
│ ┌────────────────────────┐ │
│ │ Shift 2 │ │
│ │ 35.0h 31.8% │ │
│ └────────────────────────┘ │
│ │
│ ┌────────────────────────┐ │
│ │ Shift 3 │ │
│ │ 28.5h 25.9% │ │
│ └────────────────────────┘ │
└──────────────────────────────┘
Caractéristiques Design
- Fond beige
#EEEEEE(macOS Classic) - Bordures noires 2px
- Coins arrondis 8px
- Polices augmentées pour lisibilité watchOS
- Données simulateur pour tests (mode DEBUG)
Rafraîchissement
- Automatique via
WCSessionDelegate.session(_:didReceiveApplicationContext:) - Aucune action manuelle requise
- Persistance locale avec
@AppStorage
Inspiration : Esthétique macOS classique (System 7, Mac OS 9)
Palette de Couleurs
extension Color {
static let systemBeige = Color(red: 0.95, green: 0.94, blue: 0.90) // Fond principal
static let systemBorder = Color(red: 0.30, green: 0.30, blue: 0.30) // Bordures
static let systemText = Color.black // Texte principal
}Typographie
- Titres :
.system(.title, design: .monospaced)→ Police monospacée rétro - Corps :
.system(.body, design: .monospaced) - Statistiques :
.system(size: 26, weight: .bold, design: .monospaced)
Composants Visuels
- Bordures 2px noires
- Coins arrondis 8px
- Ombres légères pour profondeur
- Espacements généreux (12-16pt)
| Période | Description | Cas d'Usage |
|---|---|---|
| Mois | Vue mensuelle avec jours travaillés | Suivi quotidien, horaires hebdomadaires |
| Trimestre | Vue trimestrielle fiscale (Q1-Q4) | Objectifs trimestriels, comparaison |
| Année | Vue annuelle complète | Bilan annuel, tendances long terme |
Par Période
- Total d'heures travaillées
- Nombre de shifts
- Jours travaillés
- Heures moyennes/jour
Par Segment
- Heures totales par type de shift
- Pourcentage du total
- Delta vs période précédente (↑↓)
- Ranking (Top 3 pour widget)
Exemple de Calcul Delta
// Trimestre actuel (Q2 2025)
Shift 1: 7h30
// Trimestre précédent (Q1 2025)
Shift 1: 5h30
// Delta
+2h vs Q1 (7h30 - 5h30 = +2h)Certificats de développement Xcode expirent après 7 jours → App devient inutilisable.
Système d'Alertes Progressives
| Jours Restants | Badge | Alerte | Action |
|---|---|---|---|
| 6-7 jours | 🟢 Vert | Aucune | - |
| 4-5 jours | 🟢 Vert | Aucune | - |
| 2-3 jours | 🟠 Orange | Avertissement | Notification douce |
| 0-1 jour | 🔴 Rouge | URGENT | Modal bloquante |
Badge Visuel dans l'App
private var daysRemaining: Int {
if UserDefaults.standard.object(forKey: "firstInstallDate") == nil {
UserDefaults.standard.set(Date(), forKey: "firstInstallDate")
}
let installDate = UserDefaults.standard.object(forKey: "firstInstallDate") as! Date
let expiryDate = Calendar.current.date(byAdding: .day, value: 7, to: installDate)!
let components = Calendar.current.dateComponents([.day], from: Date(), to: expiryDate)
return max(0, components.day ?? 0)
}Backup Automatique comme Filet de Sécurité
- Fichier JSON dans
Documents/survit à la réinstallation - Restauration automatique au prochain lancement
- Limitation : Fonctionne uniquement avec même certificat développeur
📖 Guide d'Installation Complet (INSTALLATION.md) - Instructions détaillées pour certificat gratuit
- macOS : Sonoma 14.0+ (pour Xcode)
- Xcode : 16.0+ (pour SwiftData/iOS 18)
- iOS : 18.0+ (appareil physique ou simulateur)
- Compte Développeur Apple : Gratuit (certificat 7 jours) ou payant ($99/an)
-
Ouvrir le projet
cd Shifter open WorkScheduleApp.xcodeproj -
Configurer Signing & Capabilities
- Sélectionner la target
WorkScheduleApp - Onglet "Signing & Capabilities"
- Choisir votre équipe de développement
- Activer "Automatically manage signing"
⚠️ Important : Répéter pour les targetsShifterWidgetetShifterShareExtension - Sélectionner la target
-
Configurer App Groups
- Vérifier que l'App Group
group.com.davidguia.shifterest actif pour :- ✅ WorkScheduleApp
- ✅ ShifterWidget
- ✅ ShifterShareExtension
Si vous changez l'identifiant, modifier dans :
WorkScheduleAppApp.swift(ligne 21)WidgetDataProvider.swift(ligne 14)ShareViewController.swift
- Vérifier que l'App Group
-
Build & Run
Product → Run (⌘R)
- Connecter iPhone via USB
- Sélectionner appareil dans Xcode (en haut à gauche)
- Build & Run (⌘R)
- Sur iPhone : Paramètres → Général → Gestion des appareils → Faire confiance au développeur
- Lancer l'app → Écran vide "Aucun shift trouvé"
- Appuyer sur ➕ (en haut à droite)
- Choisir "Importer Capture d'écran"
- Sélectionner une capture de planning WorkJam
- Attendre l'OCR (1-3 secondes)
- Valider les shifts détectés
Format Attendu :
lundi 25 novembre
9h-17h Shift 1 Lieu de travail A
17h-18h Pause
mardi 26 novembre
10:00 AM–6:00 PM Shift 2 Lieu de travail A
- Segmented Control en haut : Mois / Trimestre / Année
- Flèches ◀︎ ▶︎ : Naviguer entre périodes
- Statistiques : Mise à jour automatique
- Liste scrollable : Tous les segments avec heures/% /delta
- Exclut "Général" : Segments utiles uniquement
- Tri par heures : Du plus grand au plus petit
Menu ⋯ (en haut à gauche)
- Gérer les données : Liste complète des shifts
- Supprimer un shift (swipe gauche)
- Supprimer tout (bouton rouge en bas)
- Exporter JSON : Sauvegarde manuelle
- Importer JSON : Restauration manuelle
- À propos : Informations app + certificat
Ajout Widget
- Écran d'accueil iPhone → Appui long
- Toucher ➕ (en haut à gauche)
- Chercher "Shifter"
- Choisir taille : Small / Medium / Large
- Ajouter au Widget
Rafraîchissement
- Automatique : Toutes les heures
- Manuel : Modifier des shifts dans l'app → Widget mis à jour instantanément
Conventions de Nommage
- Fichiers : PascalCase (
ScheduleViewModel.swift) - Classes/Structs : PascalCase (
WorkSchedule,OCRService) - Propriétés/Méthodes : camelCase (
totalHours,importSchedule()) - Constantes : camelCase (
appGroupIdentifier)
Architecture MVVM
View (SwiftUI) → ViewModel (@Published) → Model (SwiftData)
↓
Service (OCR, Network...)
Commentaires
// MARK: -: Sections majeures///: Documentation API (visible QuickHelp)//: Commentaires inline
- Index SwiftData : Requêtes 3x plus rapides sur
dateetsegment - Cache OCR : Évite parsing redondant (20 entrées max)
- Regex statiques : Pré-compilation avec
static let(gain 40% CPU) - DateFormatterCache : Réutilisation formatters (évite allocations répétées)
- Logs production : 40
print()enveloppés dans#if DEBUG(0 logs en Release) - Compilation wholemodule : Optimisations cross-fichiers activées
- WatchConnectivity optimisé : Envoi différentiel uniquement si données changées
Test Manuel Recommandé
- Import de 10+ shifts variés
- Vérifier statistiques mensuel/trimestriel/annuel
- Tester widget Small/Medium/Large
- Simuler expiration certificat (modifier date install)
- Tester backup/restauration (supprimer app → réinstaller)
Cas Limites à Vérifier
- Import texte vide
- Import sans dates détectées
- Import avec formats horaires mixtes
- Trimestres à cheval sur années (Q1 2024 → Q2 2025)
- Suppression complète puis restauration
- Ajout des icônes d'application (multiple variants)
- Système de mise en cache optimisé
- Optimisations des images et assets
- Rafraîchissement de l'interface amélioré
- Navigation optimisée dans les vues d'horaires
- Saisie manuelle de shifts
- Import ZIP de données
- Logger d'application (AppLogger)
- Amélioration des vues About et ShiftStatistics
- Layout optimisé avec en-tête/pied de page fixes
- Captures d'écran iPhone mises à jour
- Support Apple Watch complet (WatchConnectivity)
- Synchronisation automatique iPhone ↔ Watch
- Widgets Lock Screen iOS 16+
- 7 nouveaux segments OCR détectés
- Optimisations performance (40 print() en DEBUG)
- Polices iOS augmentées (+25-33%)
- Logo About arrondi (design Watch)
- Support TestFlight (distribution beta)
- Complications Apple Watch
- Notifications push pour shifts à venir
- Export PDF des statistiques
- Mode sombre natif iOS + watchOS
- Synchronisation iCloud
- Support multi-plannings
- Graphiques de tendances
- Widget interactif (boutons)
- Siri Shortcuts
- Publication App Store
- Intégration Calendrier iOS
- Machine Learning pour prédictions horaires
- API REST pour intégrations tierces
MIT License
Copyright (c) 2025 David Guia
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
David Guia
- GitHub: @david-guia
- Email: contact@davidguia.com
- Apple : Frameworks SwiftUI, SwiftData, Vision, WidgetKit
- system.css : Inspiration design (sakofchit/system.css)
- WorkJam : Format de plannings source
- Building Widgets with SwiftUI
- OCR with Vision Framework
- SwiftData Best Practices
- WatchConnectivity Programming Guide
Fait avec ❤️ pour simplifier la vie des travailleurs à horaires variables


