mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Sorted out the memory leak in passes fragment
This commit is contained in:
1 parent
88228bfad7
commit
c58cf94181
3 files changed
+104
-114
No files matched your search
+3
-3
@@ -64,9 +64,9 @@ dependencies {
|
||||
implementation "androidx.navigation:navigation-runtime-ktx:2.4.1"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:2.4.1"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:2.4.1"
|
||||
implementation "androidx.room:room-runtime:2.4.1"
|
||||
implementation "androidx.room:room-ktx:2.4.1"
|
||||
kapt "androidx.room:room-compiler:2.4.1"
|
||||
implementation "androidx.room:room-runtime:2.4.2"
|
||||
implementation "androidx.room:room-ktx:2.4.2"
|
||||
kapt "androidx.room:room-compiler:2.4.2"
|
||||
|
||||
implementation "com.google.android.material:material:1.5.0"
|
||||
implementation "com.google.dagger:hilt-android:2.41"
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.rtbishop.look4sat.domain.predict.SatPass
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class PassesAdapter(private val isUTC: Boolean, private val clickListener: PassesClickListener) :
|
||||
class PassesAdapter(private val clickListener: PassesClickListener) :
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
private val diffCallback = object : DiffUtil.ItemCallback<SatPass>() {
|
||||
@@ -41,6 +41,7 @@ class PassesAdapter(private val isUTC: Boolean, private val clickListener: Passe
|
||||
}
|
||||
}
|
||||
private val differ = AsyncListDiffer(this, diffCallback)
|
||||
private var isUTC: Boolean = false
|
||||
|
||||
interface PassesClickListener {
|
||||
fun navigateToPass(satPass: SatPass)
|
||||
@@ -50,6 +51,10 @@ class PassesAdapter(private val isUTC: Boolean, private val clickListener: Passe
|
||||
differ.submitList(passes)
|
||||
}
|
||||
|
||||
fun setUTC(isUTC: Boolean) {
|
||||
this.isUTC = isUTC
|
||||
}
|
||||
|
||||
override fun getItemCount() = differ.currentList.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
|
||||
+95
-110
@@ -18,7 +18,6 @@
|
||||
package com.rtbishop.look4sat.presentation.passesScreen
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.LinearInterpolator
|
||||
@@ -42,27 +41,20 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener {
|
||||
|
||||
private val viewModel: PassesViewModel by viewModels()
|
||||
private lateinit var binding: FragmentPassesBinding
|
||||
private lateinit var refreshAnimator: ValueAnimator
|
||||
private val passesAdapter = PassesAdapter(this)
|
||||
private var binding: FragmentPassesBinding? = null
|
||||
private var refreshAnimator: ValueAnimator? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentPassesBinding.bind(view)
|
||||
setupComponents()
|
||||
}
|
||||
|
||||
private fun setupComponents() {
|
||||
val context = requireContext()
|
||||
val adapter = PassesAdapter(viewModel.shouldUseUTC(), this)
|
||||
val layoutManager = LinearLayoutManager(context)
|
||||
val itemDecoration = DividerItemDecoration(context, layoutManager.orientation)
|
||||
binding.run {
|
||||
binding = FragmentPassesBinding.bind(view).apply {
|
||||
passesAdapter.setUTC(viewModel.shouldUseUTC())
|
||||
passesRecycler.apply {
|
||||
setHasFixedSize(true)
|
||||
this.adapter = adapter
|
||||
this.layoutManager = layoutManager
|
||||
addItemDecoration(itemDecoration)
|
||||
adapter = passesAdapter
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
addItemDecoration(DividerItemDecoration(requireContext(), 1))
|
||||
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
if (dy > 0 && passesFab.visibility == View.VISIBLE) passesFab.hide()
|
||||
@@ -71,22 +63,52 @@ class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesC
|
||||
})
|
||||
}
|
||||
passesRefreshBtn.setOnClickListener { viewModel.calculatePasses() }
|
||||
passesMapBtn.setOnClickListener { navigateToMap() }
|
||||
passesFilterBtn.setOnClickListener { navigateToFilter() }
|
||||
passesFab.setOnClickListener { navigateToEntries() }
|
||||
passesSettingsBtn.setOnClickListener { navigateToSettings() }
|
||||
passesMapBtn.setOnClickListener {
|
||||
val dir = PassesFragmentDirections.actionGlobalMapFragment()
|
||||
findNavController().navigate(dir)
|
||||
}
|
||||
passesFilterBtn.setOnClickListener {
|
||||
val dir = PassesFragmentDirections.actionPassesToFilter()
|
||||
findNavController().navigate(dir)
|
||||
}
|
||||
passesSettingsBtn.setOnClickListener {
|
||||
val dir = PassesFragmentDirections.actionGlobalSettingsFragment()
|
||||
findNavController().navigate(dir)
|
||||
}
|
||||
setupAnimator()
|
||||
setupObservers()
|
||||
}
|
||||
setupObservers(adapter)
|
||||
setupAnimator()
|
||||
}
|
||||
|
||||
private fun setupObservers(adapter: PassesAdapter) {
|
||||
viewModel.passes.observe(viewLifecycleOwner) { passesResult ->
|
||||
handleNewPasses(passesResult, adapter)
|
||||
override fun navigateToPass(satPass: SatPass) {
|
||||
if (satPass.progress < 100) satPass.run {
|
||||
val dir = PassesFragmentDirections.actionGlobalRadarFragment(this.catNum, this.aosTime)
|
||||
findNavController().navigate(dir)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding?.passesRecycler?.adapter = null
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun setupAnimator() {
|
||||
refreshAnimator = ValueAnimator.ofFloat(0f, -360f).apply {
|
||||
duration = 875
|
||||
interpolator = LinearInterpolator()
|
||||
repeatCount = ValueAnimator.INFINITE
|
||||
addUpdateListener { binding?.passesRefreshBtn?.rotation = animatedValue as Float }
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
viewModel.entriesTotal.observe(viewLifecycleOwner) { number ->
|
||||
handleEntriesTotal(number)
|
||||
}
|
||||
viewModel.passes.observe(viewLifecycleOwner) { passesResult ->
|
||||
handleNewPasses(passesResult)
|
||||
}
|
||||
getNavResult<Pair<Int, Double>>(R.id.nav_passes, "prefs") { prefs ->
|
||||
viewModel.calculatePasses(prefs.first, prefs.second)
|
||||
}
|
||||
@@ -95,104 +117,67 @@ class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesC
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAnimator() {
|
||||
refreshAnimator = ValueAnimator.ofFloat(0f, -360f).apply {
|
||||
duration = 875
|
||||
interpolator = LinearInterpolator()
|
||||
repeatCount = ValueAnimator.INFINITE
|
||||
addUpdateListener { binding.passesRefreshBtn.rotation = animatedValue as Float }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleEntriesTotal(number: Int) {
|
||||
if (number > 0) {
|
||||
binding.passesFab.setOnClickListener { navigateToEntries() }
|
||||
} else {
|
||||
val errorMessage = getString(R.string.passes_empty_db)
|
||||
binding.passesFab.setOnClickListener { requireContext().toast(errorMessage) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNewPasses(state: DataState<List<SatPass>>, adapter: PassesAdapter) {
|
||||
when (state) {
|
||||
is DataState.Success -> {
|
||||
adapter.submitList(state.data)
|
||||
tickMainTimer(state.data)
|
||||
if (state.data.isNotEmpty()) { // show new passes list
|
||||
binding.run {
|
||||
passesRecycler.visibility = View.VISIBLE
|
||||
passesErrorMsg.visibility = View.INVISIBLE
|
||||
binding.passesRefreshBtn.isEnabled = true
|
||||
refreshAnimator.cancel()
|
||||
}
|
||||
} else { // show no passes message
|
||||
binding.run {
|
||||
passesRecycler.visibility = View.INVISIBLE
|
||||
passesErrorMsg.visibility = View.VISIBLE
|
||||
binding.passesRefreshBtn.isEnabled = true
|
||||
refreshAnimator.cancel()
|
||||
}
|
||||
binding?.run {
|
||||
if (number > 0) {
|
||||
passesFab.setOnClickListener {
|
||||
val direction = PassesFragmentDirections.actionGlobalEntriesFragment()
|
||||
findNavController().navigate(direction)
|
||||
}
|
||||
} else {
|
||||
passesFab.setOnClickListener {
|
||||
val errorMessage = getString(R.string.passes_empty_db)
|
||||
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
is DataState.Loading -> {
|
||||
binding.run {
|
||||
refreshAnimator.start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNewPasses(state: DataState<List<SatPass>>) {
|
||||
binding?.run {
|
||||
when (state) {
|
||||
is DataState.Success -> {
|
||||
passesAdapter.submitList(state.data)
|
||||
tickMainTimer(state.data)
|
||||
if (state.data.isNotEmpty()) { // show new passes list
|
||||
passesRecycler.visibility = View.VISIBLE
|
||||
passesErrorMsg.visibility = View.INVISIBLE
|
||||
passesRefreshBtn.isEnabled = true
|
||||
refreshAnimator?.cancel()
|
||||
} else { // show no passes message
|
||||
passesRecycler.visibility = View.INVISIBLE
|
||||
passesErrorMsg.visibility = View.VISIBLE
|
||||
passesRefreshBtn.isEnabled = true
|
||||
refreshAnimator?.cancel()
|
||||
}
|
||||
}
|
||||
is DataState.Loading -> {
|
||||
refreshAnimator?.start()
|
||||
passesRefreshBtn.isEnabled = false
|
||||
passesTimer.text = 0L.toTimerString()
|
||||
passesErrorMsg.visibility = View.INVISIBLE
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun tickMainTimer(passes: List<SatPass>) {
|
||||
if (passes.isNotEmpty()) {
|
||||
val timeNow = System.currentTimeMillis()
|
||||
try {
|
||||
val nextPass = passes.first { it.aosTime.minus(timeNow) > 0 }
|
||||
val millisBeforeStart = nextPass.aosTime.minus(timeNow)
|
||||
binding.passesTimer.text = millisBeforeStart.toTimerString()
|
||||
} catch (e: NoSuchElementException) {
|
||||
val lastPass = passes.last()
|
||||
val millisBeforeEnd = lastPass.losTime.minus(timeNow)
|
||||
binding.passesTimer.text = millisBeforeEnd.toTimerString()
|
||||
binding?.run {
|
||||
if (passes.isNotEmpty()) {
|
||||
val timeNow = System.currentTimeMillis()
|
||||
try {
|
||||
val nextPass = passes.first { it.aosTime.minus(timeNow) > 0 }
|
||||
val millisBeforeStart = nextPass.aosTime.minus(timeNow)
|
||||
passesTimer.text = millisBeforeStart.toTimerString()
|
||||
} catch (e: NoSuchElementException) {
|
||||
val lastPass = passes.last()
|
||||
val millisBeforeEnd = lastPass.losTime.minus(timeNow)
|
||||
passesTimer.text = millisBeforeEnd.toTimerString()
|
||||
}
|
||||
} else {
|
||||
passesTimer.text = 0L.toTimerString()
|
||||
}
|
||||
} else {
|
||||
binding.passesTimer.text = 0L.toTimerString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToMap() {
|
||||
val direction = PassesFragmentDirections.actionGlobalMapFragment()
|
||||
findNavController().navigate(direction)
|
||||
}
|
||||
|
||||
private fun navigateToFilter() {
|
||||
val direction = PassesFragmentDirections.actionPassesToFilter()
|
||||
findNavController().navigate(direction)
|
||||
}
|
||||
|
||||
private fun navigateToEntries() {
|
||||
val direction = PassesFragmentDirections.actionGlobalEntriesFragment()
|
||||
findNavController().navigate(direction)
|
||||
}
|
||||
|
||||
private fun navigateToSettings() {
|
||||
val direction = PassesFragmentDirections.actionGlobalSettingsFragment()
|
||||
findNavController().navigate(direction)
|
||||
}
|
||||
|
||||
override fun navigateToPass(satPass: SatPass) {
|
||||
if (satPass.progress < 100) {
|
||||
val catNum = satPass.catNum
|
||||
val aosTime = satPass.aosTime
|
||||
val action = PassesFragmentDirections.actionGlobalRadarFragment(catNum, aosTime)
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.toast(text: String, duration: Int = Toast.LENGTH_SHORT) {
|
||||
Toast.makeText(this, text, duration).apply { show() }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user