Skip to content

Commit

Permalink
Removed (temporarily?) pagination of medicines from pharmacy informat…
Browse files Browse the repository at this point in the history
…ion page. Medicine stock updates with real time updates across multiple devices.
  • Loading branch information
Nyckoka committed May 9, 2024
1 parent a0985de commit 1e41220
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,16 @@ class PharmacistApplication : DependenciesContainer, Application() {

companion object {
const val MEDICINE_NOTIFICATION_CHANNEL = "MedicineNotifications"
const val API_ENDPOINT = "https://pharmacist-e9t4.onrender.com"

const val API_ENDPOINT_TYPE = "ngrok"
val API_ENDPOINT = when (API_ENDPOINT_TYPE) {
"localhost" -> "http://10.0.2.2:8080"
"ngrok" -> "https://2b02-2001-818-e871-b700-c937-8172-33bf-a88.ngrok-free.app"
"render" -> "https://pharmacist-e9t4.onrender.com"
else -> {
throw IllegalStateException("Invalid API_ENDPOINT_TYPE")
}
}
const val TAG = "PharmacistApp"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class RealTimeUpdatesService(
onMedicineNotification: (RealTimeUpdateMedicineNotificationPublishingData) -> Unit = {}
) {
updatePublishFlow.collect { realTimeUpdateDto ->
// Log.d(TAG, "Received real time update: ${realTimeUpdateDto.type}")
Log.d(TAG, "Received real time update: ${realTimeUpdateDto.type}")

when (val realTimeUpdateClass = RTU.getType(realTimeUpdateDto.type)) {
RTU.NEW_PHARMACY -> onNewPharmacy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class PharmacyActivity : PharmacistActivity() {
PharmacyScreen(
pharmacy = viewModel.pharmacy,
loadingState = viewModel.loadingState,
medicinesState = viewModel.medicinesState,
medicinesList = viewModel.medicinesList,
onMedicineClick = { medicineId ->
MedicineActivity.navigate(this, medicineId)
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package pt.ulisboa.ist.pharmacist.ui.screens.pharmacy

import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import pt.ulisboa.ist.pharmacist.service.http.connection.isSuccess
import pt.ulisboa.ist.pharmacist.service.http.services.pharmacies.PharmaciesService
import pt.ulisboa.ist.pharmacist.service.http.services.pharmacies.models.listAvailableMedicines.MedicineStockModel
import pt.ulisboa.ist.pharmacist.service.real_time_updates.RealTimeUpdateSubscription
import pt.ulisboa.ist.pharmacist.service.real_time_updates.RealTimeUpdatesService
import kotlin.math.max

class PharmacyMedicinesPagingSource(
private val pharmaciesService: PharmaciesService,
private val realTimeUpdatesService: RealTimeUpdatesService,
private val pid: Long
) : PagingSource<Int, MedicineStockModel>() {

Expand All @@ -25,13 +29,25 @@ class PharmacyMedicinesPagingSource(
val offset = params.key ?: STARTING_KEY
val limit = params.loadSize

Log.d(
"RealTimeUpdatesService",
"Loading medicines for pharmacy $pid with offset $offset and limit $limit"
)
val result = pharmaciesService.listAvailableMedicines(
pharmacyId = pid,
limit = limit.toLong(),
offset = offset.toLong()
)

return if (result.isSuccess()) {
realTimeUpdatesService.subscribeToUpdates(
result.data.medicines.map {
RealTimeUpdateSubscription.pharmacyMedicineStock(
pharmacyId = pid,
medicineId = it.medicine.medicineId
)
}
)
LoadResult.Page(
data = result.data.medicines,
prevKey = when (offset) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.HorizontalPagerIndicator
Expand All @@ -38,7 +37,6 @@ import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.MapUiSettings
import com.google.maps.android.compose.Marker
import com.google.maps.android.compose.MarkerState
import kotlinx.coroutines.flow.Flow
import pt.ulisboa.ist.pharmacist.R
import pt.ulisboa.ist.pharmacist.service.http.services.pharmacies.models.getPharmacyById.PharmacyWithUserDataModel
import pt.ulisboa.ist.pharmacist.service.http.services.pharmacies.models.listAvailableMedicines.MedicineStockModel
Expand All @@ -61,7 +59,7 @@ import pt.ulisboa.ist.pharmacist.ui.theme.Favorite
fun PharmacyScreen(
pharmacy: PharmacyWithUserDataModel?,
loadingState: PharmacyViewModel.PharmacyLoadingState,
medicinesState: Flow<PagingData<MedicineStockModel>>,
medicinesList: SnapshotStateMap<Long, MedicineStockModel>,
onMedicineClick: (Long) -> Unit,
onAddMedicineClick: () -> Unit,
onAddStockClick: (Long) -> Unit,
Expand All @@ -71,7 +69,7 @@ fun PharmacyScreen(
onShareClick: () -> Unit,
onRatingChanged: (Int) -> Unit
) {
val medicinesStock = medicinesState.collectAsLazyPagingItems()
val medicinesStock = medicinesList.values.toList()

val pagerState = rememberPagerState(initialPage = 0)

Expand Down Expand Up @@ -158,9 +156,9 @@ fun PharmacyScreen(
)

Text(
text = "${medicinesStock.itemCount} " +
text = "${medicinesStock.size} " +
stringResource(
if (medicinesStock.itemCount != 1)
if (medicinesStock.size != 1)
R.string.medicines_available
else R.string.medicine_available
),
Expand All @@ -180,11 +178,8 @@ fun PharmacyScreen(
modifier = Modifier
.padding(20.dp)
) {
items(
medicinesStock.itemCount
//,key = medicinesStock.itemKey { it.medicine.medicineId }
) { index ->
val (medicine, stock) = medicinesStock[index]!!
items(medicinesStock.size) { index ->
val (medicine, stock) = medicinesStock[index]
PharmacyMedicineEntry(
medicine,
stock,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
package pt.ulisboa.ist.pharmacist.ui.screens.pharmacy

import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
import pt.ulisboa.ist.pharmacist.service.http.PharmacistService
import pt.ulisboa.ist.pharmacist.service.http.connection.isSuccess
Expand All @@ -23,7 +17,6 @@ import pt.ulisboa.ist.pharmacist.service.real_time_updates.RealTimeUpdateSubscri
import pt.ulisboa.ist.pharmacist.service.real_time_updates.RealTimeUpdatesService
import pt.ulisboa.ist.pharmacist.session.SessionManager
import pt.ulisboa.ist.pharmacist.ui.screens.PharmacistViewModel
import pt.ulisboa.ist.pharmacist.ui.screens.pharmacy.PharmacyViewModel.ModificationEvent.StockModificationEvent
import pt.ulisboa.ist.pharmacist.ui.screens.pharmacy.PharmacyViewModel.PharmacyLoadingState.LOADED
import pt.ulisboa.ist.pharmacist.ui.screens.pharmacy.PharmacyViewModel.PharmacyLoadingState.LOADING
import pt.ulisboa.ist.pharmacist.ui.screens.pharmacy.PharmacyViewModel.PharmacyLoadingState.NOT_LOADED
Expand All @@ -40,45 +33,82 @@ class PharmacyViewModel(
pharmacistService: PharmacistService,
sessionManager: SessionManager,
private val realTimeUpdatesService: RealTimeUpdatesService,
pharmacyId: Long
val pharmacyId: Long
) : PharmacistViewModel(pharmacistService, sessionManager) {
var loadingState by mutableStateOf(NOT_LOADED)
private set

var pharmacy by mutableStateOf<PharmacyWithUserDataModel?>(null)
private set

private val modificationEvents = MutableStateFlow<List<ModificationEvent>>(emptyList())

private val newMedicineFlow = MutableStateFlow<Long?>(null)

@OptIn(ExperimentalCoroutinesApi::class)
private val _medicinesState = newMedicineFlow.flatMapLatest {
Pager(
config = PagingConfig(
pageSize = PAGE_SIZE,
prefetchDistance = PREFETCH_DISTANCE,
enablePlaceholders = false
),
pagingSourceFactory = {
PharmacyMedicinesPagingSource(
pharmaciesService = pharmacistService.pharmaciesService,
pid = pharmacyId
)
//private val modificationEvents = MutableSharedFlow<ModificationEvent>()

//private val newMedicineFlow = MutableStateFlow<Long?>(null)

val medicinesList = mutableStateMapOf<Long, MedicineStockModel>()

/*@OptIn(ExperimentalCoroutinesApi::class)
private val _medicinesState = newMedicineFlow.flatMapLatest {
Pager(
config = PagingConfig(
pageSize = PAGE_SIZE,
prefetchDistance = PREFETCH_DISTANCE,
enablePlaceholders = false
),
pagingSourceFactory = {
PharmacyMedicinesPagingSource(
pharmaciesService = pharmacistService.pharmaciesService,
realTimeUpdatesService = realTimeUpdatesService,
pid = pharmacyId
)
}
).flow.cachedIn(viewModelScope)
.combine(modificationEvents) { pagingData, modificationEvents ->
modificationEvents.fold(pagingData) { pagingDataAcc, modificationEvent ->
applyModificationEvent(pagingDataAcc, modificationEvent)
}
}
}*/

private fun fetchAllMedicines() = viewModelScope.launch {
if (pharmacy == null) return@launch

var offset = 0L
val limit = 50L
while (true) {
Log.d(
"RealTimeUpdatesService",
"fetchAllMedicines - Fetching medicines for pharmacy ${pharmacy!!.pharmacy.pharmacyId}" +
" with offset $offset and limit $limit"
)
val result = pharmacistService.pharmaciesService.listAvailableMedicines(
pharmacyId = pharmacyId,
limit = limit,
offset = offset
)
if (!result.isSuccess() || result.data.medicines.isEmpty()) break

for (medicine in result.data.medicines) {
medicinesList[medicine.medicine.medicineId] = medicine
}
).flow.cachedIn(viewModelScope)
.combine(modificationEvents) { pagingData, modificationEvents ->
modificationEvents.fold(pagingData) { pagingDataAcc, modificationEvent ->
applyModificationEvent(pagingDataAcc, modificationEvent)
realTimeUpdatesService.subscribeToUpdates(
result.data.medicines.map {
RealTimeUpdateSubscription.pharmacyMedicineStock(
pharmacyId = pharmacyId,
medicineId = it.medicine.medicineId
)
}
}
)
offset += limit
}
}

private fun applyModificationEvent(
/*private fun applyModificationEvent(
pagingData: PagingData<MedicineStockModel>,
modificationEvent: ModificationEvent
): PagingData<MedicineStockModel> = when (modificationEvent) {
is StockModificationEvent -> {
Log.d("RealTimeUpdatesService", "Applying stock modification event: $modificationEvent")
pagingData.map {
if (it.medicine.medicineId != modificationEvent.medicineId)
return@map it
Expand All @@ -91,9 +121,9 @@ class PharmacyViewModel(
)
}
}
}
}*/

val medicinesState get() = _medicinesState
//val medicinesState get() = _medicinesState

fun listenForRealTimeUpdates() = viewModelScope.launch {
realTimeUpdatesService.listenForRealTimeUpdates(
Expand All @@ -117,6 +147,15 @@ class PharmacyViewModel(
pharmacy =
pharmacy?.copy(userMarkedAsFavorite = pharmacyUserFavoritedData.favorited)
},
onMedicineStock = { medicineStockData ->
Log.d(
"RealTimeUpdatesService",
"Received medicine stock update: $medicineStockData"
)
medicinesList.compute(medicineStockData.medicineId) { _, medicineStock ->
medicineStock?.copy(stock = medicineStockData.stock)
}
}
)
}

Expand All @@ -139,6 +178,7 @@ class PharmacyViewModel(
RealTimeUpdateSubscription.pharmacyUserFavorited(pharmacyId)
)
)
fetchAllMedicines()
}

loadingState = LOADED
Expand Down Expand Up @@ -205,27 +245,41 @@ class PharmacyViewModel(
quantity: Long = 1
) = pharmacy?.let {
viewModelScope.launch {
medicinesList.compute(medicineId) { _, medicineStock ->
medicineStock?.copy(
stock = medicineStock.stock + when (operation) {
MedicineStockOperation.ADD -> quantity
MedicineStockOperation.REMOVE -> -quantity
}
)
}
val result = pharmacistService.pharmaciesService.changeMedicineStock(
pharmacyId = it.pharmacy.pharmacyId,
medicineId = medicineId,
operation = operation,
stock = quantity
)
}
}

fun onMedicineAdded(medicineId: Long, quantity: Long) {
viewModelScope.launch {
val result = pharmacistService.medicinesService.getMedicineById(medicineId)

if (result.isSuccess()) {
modificationEvents.value += StockModificationEvent(
medicineId,
operation,
quantity
medicinesList[medicineId] = MedicineStockModel(
medicine = result.data.medicine,
stock = quantity
)
realTimeUpdatesService.subscribeToUpdates(
listOf(
RealTimeUpdateSubscription.pharmacyMedicineStock(pharmacyId, medicineId)
)
)
}
}
}

fun onMedicineAdded(medicineId: Long, quantity: Long) {
viewModelScope.launch { newMedicineFlow.emit(medicineId) }
}


enum class PharmacyLoadingState {
NOT_LOADED,
Expand Down

0 comments on commit 1e41220

Please sign in to comment.