Advertisement
Guest User

Untitled

a guest
Feb 19th, 2019
68
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.75 KB | None | 0 0
  1. package com.onurway.ui.main.trip.model
  2.  
  3. import android.content.Context
  4. import androidx.lifecycle.LiveData
  5. import androidx.lifecycle.MutableLiveData
  6. import com.google.android.gms.maps.model.LatLng
  7. import com.onurway.R
  8. import com.onurway.shared.data.address.data.remote.model.AddressDataModel
  9. import com.onurway.shared.data.remote.Result
  10. import com.onurway.shared.data.ticket.data.remote.model.TicketResponse
  11. import com.onurway.shared.data.trip.data.TripRepository
  12. import com.onurway.shared.data.trip.data.model.NodeDataModel
  13. import com.onurway.shared.data.trip.data.model.TicketDataModel
  14. import com.onurway.shared.data.trip.data.model.TripDataModel
  15. import com.onurway.shared.data.trip.data.remote.model.TripResponse
  16. import com.onurway.shared.utils.CoroutinesDispatcherProvider
  17. import com.onurway.shared.utils.TimeUtils
  18. import com.onurway.shared.utils.event.Event
  19. import com.onurway.shared.utils.formatAddress
  20. import com.onurway.shared.utils.formatCarTitle
  21. import com.onurway.shared.utils.formatCost
  22. import com.onurway.shared.utils.formatSeats
  23. import com.onurway.shared.utils.formatUserName
  24. import com.onurway.shared.utils.network.ApiCallErrors
  25. import com.onurway.util.BaseViewModel
  26. import kotlinx.coroutines.CoroutineScope
  27. import kotlinx.coroutines.Job
  28. import kotlinx.coroutines.launch
  29. import kotlinx.coroutines.withContext
  30. import kotlin.math.max
  31. import kotlin.math.min
  32.  
  33. class TripViewModel(
  34. private val context: Context,
  35. private val tripRepository: TripRepository,
  36. private val dispatcherProvider: CoroutinesDispatcherProvider
  37. ): BaseViewModel() {
  38. private val parentJob = Job()
  39. private val scope = CoroutineScope(dispatcherProvider.main + parentJob)
  40. private val inflight = mutableMapOf<String, Job>()
  41.  
  42. private val _uiState = MutableLiveData<UiModel>()
  43. val uiState: LiveData<UiModel>
  44. get() = _uiState
  45.  
  46. lateinit var trip: TripDataModel
  47.  
  48. private val _bookingModel = MutableLiveData<BookingModel>()
  49. val bookingModel: LiveData<BookingModel>
  50. get() = _bookingModel
  51.  
  52. fun getRemoteTrip(tripId: Long, searchPickupAddressId: Long, searchDropoffAddressId: Long) {
  53. val id = "REMOTE_TRIP"
  54. if (inflight[id]?.isActive == true) inflight[id]?.cancel()
  55. inflight[id] = launchRemoteTrip(tripId, searchPickupAddressId, searchDropoffAddressId)
  56. }
  57.  
  58. fun book() {
  59. val id = "BOOK_TRIP"
  60. if (inflight[id]?.isActive == true) inflight[id]?.cancel()
  61. inflight[id] = launchBookTrip()
  62. }
  63.  
  64. private fun launchRemoteTrip(tripId: Long, searchPickupAddressId: Long, searchDropoffAddressId: Long): Job {
  65. return scope.launch(dispatcherProvider.io) {
  66. val result = tripRepository.remoteTrip(tripId)
  67. withContext(dispatcherProvider.main) {
  68. when (result) {
  69. is Result.Success -> {
  70. val document = result.data
  71. trip = TripDataModel.fromTripResponse(
  72. document,
  73. document.asObjectDocument<TripResponse>().get(), searchPickupAddressId, searchDropoffAddressId
  74. )
  75. emitUiState(showSuccess = TripViewDataModel.fromTripDataModel(context, trip))
  76. }
  77. is Result.Error -> {
  78. emitUiState(
  79. showError = Event(nonApiErrorMessage(result, message = R.string.generic_network_error))
  80. )
  81. }
  82. is Result.ApiError -> {
  83. emitUiState(showApiErrors = Event(result.apiErrors))
  84. }
  85. }
  86. }
  87. }
  88. }
  89.  
  90. private fun launchBookTrip(): Job {
  91. return scope.launch(dispatcherProvider.io) {
  92. val ticket = bookingModel.value?.toTicketDataModel()
  93. ticket ?: return@launch
  94. val result = tripRepository.bookTrip(trip.id, ticket)
  95. withContext(dispatcherProvider.main) {
  96. when (result) {
  97. is Result.Success -> {
  98. emitUiState(showBookingSuccess = Event(result.data))
  99. }
  100. is Result.Error -> {
  101. emitUiState(
  102. showError = Event(nonApiErrorMessage(result, message = R.string.generic_network_error))
  103. )
  104. }
  105. is Result.ApiError -> {
  106. emitUiState(showApiErrors = Event(result.apiErrors))
  107. }
  108. }
  109. }
  110. }
  111. }
  112.  
  113. fun isTripInitialized() = ::trip.isInitialized
  114.  
  115. fun isBookingPickupPointPickerRequired() =
  116. trip.essentialPickupPoint.serving == true || trip.hasMultipleNodesInPickupPointCity()
  117.  
  118. fun isBookingDropoffPointPickerRequired() =
  119. trip.essentialDropoffPoint.serving == true || trip.hasMultipleNodesInDropoffPointCity()
  120.  
  121. fun initBookingModel() {
  122. _bookingModel.value = BookingModel.fromTrip(trip)
  123. }
  124.  
  125. fun isUserBooked(userId: Long) = trip.tickets.any { it.passengerId == userId }
  126.  
  127. fun initBookingPickupCords(): LatLng? {
  128. val userAddress = bookingModel.value?.userPickupAddress
  129. var lat = userAddress?.latitude
  130. var lng = userAddress?.longitude
  131. return if (lat != null && lng != null)
  132. LatLng(lat, lng)
  133. else {
  134. val tripPickupAddress = trip.essentialPickupPoint
  135. lat = tripPickupAddress.addressLatitude
  136. lng = tripPickupAddress.addressLongitude
  137. if (lat != null && lng != null)
  138. LatLng(lat, lng)
  139. else null
  140. }
  141. }
  142.  
  143. fun initBookingDropoffCords(): LatLng? {
  144. val userAddress = bookingModel.value?.userDropoffAddress
  145. var lat = userAddress?.latitude
  146. var lng = userAddress?.longitude
  147. return if (lat != null && lng != null)
  148. LatLng(lat, lng)
  149. else {
  150. val tripDropoffAddress = trip.essentialDropoffPoint
  151. lat = tripDropoffAddress.addressLatitude
  152. lng = tripDropoffAddress.addressLongitude
  153. if (lat != null && lng != null)
  154. LatLng(lat, lng)
  155. else null
  156. }
  157. }
  158.  
  159. fun defaultLatLng() = LatLng(34.023882, 36.728373)
  160.  
  161. fun onBookingSeatsChanged(newSeats: Int) {
  162. val booking = _bookingModel.value
  163. if (newSeats > 0 && newSeats <= (trip.availableSeats ?: 1)) booking?.seats = newSeats
  164. _bookingModel.value = booking
  165. }
  166.  
  167. fun onBookingPickupPointChanged(id: Long) {
  168. val booking = _bookingModel.value
  169. val newPickupPoint = trip.nodes.find { it.id == id } ?: return
  170. booking?.tripPickupPoint = newPickupPoint
  171. _bookingModel.value = booking
  172. }
  173.  
  174. fun onBookingDropoffPointChanged(id: Long) {
  175. val booking = _bookingModel.value
  176. val newDropoffPoint = trip.nodes.find { it.id == id } ?: return
  177. booking?.tripDropoffPoint = newDropoffPoint
  178. _bookingModel.value = booking
  179. }
  180.  
  181. fun onUserPickupPointChanged(
  182. location: String? = bookingModel.value?.userPickupAddress?.location,
  183. latitude: Double? = bookingModel.value?.userPickupAddress?.latitude,
  184. longitude: Double? = bookingModel.value?.userPickupAddress?.longitude
  185. ) {
  186. val booking = _bookingModel.value
  187. booking?.updateUserPickupAddress(trip.essentialPickupPoint.addressCity, location, latitude, longitude)
  188. _bookingModel.value = booking
  189. }
  190.  
  191. fun onUserDropoffPointChanged(
  192. location: String? = bookingModel.value?.userDropoffAddress?.location,
  193. latitude: Double? = bookingModel.value?.userDropoffAddress?.latitude,
  194. longitude: Double? = bookingModel.value?.userDropoffAddress?.longitude
  195. ) {
  196. val booking = _bookingModel.value
  197. booking?.updateUserDropoffAddress(trip.essentialDropoffPoint.addressCity, location, latitude, longitude)
  198. _bookingModel.value = booking
  199. }
  200.  
  201. data class UiModel(
  202. val showSuccess: TripViewDataModel? = null,
  203. val showBookingSuccess: Event<TicketResponse>? = null,
  204. val showProgress: Boolean,
  205. val showError: Event<Int>? = null,
  206. val showApiErrors: Event<ApiCallErrors>? = null
  207. ) {
  208. fun hasBookingGenericErrors() =
  209. showApiErrors?.peek()?.filterOutBySources(
  210. true, *TripViewModel.BOOKING_ERROR_ATTRIBUTES
  211. )?.isNotEmpty() ?: false
  212.  
  213. // TODO: cache errors (filtering is called twice in both hasGenericErrors and genericErrors)
  214. fun bookingGenericErrors() =
  215. showApiErrors
  216. ?.peek()
  217. ?.filterOutBySources(true, *TripViewModel.BOOKING_ERROR_ATTRIBUTES)
  218. ?.joinToString(separator = "\n") { it.title }
  219.  
  220. fun hasBookingFieldErrors(field: String) =
  221. showApiErrors?.peek()?.filterBySources(true, field)?.isNotEmpty() ?: false
  222.  
  223. fun bookingFieldErrors(field: String) =
  224. showApiErrors
  225. ?.peek()
  226. ?.filterBySources(true, field)
  227. ?.joinToString(separator = "\n") { it.title }
  228. }
  229.  
  230. private fun emitUiState(
  231. showSuccess: TripViewDataModel? = null,
  232. showBookingSuccess: Event<TicketResponse>? = null,
  233. showProgress: Boolean = false,
  234. showError: Event<Int>? = null,
  235. showApiErrors: Event<ApiCallErrors>? = null
  236. ) {
  237. val uiModel = UiModel(
  238. showSuccess,
  239. showBookingSuccess,
  240. showProgress,
  241. showError,
  242. showApiErrors
  243. )
  244. _uiState.value = uiModel
  245. }
  246.  
  247. data class TripViewDataModel(
  248. val pickupDate: String?,
  249. val cost: String,
  250. val availableSeats: String?,
  251. val details: String?,
  252. val backSeatsForFemales: Boolean?,
  253. val driverFullName: String?,
  254. val driverPictureUrl: String?,
  255. val carTitle: String?,
  256. val carColor: String?,
  257. val nodes: List<NodeViewDataModel>,
  258. val tickets: List<TicketViewDataModel>
  259. ) {
  260. companion object {
  261. fun fromTripDataModel(context: Context, tripDataModel: TripDataModel): TripViewDataModel {
  262. val separator = context.getString(R.string.address_separator)
  263. return TripViewDataModel(
  264. pickupDate= tripDataModel.pickupDate?.let { TimeUtils.formatLocalDate(it, "EEEE dd LLLL") },
  265. cost = formatCost(tripDataModel.cost, context.getString(R.string.trip_money_unit))!!,
  266. availableSeats = formatSeats(tripDataModel.availableSeats, context.getString(R.string.trip_seats_available)),
  267. details = tripDataModel.details,
  268. backSeatsForFemales = tripDataModel.backSeatsForFemales,
  269. driverFullName = formatUserName(tripDataModel.driverFirstName, tripDataModel.driverLastName),
  270. driverPictureUrl = tripDataModel.driverPictureUrl,
  271. carTitle =
  272. formatCarTitle(tripDataModel.carMake, tripDataModel.carModel),
  273. carColor = tripDataModel.carColor,
  274. nodes = tripDataModel.nodes.map {
  275. NodeViewDataModel.fromNodeDataModel(it, separator)
  276. }.apply {
  277. find { it.id == tripDataModel.essentialPickupPoint.id }?.isEssentialPickupPoint = true
  278. find { it.id == tripDataModel.essentialDropoffPoint.id }?.isEssentialDropoffPoint = true
  279. },
  280. tickets = tripDataModel.tickets.map {
  281. TicketViewDataModel.fromTicketDataModel(it, separator)
  282. }
  283. )
  284. }
  285. }
  286. }
  287.  
  288. data class NodeViewDataModel(
  289. val id: Long?,
  290. val pickupAt: String?,
  291. val address: String?,
  292. val serving: Boolean?,
  293. val availableSeats: Int,
  294. val inEssentialPath: Boolean,
  295. var isEssentialPickupPoint: Boolean = false,
  296. var isEssentialDropoffPoint: Boolean = false,
  297. val bookable: Boolean?
  298. ) {
  299. companion object {
  300. fun fromNodeDataModel(nodeDataModel: NodeDataModel, separator: String): NodeViewDataModel {
  301. return NodeViewDataModel(
  302. id = nodeDataModel.id,
  303. pickupAt = nodeDataModel.pickupAt?.let { TimeUtils.formatInstantAsTime(it) },
  304. address = formatAddress(nodeDataModel.addressCity, nodeDataModel.addressLocation, separator),
  305. serving = nodeDataModel.serving,
  306. availableSeats = nodeDataModel.availableSeats,
  307. inEssentialPath = nodeDataModel.inEssentialPath,
  308. bookable = nodeDataModel.bookable
  309. )
  310. }
  311. }
  312. }
  313.  
  314. data class TicketViewDataModel(
  315. val passengerFullName: String?,
  316. val passengerPictureUrl: String?,
  317. val pickupAddress: String?,
  318. val dropoffAddress: String?
  319. ) {
  320. companion object {
  321. fun fromTicketDataModel(ticketDataModel: TicketDataModel, separator: String): TicketViewDataModel {
  322. return TicketViewDataModel(
  323. passengerFullName = formatUserName(ticketDataModel.passengerFirstName, ticketDataModel.passengerLastName),
  324. passengerPictureUrl = ticketDataModel.passengerPictureUrl,
  325. pickupAddress =
  326. formatAddress(ticketDataModel.pickupAddressCity, ticketDataModel.pickupAddressLocation, separator),
  327. dropoffAddress =
  328. formatAddress(ticketDataModel.dropoffAddressCity, ticketDataModel.dropoffAddressLocation, separator)
  329. )
  330. }
  331. }
  332. }
  333.  
  334. data class BookingModel(
  335. var tripPickupPoint: NodeDataModel,
  336. var tripDropoffPoint: NodeDataModel,
  337. var userPickupAddress: AddressDataModel?,
  338. var userDropoffAddress: AddressDataModel?,
  339. var seats: Int
  340. ) {
  341. fun updateUserPickupAddress(city: String?, location: String? = null, latitude: Double? = null, longitude: Double? = null) {
  342. if (userPickupAddress == null)
  343. userPickupAddress = AddressDataModel(
  344. city = city,
  345. location = location,
  346. latitude = latitude,
  347. longitude = longitude
  348. )
  349. else
  350. userPickupAddress?.let {
  351. it.city = city
  352. it.location = location
  353. it.latitude = latitude
  354. it.longitude = longitude
  355. }
  356. }
  357.  
  358. fun updateUserDropoffAddress(city: String?, location: String? = null, latitude: Double? = null, longitude: Double? = null) {
  359. if (userDropoffAddress == null)
  360. userDropoffAddress = AddressDataModel(
  361. city = city,
  362. location = location,
  363. latitude = latitude,
  364. longitude = longitude
  365. )
  366. else
  367. userDropoffAddress?.let {
  368. it.city = city
  369. it.location = location
  370. it.latitude = latitude
  371. it.longitude = longitude
  372. }
  373. }
  374.  
  375. fun availableSeats(trip: TripDataModel): Int {
  376. val pps = trip.allPickupPoints()
  377. val essentialPps = pps.subList(pps.indexOf(tripPickupPoint), pps.size)
  378. val dps = trip.allDropoffPoints()
  379. val essentialDps = dps.subList(0, dps.indexOf(tripDropoffPoint) + 1)
  380. return min(
  381. max(essentialPps.map { it.availableSeats }.min() ?: 1, 1),
  382. max(essentialDps.map { it.availableSeats }.min() ?: 1, 1)
  383. )
  384. }
  385.  
  386. fun toTicketDataModel(): com.onurway.shared.data.ticket.data.remote.model.TicketDataModel? {
  387. val pickUpPointId = tripPickupPoint.id
  388. val dropoffPointId = tripDropoffPoint.id
  389. if (pickUpPointId == null || dropoffPointId == null) return null
  390. return com.onurway.shared.data.ticket.data.remote.model.TicketDataModel(
  391. pickup_point_id = pickUpPointId,
  392. dropoff_point_id = dropoffPointId,
  393. seats = seats,
  394. custom_pickup_point_attributes = userPickupAddress,
  395. custom_dropoff_point_attributes = userDropoffAddress
  396. )
  397. }
  398.  
  399. companion object {
  400. fun fromTrip(trip: TripDataModel): BookingModel {
  401. return BookingModel(
  402. tripPickupPoint = trip.essentialPickupPoint,
  403. tripDropoffPoint = trip.essentialDropoffPoint,
  404. userPickupAddress = null,
  405. userDropoffAddress = null,
  406. seats = 1
  407. )
  408. }
  409. }
  410. }
  411.  
  412. companion object {
  413. const val ERROR_BOOKING_SEATS = "seats"
  414. val BOOKING_ERROR_ATTRIBUTES = arrayOf(ERROR_BOOKING_SEATS)
  415. }
  416. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement