Коммит 099cd6f7 создал по автору Radch-enko's avatar Radch-enko
Просмотр файлов

Refactor: replace `EventManager` with `DefaultEventRepositoryMediator` for...

Refactor: replace `EventManager` with `DefaultEventRepositoryMediator` for improved coordination logic

- Removed `EventManager` and introduced `DefaultEventRepositoryMediator` to streamline network and local repository communication.
- Updated `DataModule` to provide `DefaultEventRepositoryMediator` and a new `EventManagerRepositoryImpl`.
- Enhanced error handling for repository operations and unified event creation, update, and deletion logic.
- Added validation to `EventInfo` to ensure `startTime` is before `finishTime`.
- Simplified `UpdateUseCase` with a consistent `DEFAULT_DELAY` for better readability and maintainability.
- Refined time rounding logic in `NetworkEventRepository` for accurate event scheduling updates.
- Updated `iosApp.xcscheme` to include new configurations.
владелец dfc32ebd
...@@ -8,12 +8,14 @@ import band.effective.office.tablet.core.data.api.impl.BookingApiImpl ...@@ -8,12 +8,14 @@ import band.effective.office.tablet.core.data.api.impl.BookingApiImpl
import band.effective.office.tablet.core.data.api.impl.UserApiImpl import band.effective.office.tablet.core.data.api.impl.UserApiImpl
import band.effective.office.tablet.core.data.api.impl.WorkspaceApiImpl import band.effective.office.tablet.core.data.api.impl.WorkspaceApiImpl
import band.effective.office.tablet.core.data.network.HttpClientProvider import band.effective.office.tablet.core.data.network.HttpClientProvider
import band.effective.office.tablet.core.data.repository.EventManager import band.effective.office.tablet.core.data.repository.DefaultEventRepositoryMediator
import band.effective.office.tablet.core.data.repository.EventManagerRepositoryImpl
import band.effective.office.tablet.core.data.repository.LocalEventStoreRepository import band.effective.office.tablet.core.data.repository.LocalEventStoreRepository
import band.effective.office.tablet.core.data.repository.NetworkEventRepository import band.effective.office.tablet.core.data.repository.NetworkEventRepository
import band.effective.office.tablet.core.data.repository.OrganizerRepositoryImpl import band.effective.office.tablet.core.data.repository.OrganizerRepositoryImpl
import band.effective.office.tablet.core.domain.repository.BookingRepository import band.effective.office.tablet.core.domain.repository.BookingRepository
import band.effective.office.tablet.core.domain.repository.EventManagerRepository import band.effective.office.tablet.core.domain.repository.EventManagerRepository
import band.effective.office.tablet.core.domain.repository.EventRepositoryMediator
import band.effective.office.tablet.core.domain.repository.LocalBookingRepository import band.effective.office.tablet.core.domain.repository.LocalBookingRepository
import band.effective.office.tablet.core.domain.repository.OrganizerRepository import band.effective.office.tablet.core.domain.repository.OrganizerRepository
import org.koin.dsl.module import org.koin.dsl.module
...@@ -48,7 +50,19 @@ val dataModule = module { ...@@ -48,7 +50,19 @@ val dataModule = module {
LocalEventStoreRepository() LocalEventStoreRepository()
} }
// Mediator for coordinating between repositories
single<EventRepositoryMediator> {
DefaultEventRepositoryMediator(
networkRepository = get(),
localRepository = get()
)
}
single<EventManagerRepository> { single<EventManagerRepository> {
EventManager(networkEventRepository = get(), localEventStoreRepository = get()) EventManagerRepositoryImpl(
networkEventRepository = get(),
localEventStoreRepository = get(),
mediator = get()
)
} }
} }
package band.effective.office.tablet.core.data.repository
import band.effective.office.tablet.core.domain.Either
import band.effective.office.tablet.core.domain.ErrorResponse
import band.effective.office.tablet.core.domain.ErrorWithData
import band.effective.office.tablet.core.domain.map
import band.effective.office.tablet.core.domain.model.EventInfo
import band.effective.office.tablet.core.domain.model.RoomInfo
import band.effective.office.tablet.core.domain.repository.BookingRepository
import band.effective.office.tablet.core.domain.repository.EventRepositoryMediator
import band.effective.office.tablet.core.domain.repository.LocalBookingRepository
import band.effective.office.tablet.core.domain.unbox
/**
* Default implementation of [EventRepositoryMediator] that coordinates operations between
* network and local repositories.
*
* @property networkRepository Repository for network operations
* @property localRepository Repository for local storage operations
*/
class DefaultEventRepositoryMediator(
private val networkRepository: BookingRepository,
private val localRepository: LocalBookingRepository
) : EventRepositoryMediator {
/**
* Synchronizes data between network and local repositories.
* Fetches data from the network and updates the local repository.
* If the network operation fails, it will use the saved data from the local repository.
*
* @return Either containing the synchronized room information or an error with saved data
*/
override suspend fun synchronizeData(): Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>> {
val save = localRepository.getRoomsInfo().unbox(
errorHandler = { it.saveData }
)
val roomInfos = networkRepository.getRoomsInfo()
.map(
errorMapper = { error ->
// Prevent NPE by handling null save data
error.copy(saveData = save)
},
successMapper = { it }
)
localRepository.updateRoomsInfo(roomInfos)
return roomInfos
}
/**
* Handles booking creation, coordinating between network and local repositories.
* Updates the local repository immediately with a loading state,
* then attempts to create the booking in the network repository.
* If the network operation fails, the booking is removed from the local repository.
*
* @param eventInfo Information about the event to create
* @param room Information about the room to book
* @return Either containing the created event information or an error
*/
override suspend fun handleBookingCreation(
eventInfo: EventInfo,
room: RoomInfo
): Either<ErrorResponse, EventInfo> {
val loadingEvent = eventInfo.copy(isLoading = true)
// Update local repository with loading state
localRepository.createBooking(loadingEvent, room)
// Attempt to create booking in network repository
val response = networkRepository.createBooking(loadingEvent, room)
when (response) {
is Either.Error -> {
// On error, remove the booking from local repository
localRepository.deleteBooking(loadingEvent, room)
}
is Either.Success -> {
// On success, update the booking in local repository with the response data
val event = response.data
localRepository.updateBooking(event, room)
}
}
return response
}
/**
* Handles booking update, coordinating between network and local repositories.
* Updates the local repository immediately with a loading state,
* then attempts to update the booking in the network repository.
* If the network operation fails, the original event is restored in the local repository.
*
* @param eventInfo Updated information about the event
* @param room Information about the room where the booking exists
* @return Either containing the updated event information or an error
*/
override suspend fun handleBookingUpdate(
eventInfo: EventInfo,
room: RoomInfo
): Either<ErrorResponse, EventInfo> {
val loadingEvent = eventInfo.copy(isLoading = true)
// Get the original event to restore in case of failure
val oldEvent = localRepository.getBooking(eventInfo) as? Either.Success
?: return Either.Error(ErrorResponse(404, "Old event with id ${eventInfo.id} wasn't found"))
// Update local repository with loading state
localRepository.updateBooking(loadingEvent, room)
// Attempt to update booking in network repository
val response = networkRepository.updateBooking(loadingEvent, room)
when (response) {
is Either.Error -> {
// On error, restore the original event in local repository
localRepository.updateBooking(oldEvent.data, room)
}
is Either.Success -> {
// On success, update the booking in local repository with the response data
val event = response.data
localRepository.updateBooking(event, room)
}
}
return response
}
/**
* Handles booking deletion, coordinating between network and local repositories.
* Updates the local repository immediately with a loading state,
* then attempts to delete the booking in the network repository.
* If the network operation fails, the original event is restored in the local repository.
*
* @param eventInfo Information about the event to delete
* @param room Information about the room where the booking exists
* @return Either containing a success message or an error
*/
override suspend fun handleBookingDeletion(
eventInfo: EventInfo,
room: RoomInfo
): Either<ErrorResponse, String> {
val loadingEvent = eventInfo.copy(isLoading = true)
// Save the original event state before attempting to delete
val originalEvent = eventInfo.copy()
// Mark as loading in local repository
localRepository.updateBooking(loadingEvent, room)
// Attempt to delete from network
val response = networkRepository.deleteBooking(loadingEvent, room)
when (response) {
is Either.Error -> {
// On error, restore the original event in local repository
localRepository.updateBooking(originalEvent, room)
}
is Either.Success -> {
// On success, delete from local repository
localRepository.deleteBooking(loadingEvent, room)
}
}
return response
}
}
\ No newline at end of file
...@@ -3,107 +3,130 @@ package band.effective.office.tablet.core.data.repository ...@@ -3,107 +3,130 @@ package band.effective.office.tablet.core.data.repository
import band.effective.office.tablet.core.domain.Either import band.effective.office.tablet.core.domain.Either
import band.effective.office.tablet.core.domain.ErrorResponse import band.effective.office.tablet.core.domain.ErrorResponse
import band.effective.office.tablet.core.domain.ErrorWithData import band.effective.office.tablet.core.domain.ErrorWithData
import band.effective.office.tablet.core.domain.map
import band.effective.office.tablet.core.domain.model.EventInfo import band.effective.office.tablet.core.domain.model.EventInfo
import band.effective.office.tablet.core.domain.model.RoomInfo import band.effective.office.tablet.core.domain.model.RoomInfo
import band.effective.office.tablet.core.domain.repository.BookingRepository import band.effective.office.tablet.core.domain.repository.BookingRepository
import band.effective.office.tablet.core.domain.repository.EventManagerRepository import band.effective.office.tablet.core.domain.repository.EventManagerRepository
import band.effective.office.tablet.core.domain.repository.EventRepositoryMediator
import band.effective.office.tablet.core.domain.repository.LocalBookingRepository import band.effective.office.tablet.core.domain.repository.LocalBookingRepository
import band.effective.office.tablet.core.domain.unbox import band.effective.office.tablet.core.domain.unbox
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO import kotlinx.coroutines.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class EventManager( /**
* Implementation of [EventManagerRepository] that manages events between network and local repositories.
* Handles synchronization, caching, and error recovery for booking operations.
*
* @property networkEventRepository Repository for network operations
* @property localEventStoreRepository Repository for local storage operations
* @property mediator Mediator for coordinating operations between repositories
*/
class EventManagerRepositoryImpl(
private val networkEventRepository: BookingRepository, private val networkEventRepository: BookingRepository,
private val localEventStoreRepository: LocalBookingRepository private val localEventStoreRepository: LocalBookingRepository,
private val mediator: EventRepositoryMediator = DefaultEventRepositoryMediator(
networkRepository = networkEventRepository,
localRepository = localEventStoreRepository
)
) : EventManagerRepository { ) : EventManagerRepository {
private val scope = CoroutineScope(Dispatchers.IO) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val updateJob: Job
init { init {
scope.launch { updateJob = scope.launch {
networkEventRepository.subscribeOnUpdates().collect { networkEventRepository.subscribeOnUpdates().collect {
refreshData() refreshData()
} }
} }
} }
/**
* Cancels all coroutines launched in this scope.
* Should be called when the EventManager is no longer needed to prevent memory leaks.
*/
fun dispose() {
updateJob.cancel()
scope.cancel()
}
/**
* Returns a flow of room information updates from the local repository.
* @return Flow of Either containing room information or error with saved data
*/
override fun getEventsFlow() = localEventStoreRepository.subscribeOnUpdates() override fun getEventsFlow() = localEventStoreRepository.subscribeOnUpdates()
/**
* Refreshes room information from the network repository and updates the local repository.
* If the network operation fails, it will use the saved data from the local repository.
* @return Either containing the updated room information or error with saved data
*/
override suspend fun refreshData(): Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>> { override suspend fun refreshData(): Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>> {
val save = localEventStoreRepository.getRoomsInfo().unbox( return mediator.synchronizeData()
errorHandler = { it.saveData }
)
val roomInfos = networkEventRepository.getRoomsInfo()
.map(
errorMapper = { it.copy(saveData = save) },
successMapper = { it }
)
localEventStoreRepository.updateRoomsInfo(roomInfos)
return roomInfos
} }
/**
* Creates a new booking in the specified room.
* Updates the local repository immediately with a loading state,
* then attempts to create the booking in the network repository.
* If the network operation fails, the booking is removed from the local repository.
*
* @param roomName Name of the room to book
* @param eventInfo Information about the event to create
* @return Either containing the created event information or an error
*/
override suspend fun createBooking(roomName: String, eventInfo: EventInfo): Either<ErrorResponse, EventInfo> { override suspend fun createBooking(roomName: String, eventInfo: EventInfo): Either<ErrorResponse, EventInfo> {
val loadingEvent = eventInfo.copy(isLoading = true)
val roomInfo = getRoomByName(roomName) val roomInfo = getRoomByName(roomName)
?: return Either.Error(ErrorResponse(404, "Couldn't find a room with name $roomName")) ?: return Either.Error(ErrorResponse(404, "Couldn't find a room with name $roomName"))
localEventStoreRepository.createBooking(loadingEvent, roomInfo) return mediator.handleBookingCreation(eventInfo, roomInfo)
val response = networkEventRepository.createBooking(loadingEvent, roomInfo)
when (response) {
is Either.Error -> {
localEventStoreRepository.deleteBooking(loadingEvent, roomInfo)
}
is Either.Success -> {
val event = response.data
localEventStoreRepository.updateBooking(event, roomInfo)
}
}
return response
} }
/**
* Updates an existing booking in the specified room.
* Updates the local repository immediately with a loading state,
* then attempts to update the booking in the network repository.
* If the network operation fails, the original event is restored in the local repository.
*
* @param roomName Name of the room where the booking exists
* @param eventInfo Updated information about the event
* @return Either containing the updated event information or an error
*/
override suspend fun updateBooking(roomName: String, eventInfo: EventInfo): Either<ErrorResponse, EventInfo> { override suspend fun updateBooking(roomName: String, eventInfo: EventInfo): Either<ErrorResponse, EventInfo> {
val loadingEvent = eventInfo.copy(isLoading = true)
val oldEvent = localEventStoreRepository.getBooking(eventInfo) as? Either.Success
?: return Either.Error(ErrorResponse(404, "Old event with id ${eventInfo.id} wasn't found"))
val roomInfo = getRoomByName(roomName) val roomInfo = getRoomByName(roomName)
?: return Either.Error(ErrorResponse(404, "Couldn't find a room with name $roomName")) ?: return Either.Error(ErrorResponse(404, "Couldn't find a room with name $roomName"))
localEventStoreRepository.updateBooking(loadingEvent, roomInfo) return mediator.handleBookingUpdate(eventInfo, roomInfo)
val response = networkEventRepository.updateBooking(loadingEvent, roomInfo)
when (response) {
is Either.Error -> {
localEventStoreRepository.updateBooking(oldEvent.data, roomInfo)
}
is Either.Success -> {
val event = response.data
localEventStoreRepository.updateBooking(event, roomInfo)
}
}
return response
} }
/**
* Deletes an existing booking in the specified room.
* Updates the local repository immediately with a loading state,
* then attempts to delete the booking in the network repository.
* If the network operation fails, the original event is restored in the local repository.
*
* @param roomName Name of the room where the booking exists
* @param eventInfo Information about the event to delete
* @return Either containing a success message or an error
*/
override suspend fun deleteBooking(roomName: String, eventInfo: EventInfo): Either<ErrorResponse, String> { override suspend fun deleteBooking(roomName: String, eventInfo: EventInfo): Either<ErrorResponse, String> {
val loadingEvent = eventInfo.copy(isLoading = true)
val roomInfo = getRoomByName(roomName) val roomInfo = getRoomByName(roomName)
?: return Either.Error(ErrorResponse(404, "Couldn't find a room with name $roomName")) ?: return Either.Error(ErrorResponse(404, "Couldn't find a room with name $roomName"))
localEventStoreRepository.updateBooking(loadingEvent, roomInfo)
val response = networkEventRepository.deleteBooking(loadingEvent, roomInfo)
when (response) {
is Either.Error -> {
localEventStoreRepository.createBooking(eventInfo, roomInfo)
}
is Either.Success -> { return mediator.handleBookingDeletion(eventInfo, roomInfo)
localEventStoreRepository.deleteBooking(loadingEvent, roomInfo)
}
}
return response
} }
/**
* Gets information about all rooms.
* First tries to get the information from the local repository.
* If the local repository has no data, it refreshes the data from the network repository.
*
* @return Either containing room information or an error with saved data
*/
override suspend fun getRoomsInfo(): Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>> { override suspend fun getRoomsInfo(): Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>> {
val roomInfos = localEventStoreRepository.getRoomsInfo() val roomInfos = localEventStoreRepository.getRoomsInfo()
if (roomInfos as? Either.Error != null if (roomInfos as? Either.Error != null
...@@ -114,10 +137,22 @@ class EventManager( ...@@ -114,10 +137,22 @@ class EventManager(
return roomInfos return roomInfos
} }
/**
* Gets the current information about all rooms from the local repository without refreshing from the network.
* This is useful when you need the most recent locally cached data without network latency.
*
* @return Either containing room information or an error with saved data
*/
override suspend fun getCurrentRoomInfos(): Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>> { override suspend fun getCurrentRoomInfos(): Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>> {
return localEventStoreRepository.getRoomsInfo() return localEventStoreRepository.getRoomsInfo()
} }
/**
* Gets the names of all available rooms.
* If no rooms are available, returns a list with the default room name.
*
* @return List of room names
*/
override suspend fun getRoomNames(): List<String> { override suspend fun getRoomNames(): List<String> {
val rooms = getRoomsInfo().unbox( val rooms = getRoomsInfo().unbox(
errorHandler = { it.saveData } errorHandler = { it.saveData }
...@@ -125,6 +160,12 @@ class EventManager( ...@@ -125,6 +160,12 @@ class EventManager(
return rooms?.map { it.name } ?: listOf(RoomInfo.defaultValue.name) return rooms?.map { it.name } ?: listOf(RoomInfo.defaultValue.name)
} }
/**
* Gets information about a specific room by its name.
*
* @param roomName Name of the room to get information about
* @return Room information or null if the room is not found
*/
override suspend fun getRoomByName(roomName: String): RoomInfo? { override suspend fun getRoomByName(roomName: String): RoomInfo? {
val rooms = localEventStoreRepository.getRoomsInfo().unbox( val rooms = localEventStoreRepository.getRoomsInfo().unbox(
errorHandler = { it.saveData } errorHandler = { it.saveData }
...@@ -132,4 +173,4 @@ class EventManager( ...@@ -132,4 +173,4 @@ class EventManager(
val room = rooms?.firstOrNull { it.name == roomName } val room = rooms?.firstOrNull { it.name == roomName }
return room return room
} }
} }
\ No newline at end of file
...@@ -38,25 +38,35 @@ class NetworkEventRepository( ...@@ -38,25 +38,35 @@ class NetworkEventRepository(
private val scope = CoroutineScope(Dispatchers.IO) private val scope = CoroutineScope(Dispatchers.IO)
/**
* Gets information about all rooms with their bookings.
* Rounds the current time down to the nearest 15-minute interval for the start time,
* and sets the end time to 14 days from now.
*
* @return Either containing room information or an error with saved data
*/
override suspend fun getRoomsInfo(): Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>> { override suspend fun getRoomsInfo(): Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>> {
val now = clock.now().toLocalDateTime(timeZone) // Get current time
val minutes = now.minute val now = clock.now()
val excess = minutes % 15 + 1 val nowLocalDateTime = now.toLocalDateTime(timeZone)
val adjustedNow = now
.toInstant(timeZone) // Round down to nearest 15-minute interval
.minus(excess, DateTimeUnit.MINUTE) val minutes = nowLocalDateTime.minute
.toLocalDateTime(timeZone) val roundedMinutes = (minutes / 15) * 15
// Create rounded start time
val roundedStart = LocalDateTime( val roundedStart = LocalDateTime(
year = adjustedNow.year, year = nowLocalDateTime.year,
hour = adjustedNow.hour, month = nowLocalDateTime.month, // Month is 1-based in constructor
minute = adjustedNow.minute, dayOfMonth = nowLocalDateTime.dayOfMonth,
hour = nowLocalDateTime.hour,
minute = roundedMinutes,
second = 0, second = 0,
nanosecond = 0, nanosecond = 0
monthNumber = adjustedNow.month.ordinal,
dayOfMonth = adjustedNow.dayOfMonth
) )
val finish = now.toInstant(timeZone).plus(14, DateTimeUnit.DAY, timeZone) // Set end time to 14 days from now
val finish = now.plus(14, DateTimeUnit.DAY, timeZone)
val response = workspaceApi.getWorkspacesWithBookings( val response = workspaceApi.getWorkspacesWithBookings(
tag = "meeting", tag = "meeting",
...@@ -102,7 +112,17 @@ class NetworkEventRepository( ...@@ -102,7 +112,17 @@ class NetworkEventRepository(
override fun subscribeOnUpdates(): Flow<Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>>> = override fun subscribeOnUpdates(): Flow<Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>>> =
api.subscribeOnBookingsList("", scope) api.subscribeOnBookingsList("", scope)
.map { Either.Success(emptyList()) } .map { response ->
when (response) {
is Either.Error -> Either.Error(ErrorWithData(response.error, null))
is Either.Success -> {
// When we receive booking updates, fetch the latest room information
// This is a workaround since we can't directly convert BookingResponseDTO to RoomInfo
val roomsInfo = runCatching { getRoomsInfo() }.getOrNull()
roomsInfo ?: Either.Success(emptyList())
}
}
}
/** Map domain model to DTO */ /** Map domain model to DTO */
private fun EventInfo.toBookingRequestDTO(room: RoomInfo): BookingRequestDTO = BookingRequestDTO( private fun EventInfo.toBookingRequestDTO(room: RoomInfo): BookingRequestDTO = BookingRequestDTO(
...@@ -132,4 +152,4 @@ class NetworkEventRepository( ...@@ -132,4 +152,4 @@ class NetworkEventRepository(
currentEvent = null, currentEvent = null,
id = id id = id
) )
} }
\ No newline at end of file
package band.effective.office.tablet.core.domain.model package band.effective.office.tablet.core.domain.model
import band.effective.office.tablet.core.domain.util.currentLocalDateTime import band.effective.office.tablet.core.domain.util.currentLocalDateTime
import kotlin.time.Clock
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
...@@ -13,8 +10,12 @@ data class EventInfo( ...@@ -13,8 +10,12 @@ data class EventInfo(
val finishTime: LocalDateTime, val finishTime: LocalDateTime,
val organizer: Organizer, val organizer: Organizer,
val id: String, val id: String,
var isLoading: Boolean, val isLoading: Boolean,
) { ) {
init {
// Validate that start time is before finish time
require(startTime <= finishTime) { "Start time must be before or equal to finish time" }
}
companion object { companion object {
const val defaultId: String = "" const val defaultId: String = ""
......
package band.effective.office.tablet.core.domain.repository
import band.effective.office.tablet.core.domain.Either
import band.effective.office.tablet.core.domain.ErrorResponse
import band.effective.office.tablet.core.domain.ErrorWithData
import band.effective.office.tablet.core.domain.model.EventInfo
import band.effective.office.tablet.core.domain.model.RoomInfo
/**
* Mediator interface for coordinating operations between network and local repositories.
* This interface abstracts the coordination logic, reducing tight coupling between repositories.
*/
interface EventRepositoryMediator {
/**
* Synchronizes data between network and local repositories.
* @return Either containing the synchronized room information or an error with saved data
*/
suspend fun synchronizeData(): Either<ErrorWithData<List<RoomInfo>>, List<RoomInfo>>
/**
* Handles booking creation, coordinating between network and local repositories.
* @param eventInfo Information about the event to create
* @param room Information about the room to book
* @return Either containing the created event information or an error
*/
suspend fun handleBookingCreation(
eventInfo: EventInfo,
room: RoomInfo
): Either<ErrorResponse, EventInfo>
/**
* Handles booking update, coordinating between network and local repositories.
* @param eventInfo Updated information about the event
* @param room Information about the room where the booking exists
* @return Either containing the updated event information or an error
*/
suspend fun handleBookingUpdate(
eventInfo: EventInfo,
room: RoomInfo
): Either<ErrorResponse, EventInfo>
/**
* Handles booking deletion, coordinating between network and local repositories.
* @param eventInfo Information about the event to delete
* @param room Information about the room where the booking exists
* @return Either containing a success message or an error
*/
suspend fun handleBookingDeletion(
eventInfo: EventInfo,
room: RoomInfo
): Either<ErrorResponse, String>
}
\ No newline at end of file
...@@ -3,7 +3,6 @@ package band.effective.office.tablet.core.domain.useCase ...@@ -3,7 +3,6 @@ package band.effective.office.tablet.core.domain.useCase
import band.effective.office.tablet.core.domain.unbox import band.effective.office.tablet.core.domain.unbox
import band.effective.office.tablet.core.domain.util.asInstant import band.effective.office.tablet.core.domain.util.asInstant
import band.effective.office.tablet.core.domain.util.currentInstant import band.effective.office.tablet.core.domain.util.currentInstant
import io.github.aakira.napier.Napier
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlin.time.Duration import kotlin.time.Duration
...@@ -14,6 +13,10 @@ class UpdateUseCase( ...@@ -14,6 +13,10 @@ class UpdateUseCase(
private val timerUseCase: TimerUseCase, private val timerUseCase: TimerUseCase,
private val roomInfoUseCase: RoomInfoUseCase, private val roomInfoUseCase: RoomInfoUseCase,
) { ) {
companion object {
/** Default delay duration when no events are scheduled or when calculated delay is invalid */
private val DEFAULT_DELAY = 1.minutes
}
/** Flow for update when start/finish event in room */ /** Flow for update when start/finish event in room */
fun updateFlow() = flow { fun updateFlow() = flow {
...@@ -30,7 +33,7 @@ class UpdateUseCase( ...@@ -30,7 +33,7 @@ class UpdateUseCase(
?.let { event -> ?.let { event ->
val eventInstant = event.startTime.asInstant val eventInstant = event.startTime.asInstant
eventInstant - currentInstant eventInstant - currentInstant
} ?: 1.minutes } ?: DEFAULT_DELAY
val timeToFinishCurrentEvent = roomsInfoList val timeToFinishCurrentEvent = roomsInfoList
.mapNotNull { it.currentEvent } .mapNotNull { it.currentEvent }
...@@ -38,18 +41,19 @@ class UpdateUseCase( ...@@ -38,18 +41,19 @@ class UpdateUseCase(
?.let { event -> ?.let { event ->
val finishInstant = event.finishTime.asInstant val finishInstant = event.finishTime.asInstant
finishInstant - currentInstant finishInstant - currentInstant
} ?: 1.minutes } ?: DEFAULT_DELAY
val minDelay = min(timeToStartNextEvent, timeToFinishCurrentEvent) val minDelay = min(timeToStartNextEvent, timeToFinishCurrentEvent)
val delay = if (minDelay.isNegative()) 1.minutes else minDelay // Ensure delay is always positive to prevent too frequent updates
val delay = if (minDelay.isNegative() || minDelay.inWholeMilliseconds == 0L) DEFAULT_DELAY else minDelay
timerUseCase.timerFlow(delay).first().apply { emit(0) } timerUseCase.timerFlow(delay).first().apply { emit(0) }
} else { } else {
timerUseCase.timerFlow(1.minutes).first().apply { emit(0) } timerUseCase.timerFlow(DEFAULT_DELAY).first().apply { emit(0) }
} }
} }
} }
private fun min(first: Duration, second: Duration): Duration = private fun min(first: Duration, second: Duration): Duration =
if (first < second) first else second if (first < second) first else second
} }
\ No newline at end of file
...@@ -117,26 +117,27 @@ class BookingEditorComponent( ...@@ -117,26 +117,27 @@ class BookingEditorComponent(
*/ */
private fun updateExistingEvent() = coroutineScope.launch { private fun updateExistingEvent() = coroutineScope.launch {
mutableState.update { it.copy(isLoadUpdate = true) } mutableState.update { it.copy(isLoadUpdate = true) }
withContext(Dispatchers.IO) { val updateBookingResult = withContext(Dispatchers.IO) {
updateBookingUseCase( updateBookingUseCase(
roomName = roomName, roomName = roomName,
eventInfo = stateToEventInfoMapper.map(state.value) eventInfo = stateToEventInfoMapper.map(state.value)
).unbox(
errorHandler = {
Napier.d { "Update booking failed: ${it.description}" }
mutableState.update {
it.copy(
isLoadUpdate = false,
isErrorUpdate = true
)
}
},
successHandler = {
mutableState.update { it.copy(isLoadUpdate = false) }
onCloseRequest()
}
) )
} }
updateBookingResult.unbox(
errorHandler = {
Napier.d { "Update booking failed: ${it.description}" }
mutableState.update {
it.copy(
isLoadUpdate = false,
isErrorUpdate = true
)
}
},
successHandler = {
mutableState.update { it.copy(isLoadUpdate = false) }
onCloseRequest()
}
)
} }
/** /**
...@@ -368,22 +369,23 @@ class BookingEditorComponent( ...@@ -368,22 +369,23 @@ class BookingEditorComponent(
private fun createNewEvent() = coroutineScope.launch { private fun createNewEvent() = coroutineScope.launch {
mutableState.update { it.copy(isLoadCreate = true) } mutableState.update { it.copy(isLoadCreate = true) }
val eventToCreate = stateToEventInfoMapper.map(state.value) val eventToCreate = stateToEventInfoMapper.map(state.value)
withContext(Dispatchers.IO) { val createBookingResult = withContext(Dispatchers.IO) {
createBookingUseCase(roomName = roomName, eventInfo = eventToCreate).unbox( createBookingUseCase(roomName = roomName, eventInfo = eventToCreate)
errorHandler = {
mutableState.update {
it.copy(
isLoadCreate = false,
isErrorCreate = true,
)
}
},
successHandler = {
mutableState.update { it.copy(isLoadCreate = false) }
onCloseRequest()
}
)
} }
createBookingResult.unbox(
errorHandler = {
mutableState.update {
it.copy(
isLoadCreate = false,
isErrorCreate = true,
)
}
},
successHandler = {
mutableState.update { it.copy(isLoadCreate = false) }
onCloseRequest()
}
)
} }
/** /**
......
...@@ -64,7 +64,7 @@ class DateTimePickerComponent( ...@@ -64,7 +64,7 @@ class DateTimePickerComponent(
val currentDate = state.value.currentDate val currentDate = state.value.currentDate
val newDate = LocalDateTime( val newDate = LocalDateTime(
year = year, year = year,
monthNumber = month.ordinal, month = month,
dayOfMonth = dayOfMonth, dayOfMonth = dayOfMonth,
hour = currentDate.hour, hour = currentDate.hour,
minute = currentDate.minute, minute = currentDate.minute,
...@@ -83,7 +83,7 @@ class DateTimePickerComponent( ...@@ -83,7 +83,7 @@ class DateTimePickerComponent(
val currentDate = state.value.currentDate val currentDate = state.value.currentDate
val newDate = LocalDateTime( val newDate = LocalDateTime(
year = currentDate.year, year = currentDate.year,
monthNumber = currentDate.month.ordinal, month = currentDate.month,
dayOfMonth = currentDate.dayOfMonth, dayOfMonth = currentDate.dayOfMonth,
hour = hour, hour = hour,
minute = minute, minute = minute,
......
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
version = "1.3">
<BuildAction>
<BuildActionEntries>
<BuildActionEntry
buildForRunning = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A93A953629CC810C00F8E227"
BuildableName = "BookingApp.app"
BlueprintName = "iosApp"
ReferencedContainer = "container:iosApp.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<LaunchAction
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A93A953629CC810C00F8E227"
BuildableName = "BookingApp.app"
BlueprintName = "iosApp"
ReferencedContainer = "container:iosApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<LocationScenarioReference
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
referenceType = "1">
</LocationScenarioReference>
</LaunchAction>
</Scheme>
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать