feat: Introduce offline synchronization for time registrations via a new sync service.
This commit is contained in:
+11
-2
@@ -4,6 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'screens/auth/login_screen.dart';
|
import 'screens/auth/login_screen.dart';
|
||||||
import 'screens/home/home_screen.dart';
|
import 'screens/home/home_screen.dart';
|
||||||
|
import 'services/sync_service.dart';
|
||||||
|
import 'providers/time_provider.dart';
|
||||||
import 'providers/auth_provider.dart';
|
import 'providers/auth_provider.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
@@ -15,9 +17,16 @@ void main() async {
|
|||||||
// Initialiser Hive for offline støtte
|
// Initialiser Hive for offline støtte
|
||||||
await Hive.initFlutter();
|
await Hive.initFlutter();
|
||||||
|
|
||||||
|
// Initialiser SyncService
|
||||||
|
final syncService = SyncService();
|
||||||
|
await syncService.init();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
const ProviderScope(
|
ProviderScope(
|
||||||
child: TimeRegApp(),
|
overrides: [
|
||||||
|
syncServiceProvider.overrideWithValue(syncService),
|
||||||
|
],
|
||||||
|
child: const TimeRegApp(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
enum SyncAction { create, update, delete }
|
||||||
|
|
||||||
|
class SyncOperation {
|
||||||
|
final String id;
|
||||||
|
final String collection;
|
||||||
|
final String docId;
|
||||||
|
final SyncAction action;
|
||||||
|
final Map<String, dynamic> data;
|
||||||
|
final DateTime timestamp;
|
||||||
|
|
||||||
|
SyncOperation({
|
||||||
|
required this.id,
|
||||||
|
required this.collection,
|
||||||
|
required this.docId,
|
||||||
|
required this.action,
|
||||||
|
required this.data,
|
||||||
|
required this.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'collection': collection,
|
||||||
|
'docId': docId,
|
||||||
|
'action': action.name,
|
||||||
|
'data': data,
|
||||||
|
'timestamp': timestamp.toIso8601String(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SyncOperation.fromMap(Map<String, dynamic> map) {
|
||||||
|
return SyncOperation(
|
||||||
|
id: map['id'],
|
||||||
|
collection: map['collection'],
|
||||||
|
docId: map['docId'],
|
||||||
|
action: SyncAction.values.firstWhere((e) => e.name == map['action']),
|
||||||
|
data: Map<String, dynamic>.from(map['data']),
|
||||||
|
timestamp: DateTime.parse(map['timestamp']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,16 @@ import '../services/time_service.dart';
|
|||||||
import '../models/time_registration.dart';
|
import '../models/time_registration.dart';
|
||||||
import 'auth_provider.dart';
|
import 'auth_provider.dart';
|
||||||
|
|
||||||
|
import '../services/sync_service.dart';
|
||||||
|
|
||||||
|
// Sync service provider
|
||||||
|
final syncServiceProvider = Provider<SyncService>((ref) => throw UnimplementedError());
|
||||||
|
|
||||||
// Time service provider
|
// Time service provider
|
||||||
final timeServiceProvider = Provider<TimeService>((ref) => TimeService());
|
final timeServiceProvider = Provider<TimeService>((ref) {
|
||||||
|
final syncService = ref.watch(syncServiceProvider);
|
||||||
|
return TimeService(syncService);
|
||||||
|
});
|
||||||
|
|
||||||
// Active registration provider - henter pågående timeregistrering
|
// Active registration provider - henter pågående timeregistrering
|
||||||
final activeRegistrationProvider = FutureProvider<TimeRegistration?>((ref) async {
|
final activeRegistrationProvider = FutureProvider<TimeRegistration?>((ref) async {
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
import '../models/sync_operation.dart';
|
||||||
|
|
||||||
|
class SyncService {
|
||||||
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
|
final Connectivity _connectivity = Connectivity();
|
||||||
|
late Box<Map> _queueBox;
|
||||||
|
bool _isOnline = true;
|
||||||
|
bool _isSyncing = false;
|
||||||
|
final _uuid = const Uuid();
|
||||||
|
|
||||||
|
// Stream controller for sync status
|
||||||
|
final _isSyncingController = StreamController<bool>.broadcast();
|
||||||
|
Stream<bool> get isSyncingStream => _isSyncingController.stream;
|
||||||
|
|
||||||
|
bool get isOnline => _isOnline;
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
_queueBox = await Hive.openBox<Map>('sync_queue');
|
||||||
|
|
||||||
|
// Sjekk initiell tilkobling
|
||||||
|
try {
|
||||||
|
final result = await _connectivity.checkConnectivity();
|
||||||
|
_updateConnectionStatus(result);
|
||||||
|
} catch (e) {
|
||||||
|
print('Kunne ikke sjekke tilkobling: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lytt etter endringer
|
||||||
|
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateConnectionStatus(ConnectivityResult result) {
|
||||||
|
_isOnline = result != ConnectivityResult.none;
|
||||||
|
print('Tilkoblingsstatus endret: $_isOnline ($result)');
|
||||||
|
|
||||||
|
if (_isOnline) {
|
||||||
|
_processQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legg til operasjon i køen
|
||||||
|
Future<void> addToQueue(SyncOperation operation) async {
|
||||||
|
await _queueBox.put(operation.id, operation.toMap());
|
||||||
|
|
||||||
|
// Prøv å synkronisere umiddelbart hvis vi er online
|
||||||
|
if (_isOnline) {
|
||||||
|
_processQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Behandle køen
|
||||||
|
Future<void> _processQueue() async {
|
||||||
|
if (_isSyncing || _queueBox.isEmpty) return;
|
||||||
|
|
||||||
|
_isSyncing = true;
|
||||||
|
_isSyncingController.add(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Hent alle operasjoner og sorter etter tidsstempel
|
||||||
|
final operations = _queueBox.values
|
||||||
|
.map((map) => SyncOperation.fromMap(Map<String, dynamic>.from(map)))
|
||||||
|
.toList()
|
||||||
|
..sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||||
|
|
||||||
|
for (final op in operations) {
|
||||||
|
try {
|
||||||
|
await _performOperation(op);
|
||||||
|
await _queueBox.delete(op.id);
|
||||||
|
} catch (e) {
|
||||||
|
print('Feil ved synkronisering av operasjon ${op.id}: $e');
|
||||||
|
// Vi lar den ligge i køen for å prøve igjen senere
|
||||||
|
// Men hvis det er en permanent feil, burde vi kanskje flytte den til en "feil-kø"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_isSyncing = false;
|
||||||
|
_isSyncingController.add(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _performOperation(SyncOperation op) async {
|
||||||
|
final collection = _firestore.collection(op.collection);
|
||||||
|
final docRef = collection.doc(op.docId);
|
||||||
|
|
||||||
|
switch (op.action) {
|
||||||
|
case SyncAction.create:
|
||||||
|
await docRef.set(op.data);
|
||||||
|
break;
|
||||||
|
case SyncAction.update:
|
||||||
|
await docRef.update(op.data);
|
||||||
|
break;
|
||||||
|
case SyncAction.delete:
|
||||||
|
await docRef.delete();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hjelpemetode for å opprette operasjon
|
||||||
|
Future<void> queueOperation({
|
||||||
|
required String collection,
|
||||||
|
required String docId,
|
||||||
|
required SyncAction action,
|
||||||
|
required Map<String, dynamic> data,
|
||||||
|
}) async {
|
||||||
|
final op = SyncOperation(
|
||||||
|
id: _uuid.v4(),
|
||||||
|
collection: collection,
|
||||||
|
docId: docId,
|
||||||
|
action: action,
|
||||||
|
data: data,
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
);
|
||||||
|
await addToQueue(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
+155
-73
@@ -2,11 +2,16 @@ import 'package:cloud_firestore/cloud_firestore.dart';
|
|||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
import '../models/time_registration.dart';
|
import '../models/time_registration.dart';
|
||||||
import '../models/deviation.dart';
|
import '../models/deviation.dart';
|
||||||
|
import '../models/sync_operation.dart';
|
||||||
|
import 'sync_service.dart';
|
||||||
|
|
||||||
class TimeService {
|
class TimeService {
|
||||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
|
final SyncService _syncService;
|
||||||
final Uuid _uuid = const Uuid();
|
final Uuid _uuid = const Uuid();
|
||||||
|
|
||||||
|
TimeService(this._syncService);
|
||||||
|
|
||||||
// Start ny timeregistrering (stempling)
|
// Start ny timeregistrering (stempling)
|
||||||
Future<String> startTimer({
|
Future<String> startTimer({
|
||||||
required String userId,
|
required String userId,
|
||||||
@@ -35,10 +40,19 @@ class TimeService {
|
|||||||
isManual: false,
|
isManual: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
await _firestore
|
if (_syncService.isOnline) {
|
||||||
.collection('time_registrations')
|
await _firestore
|
||||||
.doc(registration.id)
|
.collection('time_registrations')
|
||||||
.set(registration.toFirestore());
|
.doc(registration.id)
|
||||||
|
.set(registration.toFirestore());
|
||||||
|
} else {
|
||||||
|
await _syncService.queueOperation(
|
||||||
|
collection: 'time_registrations',
|
||||||
|
docId: registration.id,
|
||||||
|
action: SyncAction.create,
|
||||||
|
data: registration.toFirestore(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return registration.id;
|
return registration.id;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -49,28 +63,63 @@ class TimeService {
|
|||||||
// Stopp pågående timeregistrering
|
// Stopp pågående timeregistrering
|
||||||
Future<void> stopTimer(String registrationId, String userId) async {
|
Future<void> stopTimer(String registrationId, String userId) async {
|
||||||
try {
|
try {
|
||||||
final doc = await _firestore
|
// Hvis vi er offline, kan vi ikke hente dokumentet for å beregne varighet nøyaktig
|
||||||
.collection('time_registrations')
|
// basert på server-data. Vi må stole på lokal logikk eller gjøre en "blind" oppdatering.
|
||||||
.doc(registrationId)
|
// For enkelhets skyld antar vi her at vi har start-tiden tilgjengelig i UI/State
|
||||||
.get();
|
// eller at vi gjør en update med serverTimestamp for slutt-tid.
|
||||||
|
|
||||||
if (!doc.exists) {
|
// Men for offline-støtte må vi beregne alt lokalt.
|
||||||
throw Exception('Timeregistrering ikke funnet');
|
// Dette krever at vi har den aktive registreringen lagret lokalt.
|
||||||
}
|
// For nå implementerer vi en enkel offline-støtte som bare køer oppdateringen.
|
||||||
|
|
||||||
final registration = TimeRegistration.fromFirestore(doc);
|
// NB: Dette er en forenkling. I en fullverdig offline-app bør vi lese fra lokal cache.
|
||||||
|
|
||||||
final endTime = DateTime.now();
|
final endTime = DateTime.now();
|
||||||
final duration = endTime.difference(registration.startTime).inMinutes;
|
|
||||||
|
// Vi sender en oppdatering som setter endTime.
|
||||||
await _firestore
|
// Cloud Functions eller en senere sync vil måtte håndtere duration-beregning hvis vi ikke har start-tid.
|
||||||
.collection('time_registrations')
|
// Men vent, vi kan ikke stole på at Cloud Functions kjører offline.
|
||||||
.doc(registrationId)
|
|
||||||
.update({
|
// Løsning: Vi gjør en 'update' operasjon.
|
||||||
|
|
||||||
|
final updates = {
|
||||||
'endTime': Timestamp.fromDate(endTime),
|
'endTime': Timestamp.fromDate(endTime),
|
||||||
'duration': duration,
|
|
||||||
'updatedAt': Timestamp.fromDate(DateTime.now()),
|
'updatedAt': Timestamp.fromDate(DateTime.now()),
|
||||||
'lastModifiedBy': userId,
|
'lastModifiedBy': userId,
|
||||||
});
|
// Vi kan ikke sette duration her uten å vite starttid.
|
||||||
|
// Hvis vi er online, henter vi og beregner.
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_syncService.isOnline) {
|
||||||
|
final doc = await _firestore
|
||||||
|
.collection('time_registrations')
|
||||||
|
.doc(registrationId)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (!doc.exists) {
|
||||||
|
throw Exception('Timeregistrering ikke funnet');
|
||||||
|
}
|
||||||
|
|
||||||
|
final registration = TimeRegistration.fromFirestore(doc);
|
||||||
|
final duration = endTime.difference(registration.startTime).inMinutes;
|
||||||
|
updates['duration'] = duration;
|
||||||
|
|
||||||
|
await _firestore
|
||||||
|
.collection('time_registrations')
|
||||||
|
.doc(registrationId)
|
||||||
|
.update(updates);
|
||||||
|
} else {
|
||||||
|
// Offline: Vi køer oppdateringen.
|
||||||
|
// NB: Duration vil mangle eller være feil hvis vi ikke har starttid lokalt.
|
||||||
|
// Dette er en begrensning i denne enkle implementasjonen.
|
||||||
|
// En bedre løsning ville være å cache aktive registreringer lokalt.
|
||||||
|
await _syncService.queueOperation(
|
||||||
|
collection: 'time_registrations',
|
||||||
|
docId: registrationId,
|
||||||
|
action: SyncAction.update,
|
||||||
|
data: updates,
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Kunne ikke stoppe timer: $e');
|
throw Exception('Kunne ikke stoppe timer: $e');
|
||||||
}
|
}
|
||||||
@@ -109,10 +158,19 @@ class TimeService {
|
|||||||
isManual: true,
|
isManual: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
await _firestore
|
if (_syncService.isOnline) {
|
||||||
.collection('time_registrations')
|
await _firestore
|
||||||
.doc(registration.id)
|
.collection('time_registrations')
|
||||||
.set(registration.toFirestore());
|
.doc(registration.id)
|
||||||
|
.set(registration.toFirestore());
|
||||||
|
} else {
|
||||||
|
await _syncService.queueOperation(
|
||||||
|
collection: 'time_registrations',
|
||||||
|
docId: registration.id,
|
||||||
|
action: SyncAction.create,
|
||||||
|
data: registration.toFirestore(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return registration.id;
|
return registration.id;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -133,54 +191,46 @@ class TimeService {
|
|||||||
String? comment,
|
String? comment,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final doc = await _firestore
|
|
||||||
.collection('time_registrations')
|
|
||||||
.doc(registrationId)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (!doc.exists) {
|
|
||||||
throw Exception('Timeregistrering ikke funnet');
|
|
||||||
}
|
|
||||||
|
|
||||||
final registration = TimeRegistration.fromFirestore(doc);
|
|
||||||
final Map<String, dynamic> updates = {
|
final Map<String, dynamic> updates = {
|
||||||
'updatedAt': Timestamp.fromDate(DateTime.now()),
|
'updatedAt': Timestamp.fromDate(DateTime.now()),
|
||||||
'lastModifiedBy': userId,
|
'lastModifiedBy': userId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (startTime != null) {
|
if (startTime != null) updates['startTime'] = Timestamp.fromDate(startTime);
|
||||||
updates['startTime'] = Timestamp.fromDate(startTime);
|
if (endTime != null) updates['endTime'] = Timestamp.fromDate(endTime);
|
||||||
}
|
if (type != null) updates['type'] = type.name;
|
||||||
if (endTime != null) {
|
if (breaks != null) updates['breaks'] = breaks.map((b) => b.toMap()).toList();
|
||||||
updates['endTime'] = Timestamp.fromDate(endTime);
|
if (projectId != null) updates['projectId'] = projectId;
|
||||||
}
|
if (customerId != null) updates['customerId'] = customerId;
|
||||||
if (type != null) {
|
if (comment != null) updates['comment'] = comment;
|
||||||
updates['type'] = type.name;
|
|
||||||
}
|
|
||||||
if (breaks != null) {
|
|
||||||
updates['breaks'] = breaks.map((b) => b.toMap()).toList();
|
|
||||||
}
|
|
||||||
if (projectId != null) {
|
|
||||||
updates['projectId'] = projectId;
|
|
||||||
}
|
|
||||||
if (customerId != null) {
|
|
||||||
updates['customerId'] = customerId;
|
|
||||||
}
|
|
||||||
if (comment != null) {
|
|
||||||
updates['comment'] = comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Beregn ny varighet hvis start eller slutt er endret
|
if (_syncService.isOnline) {
|
||||||
final newStartTime = startTime ?? registration.startTime;
|
// Hvis online, hent og beregn duration
|
||||||
final newEndTime = endTime ?? registration.endTime;
|
if (startTime != null || endTime != null) {
|
||||||
if (newEndTime != null) {
|
final doc = await _firestore.collection('time_registrations').doc(registrationId).get();
|
||||||
updates['duration'] = newEndTime.difference(newStartTime).inMinutes;
|
if (doc.exists) {
|
||||||
|
final reg = TimeRegistration.fromFirestore(doc);
|
||||||
|
final newStart = startTime ?? reg.startTime;
|
||||||
|
final newEnd = endTime ?? reg.endTime;
|
||||||
|
if (newEnd != null) {
|
||||||
|
updates['duration'] = newEnd.difference(newStart).inMinutes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _firestore
|
||||||
|
.collection('time_registrations')
|
||||||
|
.doc(registrationId)
|
||||||
|
.update(updates);
|
||||||
|
} else {
|
||||||
|
// Offline: Kø oppdatering. Duration beregnes ikke her (begrensning).
|
||||||
|
await _syncService.queueOperation(
|
||||||
|
collection: 'time_registrations',
|
||||||
|
docId: registrationId,
|
||||||
|
action: SyncAction.update,
|
||||||
|
data: updates,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _firestore
|
|
||||||
.collection('time_registrations')
|
|
||||||
.doc(registrationId)
|
|
||||||
.update(updates);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Kunne ikke oppdatere timeregistrering: $e');
|
throw Exception('Kunne ikke oppdatere timeregistrering: $e');
|
||||||
}
|
}
|
||||||
@@ -189,10 +239,19 @@ class TimeService {
|
|||||||
// Slett timeregistrering
|
// Slett timeregistrering
|
||||||
Future<void> deleteRegistration(String registrationId) async {
|
Future<void> deleteRegistration(String registrationId) async {
|
||||||
try {
|
try {
|
||||||
await _firestore
|
if (_syncService.isOnline) {
|
||||||
.collection('time_registrations')
|
await _firestore
|
||||||
.doc(registrationId)
|
.collection('time_registrations')
|
||||||
.delete();
|
.doc(registrationId)
|
||||||
|
.delete();
|
||||||
|
} else {
|
||||||
|
await _syncService.queueOperation(
|
||||||
|
collection: 'time_registrations',
|
||||||
|
docId: registrationId,
|
||||||
|
action: SyncAction.delete,
|
||||||
|
data: {},
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Kunne ikke slette timeregistrering: $e');
|
throw Exception('Kunne ikke slette timeregistrering: $e');
|
||||||
}
|
}
|
||||||
@@ -205,6 +264,13 @@ class TimeService {
|
|||||||
DateTime? endDate,
|
DateTime? endDate,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
|
// TODO: Implementer lokal caching for lesing offline
|
||||||
|
// For nå kaster vi feil hvis offline og ingen cache
|
||||||
|
if (!_syncService.isOnline) {
|
||||||
|
// Her burde vi returnere fra lokal cache
|
||||||
|
// return [];
|
||||||
|
}
|
||||||
|
|
||||||
Query query = _firestore
|
Query query = _firestore
|
||||||
.collection('time_registrations')
|
.collection('time_registrations')
|
||||||
.where('userId', isEqualTo: userId);
|
.where('userId', isEqualTo: userId);
|
||||||
@@ -245,6 +311,8 @@ class TimeService {
|
|||||||
// Hent pågående timeregistrering
|
// Hent pågående timeregistrering
|
||||||
Future<TimeRegistration?> getActiveRegistration(String userId) async {
|
Future<TimeRegistration?> getActiveRegistration(String userId) async {
|
||||||
try {
|
try {
|
||||||
|
// TODO: Sjekk lokal cache først
|
||||||
|
|
||||||
final snapshot = await _firestore
|
final snapshot = await _firestore
|
||||||
.collection('time_registrations')
|
.collection('time_registrations')
|
||||||
.where('userId', isEqualTo: userId)
|
.where('userId', isEqualTo: userId)
|
||||||
@@ -267,6 +335,8 @@ class TimeService {
|
|||||||
DateTime? startDate,
|
DateTime? startDate,
|
||||||
DateTime? endDate,
|
DateTime? endDate,
|
||||||
}) {
|
}) {
|
||||||
|
// TODO: Kombiner lokal cache og firestore stream
|
||||||
|
|
||||||
Query query = _firestore
|
Query query = _firestore
|
||||||
.collection('time_registrations')
|
.collection('time_registrations')
|
||||||
.where('userId', isEqualTo: userId);
|
.where('userId', isEqualTo: userId);
|
||||||
@@ -320,6 +390,7 @@ class TimeService {
|
|||||||
'overtime': overtimeMinutes,
|
'overtime': overtimeMinutes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hent avvik for organisasjon
|
// Hent avvik for organisasjon
|
||||||
Future<List<Deviation>> getDeviations({
|
Future<List<Deviation>> getDeviations({
|
||||||
required String organizationId,
|
required String organizationId,
|
||||||
@@ -348,11 +419,22 @@ class TimeService {
|
|||||||
String? comment,
|
String? comment,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
await _firestore.collection('deviations').doc(deviationId).update({
|
final updates = {
|
||||||
'acknowledgedAt': Timestamp.now(),
|
'acknowledgedAt': Timestamp.now(),
|
||||||
'acknowledgedBy': userId,
|
'acknowledgedBy': userId,
|
||||||
if (comment != null) 'comment': comment,
|
if (comment != null) 'comment': comment,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (_syncService.isOnline) {
|
||||||
|
await _firestore.collection('deviations').doc(deviationId).update(updates);
|
||||||
|
} else {
|
||||||
|
await _syncService.queueOperation(
|
||||||
|
collection: 'deviations',
|
||||||
|
docId: deviationId,
|
||||||
|
action: SyncAction.update,
|
||||||
|
data: updates,
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Kunne ikke kvittere ut avvik: $e');
|
throw Exception('Kunne ikke kvittere ut avvik: $e');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import FlutterMacOS
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import cloud_firestore
|
import cloud_firestore
|
||||||
|
import connectivity_plus
|
||||||
import firebase_auth
|
import firebase_auth
|
||||||
import firebase_core
|
import firebase_core
|
||||||
import firebase_messaging
|
import firebase_messaging
|
||||||
@@ -16,6 +17,7 @@ import share_plus
|
|||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
|
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
|
||||||
|
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
||||||
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||||
|
|||||||
@@ -105,6 +105,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
connectivity_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: connectivity_plus
|
||||||
|
sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.2"
|
||||||
|
connectivity_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_plus_platform_interface
|
||||||
|
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.4"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -464,6 +480,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.6"
|
version: "1.0.6"
|
||||||
|
nm:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nm
|
||||||
|
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ dependencies:
|
|||||||
# Offline support
|
# Offline support
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
hive_flutter: ^1.1.0
|
hive_flutter: ^1.1.0
|
||||||
|
connectivity_plus: ^5.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <cloud_firestore/cloud_firestore_plugin_c_api.h>
|
#include <cloud_firestore/cloud_firestore_plugin_c_api.h>
|
||||||
|
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||||
#include <firebase_auth/firebase_auth_plugin_c_api.h>
|
#include <firebase_auth/firebase_auth_plugin_c_api.h>
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
#include <firebase_storage/firebase_storage_plugin_c_api.h>
|
#include <firebase_storage/firebase_storage_plugin_c_api.h>
|
||||||
@@ -16,6 +17,8 @@
|
|||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
CloudFirestorePluginCApiRegisterWithRegistrar(
|
CloudFirestorePluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("CloudFirestorePluginCApi"));
|
registry->GetRegistrarForPlugin("CloudFirestorePluginCApi"));
|
||||||
|
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||||
FirebaseAuthPluginCApiRegisterWithRegistrar(
|
FirebaseAuthPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
|
||||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
cloud_firestore
|
cloud_firestore
|
||||||
|
connectivity_plus
|
||||||
firebase_auth
|
firebase_auth
|
||||||
firebase_core
|
firebase_core
|
||||||
firebase_storage
|
firebase_storage
|
||||||
|
|||||||
Reference in New Issue
Block a user