From acb851d4f6e0b1bfd42825beedbe5ffbcceb6285 Mon Sep 17 00:00:00 2001 From: Arty Bishop Date: Sat, 25 Feb 2023 19:15:47 +0000 Subject: [PATCH] Added filter dialog, removed PassesFragment --- .../look4sat/framework/model/DataSource.kt | 20 --- .../presentation/passesScreen/FilterDialog.kt | 104 ++++++----- .../passesScreen/PassesAdapter.kt | 124 ------------- .../passesScreen/PassesFragment.kt | 167 ------------------ .../presentation/passesScreen/PassesScreen.kt | 17 +- .../passesScreen/PassesViewModel.kt | 4 + app/src/main/res/layout/dialog_filter.xml | 131 -------------- app/src/main/res/layout/dialog_sources.xml | 84 --------- app/src/main/res/layout/fragment_passes.xml | 116 ------------ app/src/main/res/layout/item_pass.xml | 115 ------------ app/src/main/res/navigation/nav_graph.xml | 18 +- 11 files changed, 79 insertions(+), 821 deletions(-) delete mode 100644 app/src/main/java/com/rtbishop/look4sat/framework/model/DataSource.kt delete mode 100644 app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesAdapter.kt delete mode 100644 app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesFragment.kt delete mode 100644 app/src/main/res/layout/dialog_filter.xml delete mode 100644 app/src/main/res/layout/dialog_sources.xml delete mode 100644 app/src/main/res/layout/fragment_passes.xml delete mode 100644 app/src/main/res/layout/item_pass.xml diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/model/DataSource.kt b/app/src/main/java/com/rtbishop/look4sat/framework/model/DataSource.kt deleted file mode 100644 index 58a01a49..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/framework/model/DataSource.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Look4Sat. Amateur radio satellite tracker and pass predictor. - * Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.rtbishop.look4sat.framework.model - -data class DataSource(var url: String = String()) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/FilterDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/FilterDialog.kt index ec0035c7..707ae20c 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/FilterDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/FilterDialog.kt @@ -17,54 +17,70 @@ */ package com.rtbishop.look4sat.presentation.passesScreen -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import androidx.appcompat.app.AppCompatDialogFragment -import com.rtbishop.look4sat.R -import com.rtbishop.look4sat.databinding.DialogFilterBinding -import com.rtbishop.look4sat.domain.ISettingsManager -import com.rtbishop.look4sat.presentation.setNavResult -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.window.Dialog +import com.rtbishop.look4sat.presentation.CardButton +import com.rtbishop.look4sat.presentation.MainTheme -@AndroidEntryPoint -class FilterDialog : AppCompatDialogFragment() { +@Preview(showBackground = true) +@Composable +private fun FilterDialogPreview() { + MainTheme { FilterDialog(8, 16.0, {}) { _, _ -> } } +} - @Inject - lateinit var preferences: ISettingsManager - - override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? { - return inflater.inflate(R.layout.dialog_filter, group, false) - } - - override fun onViewCreated(view: View, state: Bundle?) { - super.onViewCreated(view, state) - DialogFilterBinding.bind(view).apply { - dialog?.window?.setBackgroundDrawableResource(R.color.transparent) - dialog?.window?.setLayout( - (resources.displayMetrics.widthPixels * 0.94).toInt(), - WindowManager.LayoutParams.WRAP_CONTENT - ) - filterHoursEdit.setText(preferences.getHoursAhead().toString()) - filterElevEdit.setText(preferences.getMinElevation().toString()) - filterBtnPos.setOnClickListener { - val hoursAhead = try { - filterHoursEdit.text.toString().toInt() - } catch (exception: Exception) { - 24 +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FilterDialog(hours: Int, elevation: Double, toggle: () -> Unit, save: (Int, Double) -> Unit) { + val hoursValue = rememberSaveable { mutableStateOf(hours) } + val elevValue = rememberSaveable { mutableStateOf(elevation) } + Dialog(onDismissRequest = { toggle() }) { + ElevatedCard { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(1f) + ) { + Text(text = "Filter passes", color = MaterialTheme.colorScheme.primary) + Text(text = "Show passes that occur within X hours") + OutlinedTextField(value = hoursValue.value.toString(), onValueChange = { newValue -> + val hoursAhead = try { + newValue.toInt() + } catch (exception: Exception) { + 12 + } + hoursValue.value = hoursAhead + }) + Text(text = "Show passes with max elevation above") + OutlinedTextField(value = elevValue.value.toString(), onValueChange = { newValue -> + val maxElevation = try { + newValue.toDouble() + } catch (exception: Exception) { + 16.0 + } + elevValue.value = maxElevation + }) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + CardButton(onClick = { toggle() }, text = "Cancel") + CardButton( + onClick = { + save(hoursValue.value, elevValue.value) + toggle() + }, text = "Accept" + ) } - val minElevation = try { - filterElevEdit.text.toString().toDouble() - } catch (exception: Exception) { - 16.0 - } - setNavResult("prefs", Pair(hoursAhead, minElevation)) - dismiss() } - filterBtnNeg.setOnClickListener { dismiss() } } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesAdapter.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesAdapter.kt deleted file mode 100644 index 67a4f4a1..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesAdapter.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Look4Sat. Amateur radio satellite tracker and pass predictor. - * Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.rtbishop.look4sat.presentation.passesScreen - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.AsyncListDiffer -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import com.rtbishop.look4sat.R -import com.rtbishop.look4sat.databinding.ItemPassBinding -import com.rtbishop.look4sat.domain.predict.SatPass -import java.text.SimpleDateFormat -import java.util.* - -class PassesAdapter(private val clickListener: PassesClickListener) : - RecyclerView.Adapter() { - - private val diffCallback = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: SatPass, newItem: SatPass): Boolean { - return oldItem.catNum == newItem.catNum && oldItem.aosTime == newItem.aosTime - } - - override fun areContentsTheSame(oldItem: SatPass, newItem: SatPass): Boolean { - return oldItem.progress == newItem.progress - } - } - private val differ = AsyncListDiffer(this, diffCallback) - private var isUTC: Boolean = false - - interface PassesClickListener { - fun navigateToPass(satPass: SatPass) - } - - fun submitList(passes: List) { - 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 { - return PassHolder.from(parent) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - (holder as PassHolder).bind(differ.currentList[position], clickListener, isUTC) - } - - class PassHolder private constructor(private val binding: ItemPassBinding) : - RecyclerView.ViewHolder(binding.root) { - - private val satIdFormat = itemView.context.getString(R.string.pass_satId) - private val aosAzFormat = itemView.context.getString(R.string.pass_aosAz) - private val altFormat = itemView.context.getString(R.string.pass_altitude) - private val losAzFormat = itemView.context.getString(R.string.pass_losAz) - private val startTimeFormat = itemView.context.getString(R.string.pass_startTime) - private val elevFormat = itemView.context.getString(R.string.pass_elevation) - private val endTimeFormat = itemView.context.getString(R.string.pass_endTime) - private val placeholder = itemView.context.getString(R.string.pass_placeholder) - private val timeZoneUTC = TimeZone.getTimeZone("UTC") - private val startFormat = SimpleDateFormat(startTimeFormat, Locale.getDefault()) - private val endFormat = SimpleDateFormat(endTimeFormat, Locale.getDefault()) - - fun bind(satPass: SatPass, listener: PassesClickListener, shouldUseUTC: Boolean) { - binding.apply { - if (satPass.isDeepSpace) { - passName.text = satPass.name - passId.text = String.format(satIdFormat, satPass.catNum) - passAos.text = String.format(aosAzFormat, satPass.aosAzimuth) - passAltitude.text = String.format(altFormat, satPass.altitude) - passLos.text = String.format(losAzFormat, satPass.losAzimuth) - passStart.text = placeholder - passElev.text = String.format(elevFormat, satPass.maxElevation) - passEnd.text = placeholder - passProgress.progress = 100 - } else { - if (shouldUseUTC) { - startFormat.timeZone = timeZoneUTC - endFormat.timeZone = timeZoneUTC - } - passName.text = satPass.name - passId.text = String.format(satIdFormat, satPass.catNum) - passAos.text = String.format(aosAzFormat, satPass.aosAzimuth) - passAltitude.text = String.format(altFormat, satPass.altitude) - passLos.text = String.format(losAzFormat, satPass.losAzimuth) - passStart.text = startFormat.format(Date(satPass.aosTime)) - passElev.text = String.format(elevFormat, satPass.maxElevation) - passEnd.text = endFormat.format(Date(satPass.losTime)) - passProgress.progress = satPass.progress.toInt() - } - } - - itemView.setOnClickListener { - listener.navigateToPass(satPass) - } - } - - companion object { - fun from(parent: ViewGroup): PassHolder { - val inflater = LayoutInflater.from(parent.context) - return PassHolder(ItemPassBinding.inflate(inflater, parent, false)) - } - } - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesFragment.kt deleted file mode 100644 index 41e99363..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesFragment.kt +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Look4Sat. Amateur radio satellite tracker and pass predictor. - * Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.rtbishop.look4sat.presentation.passesScreen - -import android.os.Bundle -import android.view.View -import android.widget.Toast -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.SimpleItemAnimator -import com.rtbishop.look4sat.R -import com.rtbishop.look4sat.databinding.FragmentPassesBinding -import com.rtbishop.look4sat.domain.model.DataState -import com.rtbishop.look4sat.domain.predict.SatPass -import com.rtbishop.look4sat.presentation.clickWithDebounce -import com.rtbishop.look4sat.presentation.getNavResult -import com.rtbishop.look4sat.utility.toTimerString -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener { - - private val viewModel: PassesViewModel by viewModels() - private val passesAdapter = PassesAdapter(this) - private var binding: FragmentPassesBinding? = null - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding = FragmentPassesBinding.bind(view).apply { - passesAdapter.setUTC(viewModel.shouldUseUTC()) - passesRecycler.apply { - setHasFixedSize(true) - 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() - else if (dy < 0 && passesFab.visibility != View.VISIBLE) passesFab.show() - } - }) - } - passesBtnRefresh.clickWithDebounce { viewModel.calculatePasses() } - passesBtnMap.clickWithDebounce { - val dir = PassesFragmentDirections.globalToMap() - findNavController().navigate(dir) - } - passesBtnFilter.clickWithDebounce { - val dir = PassesFragmentDirections.passesToFilter() - findNavController().navigate(dir) - } - passesBtnSettings.clickWithDebounce { - val dir = PassesFragmentDirections.globalToSettings() - findNavController().navigate(dir) - } - setupObservers() - } - } - - override fun navigateToPass(satPass: SatPass) { - if (satPass.progress < 100) satPass.run { - val dir = PassesFragmentDirections.globalToRadar(this.catNum, this.aosTime) - findNavController().navigate(dir) - } - } - - override fun onDestroyView() { - binding?.passesRecycler?.adapter = null - binding = null - super.onDestroyView() - } - - private fun setupObservers() { - viewModel.entriesTotal.observe(viewLifecycleOwner) { number -> - handleEntriesTotal(number) - } - viewModel.passes.observe(viewLifecycleOwner) { passesResult -> - handleNewPasses(passesResult) - } - getNavResult>(R.id.nav_passes, "prefs") { prefs -> - viewModel.calculatePasses(prefs.first, prefs.second) - } - getNavResult>(R.id.nav_passes, "selection") { items -> - viewModel.calculatePasses(selection = items) - } - } - - private fun handleEntriesTotal(number: Int) { - binding?.run { - if (number > 0) { - passesFab.clickWithDebounce { - val direction = PassesFragmentDirections.globalToEntries() - findNavController().navigate(direction) - } - } else { - passesFab.clickWithDebounce { - val errorMessage = getString(R.string.passes_empty_db) - Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show() - } - } - } - } - - private fun handleNewPasses(state: DataState>) { - binding?.run { - when (state) { - is DataState.Success -> { - passesAdapter.submitList(state.data) - tickMainTimer(state.data) - if (state.data.isNotEmpty()) { // show new passes list - passesEmpty.visibility = View.INVISIBLE - passesProgress.visibility = View.INVISIBLE - } else { // show no passes message - passesEmpty.visibility = View.VISIBLE - passesProgress.visibility = View.INVISIBLE - } - } - is DataState.Loading -> { - passesEmpty.visibility = View.INVISIBLE - passesProgress.visibility = View.VISIBLE - passesTimer.text = 0L.toTimerString() - } - else -> {} - } - } - } - - private fun tickMainTimer(passes: List) { - 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() - } - passesBtnRefresh.isEnabled = true - } - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesScreen.kt index 0f867ed2..aa0d56ae 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesScreen.kt @@ -15,6 +15,8 @@ import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -42,14 +44,23 @@ fun PassesScreen(viewModel: PassesViewModel = hiltViewModel()) { val state = viewModel.passes.observeAsState() val timerText = viewModel.timerText.observeAsState() val isRefreshing = state.value is DataState.Loading - val refreshState = rememberPullRefreshState( - refreshing = isRefreshing, + val refreshState = rememberPullRefreshState(refreshing = isRefreshing, onRefresh = { viewModel.calculatePasses() }) val value = state.value var passes = emptyList() if (value is DataState.Success) passes = value.data + val showDialog = rememberSaveable { mutableStateOf(false) } + val toggleDialog = { showDialog.value = showDialog.value.not() } + if (showDialog.value) { + FilterDialog(viewModel.getHoursAhead(), + viewModel.getMinElevation(), + { toggleDialog() }) { hours, elev -> + viewModel.calculatePasses(hours, elev) + } + } + Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { ElevatedCard(modifier = Modifier.height(52.dp)) { Row( @@ -57,7 +68,7 @@ fun PassesScreen(viewModel: PassesViewModel = hiltViewModel()) { horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxSize() ) { - IconButton(onClick = {}) { + IconButton(onClick = { toggleDialog() }) { Icon( painter = painterResource(id = R.drawable.ic_filter), contentDescription = null diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt index acc5704d..21537f53 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt @@ -43,6 +43,10 @@ class PassesViewModel @Inject constructor( val passes: LiveData>> = _passes val timerText: LiveData> = _timerText + fun getHoursAhead() = settings.getHoursAhead() + + fun getMinElevation() = settings.getMinElevation() + init { viewModelScope.launch { satelliteManager.calculatedPasses.collect { passes -> diff --git a/app/src/main/res/layout/dialog_filter.xml b/app/src/main/res/layout/dialog_filter.xml deleted file mode 100644 index cb09370c..00000000 --- a/app/src/main/res/layout/dialog_filter.xml +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - -