ProviderProxy.kt 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package ml.adamsprogs.bimba
  2. import android.content.Context
  3. import android.content.Intent
  4. import kotlinx.coroutines.*
  5. import kotlinx.coroutines.android.Main
  6. import ml.adamsprogs.bimba.datasources.VmClient
  7. import ml.adamsprogs.bimba.datasources.VmService
  8. import ml.adamsprogs.bimba.models.Departure
  9. import ml.adamsprogs.bimba.models.Plate
  10. import ml.adamsprogs.bimba.models.StopSegment
  11. import ml.adamsprogs.bimba.models.Timetable
  12. import ml.adamsprogs.bimba.models.suggestions.GtfsSuggestion
  13. import ml.adamsprogs.bimba.models.suggestions.StopSuggestion
  14. import java.util.*
  15. import kotlin.collections.HashMap
  16. class ProviderProxy(context: Context? = null) {
  17. private val vmClient = VmClient.getVmClient()
  18. private var timetable: Timetable = Timetable.getTimetable(context)
  19. private var suggestions = emptyList<GtfsSuggestion>()
  20. private val requests = HashMap<String, Request>()
  21. var mode = if (timetable.isEmpty()) MODE_VM else MODE_FULL
  22. companion object {
  23. const val MODE_FULL = "mode_full"
  24. const val MODE_VM = "mode_vm"
  25. }
  26. fun getSuggestions(query: String = "", callback: (List<GtfsSuggestion>) -> Unit) {
  27. GlobalScope.launch {
  28. suggestions = getStopSuggestions(query) //+ getLineSuggestions(query) //todo<p:v+1> + bike stations, train stations, &c
  29. val filtered = filterSuggestions(query)
  30. launch(Dispatchers.Main) {
  31. callback(filtered)
  32. }
  33. }
  34. }
  35. private suspend fun getStopSuggestions(query: String): List<StopSuggestion> {
  36. val vmSuggestions = withContext(Dispatchers.Default) {
  37. vmClient.getStops(query)
  38. }
  39. return if (vmSuggestions.isEmpty() and !timetable.isEmpty()) {
  40. timetable.getStopSuggestions()
  41. } else {
  42. vmSuggestions
  43. }
  44. }
  45. private fun filterSuggestions(query: String): List<GtfsSuggestion> {
  46. return suggestions.filter {
  47. deAccent(it.name).contains(deAccent(query), true)
  48. }
  49. }
  50. private fun deAccent(str: String): String {
  51. var result = str.replace('ę', 'e', true)
  52. result = result.replace('ó', 'o', true)
  53. result = result.replace('ą', 'a', true)
  54. result = result.replace('ś', 's', true)
  55. result = result.replace('ł', 'l', true)
  56. result = result.replace('ż', 'z', true)
  57. result = result.replace('ź', 'z', true)
  58. result = result.replace('ć', 'c', true)
  59. result = result.replace('ń', 'n', true)
  60. return result
  61. }
  62. fun getSheds(name: String, callback: (Map<String, Set<String>>) -> Unit) {
  63. GlobalScope.launch {
  64. val vmSheds = vmClient.getSheds(name)
  65. val sheds = if (vmSheds.isEmpty() and !timetable.isEmpty()) {
  66. timetable.getHeadlinesForStop(name)
  67. } else {
  68. vmSheds
  69. }
  70. launch(Dispatchers.Main) {
  71. callback(sheds)
  72. }
  73. }
  74. }
  75. fun getVmMessage(shed: String, callback: (String?) -> Unit) {
  76. GlobalScope.launch {
  77. val message = vmClient.getMessage(shed)
  78. launch(Dispatchers.Main) {
  79. callback(message)
  80. }
  81. }
  82. }
  83. fun subscribeForDepartures(stopSegments: Set<StopSegment>, listener: OnDeparturesReadyListener, context: Context): String {
  84. stopSegments.forEach {
  85. val intent = Intent(context, VmService::class.java)
  86. intent.putExtra("stop", it.stop)
  87. intent.action = "request"
  88. context.startService(intent)
  89. }
  90. val uuid = UUID.randomUUID().toString()
  91. requests[uuid] = Request(listener, stopSegments)
  92. return uuid
  93. }
  94. fun subscribeForDepartures(stopCode: String, listener: OnDeparturesReadyListener, context: Context): String {
  95. val intent = Intent(context, VmService::class.java)
  96. intent.putExtra("stop", stopCode)
  97. intent.action = "request"
  98. context.startService(intent)
  99. val uuid = UUID.randomUUID().toString()
  100. requests[uuid] = Request(listener, setOf(StopSegment(stopCode, null)))
  101. return uuid
  102. }
  103. private fun constructSegmentDepartures(stopSegments: Set<StopSegment>): Deferred<Map<String, List<Departure>>> {
  104. return GlobalScope.async {
  105. if (timetable.isEmpty())
  106. emptyMap()
  107. else {
  108. timetable.getStopDeparturesBySegments(stopSegments)
  109. }
  110. }
  111. }
  112. private fun filterDepartures(departures: Map<String, List<Departure>>): List<Departure> {
  113. val now = Calendar.getInstance().secondsAfterMidnight()
  114. val lines = HashMap<String, Int>()
  115. val twoDayDepartures = (timetable.getServiceForToday()?.let {
  116. departures[it]
  117. } ?: emptyList()) +
  118. (timetable.getServiceForTomorrow()?.let { service ->
  119. departures[service]!!.map { it.copy().apply { tomorrow = true } }
  120. } ?: emptyList())
  121. return twoDayDepartures
  122. .filter { it.timeTill(now) >= 0 }
  123. .filter {
  124. val existed = lines[it.line] ?: 0
  125. if (existed < 3) {
  126. lines[it.line] = existed + 1
  127. true
  128. } else false
  129. }
  130. }
  131. fun unsubscribeFromDepartures(uuid: String, context: Context) {
  132. requests[uuid]?.unsubscribe(context)
  133. requests.remove(uuid)
  134. }
  135. fun refreshTimetable(context: Context) {
  136. timetable = Timetable.getTimetable(context, true)
  137. mode = MODE_FULL
  138. }
  139. fun getFullTimetable(stopCode: String): Map<String, List<Departure>> {
  140. return if (timetable.isEmpty())
  141. emptyMap()
  142. else
  143. timetable.getStopDepartures(stopCode)
  144. }
  145. fun getFullTimetable(stopSegments: Set<StopSegment>): Map<String, List<Departure>> {
  146. return if (timetable.isEmpty())
  147. emptyMap()
  148. else
  149. timetable.getStopDeparturesBySegments(stopSegments)
  150. }
  151. fun fillStopSegment(stopSegment: StopSegment, callback: (StopSegment?) -> Unit) {
  152. GlobalScope.launch {
  153. callback(fillStopSegment(stopSegment))
  154. }
  155. }
  156. suspend fun fillStopSegment(stopSegment: StopSegment): StopSegment? {
  157. if (stopSegment.plates != null)
  158. return stopSegment
  159. return if (timetable.isEmpty())
  160. vmClient.getDirections(stopSegment.stop)
  161. else
  162. timetable.getHeadlinesForStopCode(stopSegment.stop)
  163. }
  164. fun getStopName(stopCode: String, callback: (String?) -> Unit) {
  165. GlobalScope.launch {
  166. callback(getStopName(stopCode))
  167. }
  168. }
  169. suspend fun getStopName(stopCode: String): String? {
  170. return if (timetable.isEmpty())
  171. vmClient.getName(stopCode)
  172. else
  173. timetable.getStopName(stopCode)
  174. }
  175. fun describeService(service: String, context: Context): String? {
  176. return if (timetable.isEmpty())
  177. null
  178. else
  179. timetable.getServiceDescription(service, context)
  180. }
  181. fun getServiceFirstDay(service: String): Int {
  182. return timetable.getServiceFirstDay(service)
  183. }
  184. interface OnDeparturesReadyListener {
  185. fun onDeparturesReady(departures: List<Departure>, plateId: Plate.ID?, code: Int)
  186. }
  187. inner class Request(private val listener: OnDeparturesReadyListener, private val segments: Set<StopSegment>) : MessageReceiver.OnVmListener {
  188. private val receiver = MessageReceiver.getMessageReceiver()
  189. private val receivedPlates = HashSet<Plate.ID>()
  190. private var cache: Deferred<Map<String, List<Departure>>>? = null
  191. init {
  192. receiver.addOnVmListener(this@Request)
  193. GlobalScope.launch {
  194. cache = constructSegmentDepartures(segments)
  195. }
  196. }
  197. override fun onVm(vmDepartures: Set<Departure>?, plateId: Plate.ID?, stopCode: String, code: Int) {
  198. GlobalScope.launch(Dispatchers.Main) {
  199. if ((plateId == null || vmDepartures == null) and (timetable.isEmpty())) {
  200. listener.onDeparturesReady(emptyList(), null, code)
  201. return@launch
  202. }
  203. if (plateId == null) {
  204. listener.onDeparturesReady(filterDepartures(cache!!.await()), null, code)
  205. } else {
  206. if (segments.any { plateId in it }) {
  207. if (vmDepartures != null) {
  208. listener.onDeparturesReady(vmDepartures.toList(), plateId, code)
  209. if (plateId !in receivedPlates)
  210. receivedPlates.add(plateId)
  211. } else {
  212. receivedPlates.remove(plateId)
  213. if (receivedPlates.isEmpty()) {
  214. listener.onDeparturesReady(filterDepartures(cache!!.await()), null, code)
  215. }
  216. }
  217. }
  218. }
  219. }
  220. }
  221. fun unsubscribe(context: Context) {
  222. segments.forEach {
  223. val intent = Intent(context, VmService::class.java)
  224. intent.putExtra("stop", it.stop)
  225. intent.action = "remove"
  226. context.startService(intent)
  227. }
  228. receiver.removeOnVmListener(this)
  229. }
  230. }
  231. }