Skip to content

Commit

Permalink
feat: Timetables loading and error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
magicsk committed Oct 2, 2023
1 parent e97babc commit 56949ef
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 140 deletions.
266 changes: 150 additions & 116 deletions app/src/main/java/eu/magicsk/transi/TimetableFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import eu.magicsk.transi.adapters.TimetableAdapter
import eu.magicsk.transi.data.remote.responses.idsbk.Session
import eu.magicsk.transi.databinding.FragmentTimetableBinding
import eu.magicsk.transi.util.*
import eu.magicsk.transi.view_models.MainViewModel
import eu.magicsk.transi.view_models.TimetablesViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class TimetableFragment : Fragment() {

Expand All @@ -21,9 +26,7 @@ class TimetableFragment : Fragment() {


override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
_binding = FragmentTimetableBinding.inflate(inflater, container, false)
return binding.root
Expand All @@ -40,139 +43,170 @@ class TimetableFragment : Fragment() {
val lineNum = requireArguments().getString("short_name") ?: "Error"
val lineDirections = requireArguments().getString("long_name") ?: "Error"
binding.apply {
TimetableError.isVisible = false
TimetableNoDeparturesInfo.isVisible = false
val timetablesViewModel =
ViewModelProvider(requireActivity())[TimetablesViewModel::class.java]
val context = root.context
val resources = root.resources
customizeLineText(TimetableTitleLine, lineNum, context, resources)
TimetableTitleDirections.text = lineDirections
TimetableTitleDirections.isSelected = true
val timetablesViewModel =
ViewModelProvider(requireActivity())[TimetablesViewModel::class.java]
val mainViewModel =
ViewModelProvider(requireActivity())[MainViewModel::class.java]
val mainViewModel = ViewModelProvider(requireActivity())[MainViewModel::class.java]
mainViewModel.idsbkSession.observe(viewLifecycleOwner) { idsbkSession ->
idsbkSession?.let {
timetablesViewModel.getTimetableDirections(routeId, idsbkSession)
timetablesViewModel.timetablesDirections.observe(viewLifecycleOwner) { data ->
val directions = data.directions
if (directions.isNotEmpty()) {
TimetableDirection1.text = directions[0].direction
if (directions.size > 1) TimetableDirection2.text =
directions[1].direction
TimetableDirectionToggle.addOnButtonCheckedListener { _, checkedId, isChecked ->
if (isChecked) {
when (checkedId) {
R.id.TimetableDirection1 -> {
animatedAlphaChange(
0F,
1F,
0,
TimetableLoadingIndicator
)
timetablesViewModel.getTimetable(
routeId,
"departures",
directions[0].direction_id,
getDate(),
idsbkSession
)
}
TimetableErrorBtn.setOnClickListener {
TimetableError.isVisible = false
animatedAlphaChange(0F, 1F, 0, TimetableLoadingIndicator)
fetchTimetableDirection(
timetablesViewModel, routeId, idsbkSession, lineNum
)
}
fetchTimetableDirection(timetablesViewModel, routeId, idsbkSession, lineNum)
}
}
}
}

R.id.TimetableDirection2 -> {
animatedAlphaChange(
0F,
1F,
0,
TimetableLoadingIndicator
)
timetablesViewModel.getTimetable(
routeId,
"departures",
directions[1].direction_id,
getDate(),
idsbkSession
)
}
private fun fetchTimetableDirection(
timetablesViewModel: TimetablesViewModel,
routeId: Int,
idsbkSession: Session,
lineNum: String
) {
CoroutineScope(Dispatchers.IO).launch {
val directions =
timetablesViewModel.getTimetableDirections(routeId, idsbkSession)?.directions
binding.apply {
CoroutineScope(Dispatchers.Main).launch {
if (!directions.isNullOrEmpty()) {
TimetableDirection1.text = directions[0].direction
if (directions.size > 1) {
TimetableDirection2.isVisible = true
TimetableDirection2.text = directions[1].direction
} else {
TimetableDirection2.isVisible = false
}

}
}
}
TimetableDirectionToggle.clearChecked()
TimetableDirectionToggle.check(R.id.TimetableDirection1)
timetablesViewModel.timetable.observe(viewLifecycleOwner) { timetableData ->
if (timetableData.departures.isNotEmpty()) {
val departures = timetableData.departures
TimetableTimeSlider.valueFrom = 0F
TimetableTimeSlider.valueTo = (departures.size - 1).toFloat()
TimetableTimeSlider.value = 0F
TimetableTimeSlider.stepSize = 1F
val minutes = getMinutes()
var tooLate = true
departures.forEachIndexed { i, d ->
if (d.departure - minutes > 1 && tooLate) {
tooLate = false
TimetableTimeSlider.value = i.toFloat()
}
}
if (tooLate) TimetableTimeSlider.value =
(departures.size - 1).toFloat()
TimetableTimeSlider.setLabelFormatter { value ->
val departureTime = departures[value.toInt()].departure
return@setLabelFormatter "${departureTime / 60}:${
(departureTime % 60).toString().padStart(2, Char(48))
}"
}
fun onTimetableStopClick(stationId: Int, stationName: String) {
val timetableDetailBundle = Bundle()
timetableDetailBundle.putInt("route_id", routeId)
timetableDetailBundle.putString("short_name", lineNum)
val direction =
if (TimetableDirectionToggle.checkedButtonId == R.id.TimetableDirection1) directions[0] else directions[1]
timetableDetailBundle.putString(
"long_name",
direction.direction
val timetableDetailBundle = Bundle()
timetableDetailBundle.putInt("route_id", routeId)
timetableDetailBundle.putString("short_name", lineNum)

TimetableDirectionToggle.addOnButtonCheckedListener { _, checkedId, isChecked ->
if (isChecked) {
when (checkedId) {
R.id.TimetableDirection1 -> {
animatedAlphaChange(
0F, 1F, 0, TimetableLoadingIndicator
)
timetableDetailBundle.putInt(
"direction",
direction.direction_id
timetableDetailBundle.putString(
"long_name", directions[0].direction
)
timetableDetailBundle.putInt("station_id", stationId)
timetableDetailBundle.putString("station_name", stationName)
findNavController().navigate(
R.id.action_navigationTimetable_to_navigationTimetableDetail,
fetchTimetable(
timetablesViewModel,
routeId,
directions[0].direction_id,
idsbkSession,
timetableDetailBundle
)
}

val timetableAdapter =
TimetableAdapter(timetableData.departures[TimetableTimeSlider.value.toInt()]) { stationId, stationName ->
onTimetableStopClick(
stationId,
stationName
)
}
TimetableStopsList.adapter = timetableAdapter
TimetableStopsList.layoutManager = LinearLayoutManager(context)
TimetableTimeSlider.addOnChangeListener { _, value, _ ->
if (value < timetableData.departures.size)
timetableAdapter.replaceList(timetableData.departures[value.toInt()])
}
TimetableTimePlusButton.setOnClickListener {
val newValue = TimetableTimeSlider.value + 1F
if (newValue <= (departures.size - 1).toFloat())
TimetableTimeSlider.value = newValue
}
TimetableTimeMinusButton.setOnClickListener {
val newValue = TimetableTimeSlider.value - 1F
if (newValue >= 0F)
TimetableTimeSlider.value = newValue
R.id.TimetableDirection2 -> {
animatedAlphaChange(
0F, 1F, 0, TimetableLoadingIndicator
)
timetableDetailBundle.putString(
"long_name", directions[1].direction
)
fetchTimetable(
timetablesViewModel,
routeId,
directions[1].direction_id,
idsbkSession,
timetableDetailBundle
)
}
} else {
println("Error") // TODO: display error and retry button

}
animatedAlphaChange(1F, 0F, 100, TimetableLoadingIndicator)
}
}
TimetableDirectionToggle.clearChecked()
TimetableDirectionToggle.check(R.id.TimetableDirection1)
} else {
TimetableError.isVisible = true
}
}
}
}
}


private fun fetchTimetable(
timetablesViewModel: TimetablesViewModel,
routeId: Int,
direction: Int,
idsbkSession: Session,
timetableDetailBundle: Bundle
) {
CoroutineScope(Dispatchers.IO).launch {
val timetable = timetablesViewModel.getTimetable(
routeId, "departures", direction, getDate(), idsbkSession
)
val departures = timetable?.departures
binding.apply {
CoroutineScope(Dispatchers.Main).launch {
if (!departures.isNullOrEmpty()) {
val valueTo = (departures.size - 1).toFloat()
TimetableTimeSlider.valueFrom = 0F
TimetableTimeSlider.valueTo = if (valueTo > 0F) valueTo else 1F
TimetableTimeSlider.value = 0F
TimetableTimeSlider.stepSize = 1F
val minutes = getMinutes()
var tooLate = true
departures.forEachIndexed { i, d ->
if (d.departure - minutes > 1 && tooLate) {
tooLate = false
TimetableTimeSlider.value = i.toFloat()
}
}
if (tooLate) TimetableTimeSlider.value = (departures.size - 1).toFloat()
TimetableTimeSlider.setLabelFormatter { value ->
val departureTime = departures[value.toInt()].departure
return@setLabelFormatter "${departureTime / 60}:${
(departureTime % 60).toString().padStart(2, Char(48))
}"
}

val timetableAdapter =
TimetableAdapter(departures[TimetableTimeSlider.value.toInt()]) { stationId, stationName ->
timetableDetailBundle.putInt("direction", direction)
timetableDetailBundle.putInt("station_id", stationId)
timetableDetailBundle.putString("station_name", stationName)
findNavController().navigate(
R.id.action_navigationTimetable_to_navigationTimetableDetail,
timetableDetailBundle
)
}
TimetableStopsList.adapter = timetableAdapter
TimetableStopsList.layoutManager = LinearLayoutManager(context)
TimetableTimeSlider.addOnChangeListener { _, value, _ ->
if (value < departures.size) timetableAdapter.replaceList(departures[value.toInt()])
}
TimetableTimePlusButton.setOnClickListener {
val newValue = TimetableTimeSlider.value + 1F
if (newValue <= (departures.size - 1).toFloat()) TimetableTimeSlider.value =
newValue
}
TimetableTimeMinusButton.setOnClickListener {
val newValue = TimetableTimeSlider.value - 1F
if (newValue >= 0F) TimetableTimeSlider.value = newValue
}
} else if (departures == null) {
TimetableError.isVisible = true
} else {
TimetableNoDeparturesInfo.isVisible = true
}
animatedAlphaChange(1F, 0F, 100, TimetableLoadingIndicator)
}
}
}
Expand Down
Loading

0 comments on commit 56949ef

Please sign in to comment.