StopActivity.kt 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. package ml.adamsprogs.bimba.activities
  2. import android.content.DialogInterface
  3. import android.content.Intent
  4. import android.content.IntentFilter
  5. import android.os.Build
  6. import android.os.Bundle
  7. import android.text.Html
  8. import android.view.Menu
  9. import android.view.MenuItem
  10. import android.view.View
  11. import android.widget.AdapterView
  12. import androidx.appcompat.app.AlertDialog
  13. import androidx.appcompat.app.AppCompatActivity
  14. import androidx.core.content.res.ResourcesCompat
  15. import com.google.android.material.snackbar.Snackbar
  16. import kotlinx.android.synthetic.main.activity_stop.*
  17. import kotlinx.android.synthetic.main.banner.*
  18. import ml.adamsprogs.bimba.*
  19. import ml.adamsprogs.bimba.collections.FavouriteStorage
  20. import ml.adamsprogs.bimba.datasources.TimetableDownloader
  21. import ml.adamsprogs.bimba.datasources.VmService
  22. import ml.adamsprogs.bimba.models.Departure
  23. import ml.adamsprogs.bimba.models.Favourite
  24. import ml.adamsprogs.bimba.models.Plate
  25. import ml.adamsprogs.bimba.models.StopSegment
  26. import ml.adamsprogs.bimba.models.adapters.DeparturesAdapter
  27. import ml.adamsprogs.bimba.models.adapters.ServiceAdapter
  28. import java.util.Calendar
  29. import kotlin.collections.HashMap
  30. import kotlin.collections.HashSet
  31. import kotlin.collections.set
  32. class StopActivity : AppCompatActivity(), MessageReceiver.OnTimetableDownloadListener, ProviderProxy.OnDeparturesReadyListener {
  33. companion object {
  34. const val EXTRA_STOP_CODE = "stopCode"
  35. const val EXTRA_STOP_NAME = "stopName"
  36. const val EXTRA_FAVOURITE = "favourite"
  37. const val SOURCE_TYPE = "sourceType"
  38. const val SOURCE_TYPE_STOP = "stop"
  39. const val SOURCE_TYPE_FAV = "favourite"
  40. const val TIMETABLE_TYPE_DEPARTURE = "timetable_type_departure"
  41. const val TIMETABLE_TYPE_FULL = "timetable_type_full"
  42. }
  43. private var stopCode = ""
  44. private var favourite: Favourite? = null
  45. private var timetableType = TIMETABLE_TYPE_DEPARTURE
  46. private val context = this
  47. private val receiver = MessageReceiver.getMessageReceiver()
  48. private lateinit var providerProxy: ProviderProxy
  49. private val departures = HashMap<Plate.ID, List<Departure>>()
  50. private val fullDepartures = HashMap<String, List<Departure>>()
  51. private lateinit var subscriptionId: String
  52. private lateinit var adapter: DeparturesAdapter
  53. private lateinit var sourceType: String
  54. override fun onCreate(savedInstanceState: Bundle?) {
  55. super.onCreate(savedInstanceState)
  56. setContentView(R.layout.activity_stop)
  57. providerProxy = ProviderProxy(this)
  58. sourceType = intent.getStringExtra(SOURCE_TYPE)
  59. setSupportActionBar(toolbar)
  60. when (sourceType) {
  61. SOURCE_TYPE_STOP -> {
  62. stopCode = intent.getSerializableExtra(EXTRA_STOP_CODE) as String
  63. supportActionBar?.title = intent.getSerializableExtra(EXTRA_STOP_NAME) as String
  64. }
  65. SOURCE_TYPE_FAV -> {
  66. favourite = intent.getParcelableExtra(EXTRA_FAVOURITE)
  67. supportActionBar?.title = favourite!!.name
  68. }
  69. }
  70. showFab()
  71. val layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this)
  72. departuresList.addItemDecoration(androidx.recyclerview.widget.DividerItemDecoration(departuresList.context, layoutManager.orientation))
  73. departuresList.adapter = DeparturesAdapter(this, null, true)
  74. adapter = departuresList.adapter as DeparturesAdapter
  75. departuresList.layoutManager = layoutManager
  76. departuresList.addOnScrollListener(object : androidx.recyclerview.widget.RecyclerView.OnScrollListener() {
  77. override fun onScrollStateChanged(recyclerView: androidx.recyclerview.widget.RecyclerView, newState: Int) {}
  78. override fun onScrolled(recyclerView: androidx.recyclerview.widget.RecyclerView, dx: Int, dy: Int) {
  79. updateFabVisibility(dy)
  80. super.onScrolled(recyclerView, dx, dy)
  81. }
  82. })
  83. if (stopCode != "")
  84. providerProxy.getVmMessage(stopCode) { message ->
  85. if (message != null) {
  86. val rendered =
  87. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  88. Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY)
  89. } else {
  90. @Suppress("DEPRECATION")
  91. Html.fromHtml(message)
  92. }
  93. banner.visibility = View.VISIBLE
  94. banner_text.text = rendered
  95. banner_more.setOnClickListener {
  96. AlertDialog.Builder(context)
  97. .setPositiveButton(context.getText(android.R.string.ok))
  98. { dialog: DialogInterface, _: Int -> dialog.cancel() }
  99. .setCancelable(true)
  100. .setMessage(rendered)
  101. .create().show()
  102. }
  103. }
  104. }
  105. prepareOnDownloadListener()
  106. subscribeForDepartures()
  107. }
  108. private fun showFab() {
  109. if (sourceType == SOURCE_TYPE_FAV)
  110. return
  111. val favourites = FavouriteStorage.getFavouriteStorage(context)
  112. if (!favourites.has(stopCode)) {
  113. fab.setImageDrawable(ResourcesCompat.getDrawable(context.resources, R.drawable.ic_favourite_empty, this.theme))
  114. }
  115. fab.setOnClickListener {
  116. if (!favourites.has(stopCode)) {
  117. val items = HashSet<StopSegment>()
  118. items.add(StopSegment(stopCode, null))
  119. favourites.add(stopCode, items, this@StopActivity)
  120. fab.setImageDrawable(ResourcesCompat.getDrawable(context.resources, R.drawable.ic_favourite, this.theme))
  121. } else {
  122. Snackbar.make(it, getString(R.string.stop_already_fav), Snackbar.LENGTH_LONG)
  123. .setAction("Action", null).show()
  124. }
  125. }
  126. }
  127. //fixme<p:5> maybe better effects
  128. private fun updateFabVisibility(dy: Int) {
  129. if (fab == null)
  130. return
  131. if (dy > 0) {
  132. fab.hide()
  133. } else {
  134. fab.show()
  135. }
  136. }
  137. private fun prepareOnDownloadListener() {
  138. val filter = IntentFilter(TimetableDownloader.ACTION_DOWNLOADED)
  139. filter.addAction(VmService.ACTION_READY)
  140. filter.addCategory(Intent.CATEGORY_DEFAULT)
  141. registerReceiver(receiver, filter)
  142. receiver.addOnTimetableDownloadListener(context)
  143. }
  144. private fun subscribeForDepartures() {
  145. subscriptionId = if (sourceType == SOURCE_TYPE_STOP) {
  146. providerProxy.subscribeForDepartures(stopCode, this, this)
  147. } else
  148. favourite!!.subscribeForDepartures(this, context)
  149. }
  150. override fun onDeparturesReady(departures: List<Departure>, plateId: Plate.ID?, code: Int) {
  151. progressBar.visibility = View.GONE
  152. showError(stop_layout, code, this)
  153. if (plateId == null) {
  154. this.departures.clear()
  155. this.departures[Plate.ID.dummy] = departures
  156. } else {
  157. this.departures.remove(Plate.ID.dummy)
  158. this.departures[plateId] = departures
  159. }
  160. if (timetableType == TIMETABLE_TYPE_FULL)
  161. return
  162. refreshAdapter()
  163. if (adapter.departures?.isEmpty() != false) {
  164. emptyStateIcon.visibility = View.VISIBLE
  165. emptyStateText.visibility = View.VISIBLE
  166. departuresList.visibility = View.GONE
  167. } else {
  168. emptyStateIcon.visibility = View.GONE
  169. emptyStateText.visibility = View.GONE
  170. departuresList.visibility = View.VISIBLE
  171. }
  172. }
  173. private fun refreshAdapter() {
  174. if (timetableType == TIMETABLE_TYPE_FULL) {
  175. @Suppress("UNCHECKED_CAST")
  176. adapter.departures = fullDepartures[(dateSpinner.selectedItem as ServiceAdapter.RowItem).service]
  177. } else {
  178. val now = Calendar.getInstance()
  179. val seconds = now.secondsAfterMidnight()
  180. adapter.departures = this.departures.flatMap { it.value }.sortedBy { (if (it.onStop) 0 else 1) * 172800 + it.timeTill(seconds) } // todo sorted by also onStop
  181. }
  182. adapter.notifyDataSetChanged()
  183. }
  184. override fun onTimetableDownload(result: String?) {
  185. val message: String = when (result) {
  186. TimetableDownloader.RESULT_NO_CONNECTIVITY -> getString(R.string.no_connectivity_cant_update)
  187. TimetableDownloader.RESULT_UP_TO_DATE -> getString(R.string.timetable_up_to_date)
  188. TimetableDownloader.RESULT_FINISHED -> getString(R.string.timetable_downloaded)
  189. else -> getString(R.string.error_try_later)
  190. }
  191. try {
  192. Snackbar.make(findViewById(R.id.stop_layout), message, Snackbar.LENGTH_LONG).show()
  193. } catch (e: IllegalArgumentException) {
  194. }
  195. providerProxy.refreshTimetable(this)
  196. }
  197. override fun onCreateOptionsMenu(menu: Menu): Boolean {
  198. if (providerProxy.mode == ProviderProxy.MODE_FULL)
  199. menuInflater.inflate(R.menu.menu_stop, menu)
  200. return true
  201. }
  202. override fun onOptionsItemSelected(item: MenuItem): Boolean {
  203. val id = item.itemId
  204. if (id == R.id.action_change_type) {
  205. if (timetableType == TIMETABLE_TYPE_DEPARTURE) {
  206. timetableType = TIMETABLE_TYPE_FULL
  207. item.icon = (ResourcesCompat.getDrawable(resources, R.drawable.ic_timetable_departure, this.theme))
  208. adapter.relativeTime = false
  209. if (fullDepartures.isEmpty())
  210. if (sourceType == SOURCE_TYPE_STOP)
  211. fullDepartures.putAll(providerProxy.getFullTimetable(stopCode))
  212. else
  213. fullDepartures.putAll(favourite!!.fullTimetable())
  214. dateSpinner.let { spinner ->
  215. spinner.adapter = ServiceAdapter(this, R.layout.toolbar_spinner_item, fullDepartures.keys.map {
  216. ServiceAdapter.RowItem(it, providerProxy.describeService(it, this)!!)
  217. }.sorted()).apply {
  218. setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
  219. }
  220. spinner.visibility = View.VISIBLE
  221. spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
  222. override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
  223. refreshAdapter()
  224. }
  225. override fun onNothingSelected(parent: AdapterView<*>?) {
  226. }
  227. }
  228. }
  229. refreshAdapter()
  230. } else {
  231. dateSpinner.visibility = View.GONE
  232. timetableType = TIMETABLE_TYPE_DEPARTURE
  233. item.icon = (ResourcesCompat.getDrawable(resources, R.drawable.ic_timetable_full, this.theme))
  234. adapter.relativeTime = true
  235. refreshAdapter()
  236. }
  237. return true
  238. }
  239. return super.onOptionsItemSelected(item)
  240. }
  241. override fun onDestroy() {
  242. super.onDestroy()
  243. receiver.removeOnTimetableDownloadListener(context)
  244. if (sourceType == SOURCE_TYPE_STOP)
  245. providerProxy.unsubscribeFromDepartures(subscriptionId, this)
  246. else
  247. favourite!!.unsubscribeFromDepartures(this)
  248. unregisterReceiver(receiver)
  249. }
  250. }