Skip to content

Commit

Permalink
[feat]: log lines selection affects toolbar
Browse files Browse the repository at this point in the history
  • Loading branch information
F0x1d authored Oct 14, 2023
1 parent 5edd92d commit 48e65c8
Show file tree
Hide file tree
Showing 19 changed files with 139 additions and 74 deletions.
13 changes: 7 additions & 6 deletions app/src/main/java/com/f0x1d/logfox/adapter/LogsAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.f0x1d.logfox.utils.preferences.AppPreferences

class LogsAdapter(
private val appPreferences: AppPreferences,
private val selectedItem: (LogLine, Boolean) -> Unit,
private val copyLog: (LogLine) -> Unit
): BaseListAdapter<LogLine, ItemLogBinding>(LOGLINE_DIFF) {

Expand All @@ -23,7 +24,11 @@ class LogsAdapter(
}

val expandedStates = mutableMapOf<Long, Boolean>()
val selectedItems = mutableListOf<LogLine>()
var selectedItems = emptyList<LogLine>()
set(value) {
field = value
notifyItemRangeChanged(0, itemCount)
}

var textSize = appPreferences.logsTextSize.toFloat()
set(value) {
Expand All @@ -43,11 +48,7 @@ class LogsAdapter(

override fun createHolder(layoutInflater: LayoutInflater, parent: ViewGroup) = LogViewHolder(
binding = ItemLogBinding.inflate(layoutInflater, parent, false),
selectedItem = selectedItem,
copyLog = copyLog
)

fun clearSelected() {
selectedItems.clear()
notifyItemRangeChanged(0, itemCount)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ fun Context.shareFileIntent(file: File) = baseShareIntent {
private fun Context.baseShareIntent(block: (Intent) -> Unit) {
try {
val intent = Intent(Intent.ACTION_SEND)
block.invoke(intent)
block(intent)

startActivity(Intent.createChooser(intent, getString(R.string.share)))
} catch (e: Exception) {
Expand All @@ -95,7 +95,7 @@ private fun Context.baseShareIntent(block: (Intent) -> Unit) {
}

fun Context.catchingNotNumber(block: () -> Unit) = try {
block.invoke()
block()
} catch (e: NumberFormatException) {
toast(R.string.this_is_not_a_number)
}
Expand All @@ -111,7 +111,7 @@ else
true

fun Context.doIfPermitted(block: NotificationManagerCompat.() -> Unit) = if (hasNotificationsPermission())
block.invoke(notificationManagerCompat)
block(notificationManagerCompat)
else
Unit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ else
fun <T : BroadcastReceiver> Context.makeBroadcastPendingIntent(id: Int, clazz: Class<T>, setup: Intent.() -> Unit) = PendingIntent.getBroadcast(
this,
id,
Intent(this, clazz).also { setup.invoke(it) },
Intent(this, clazz).also { setup(it) },
pendingIntentFlags
)

Expand All @@ -39,7 +39,7 @@ fun <T : BroadcastReceiver> Context.makeBroadcastPendingIntent(id: Int, clazz: C
fun <T : Service> Context.makeServicePendingIntent(id: Int, clazz: Class<T>, setup: Intent.() -> Unit) = PendingIntent.getService(
this,
id,
Intent(this, clazz).also { setup.invoke(it) },
Intent(this, clazz).also { setup(it) },
pendingIntentFlags
)

Expand All @@ -51,7 +51,7 @@ fun <T : Service> Context.makeServicePendingIntent(id: Int, clazz: Class<T>, ext
fun <T : Activity> Context.makeActivityPendingIntent(id: Int, clazz: Class<T>, setup: Intent.() -> Unit) = PendingIntent.getActivity(
this,
id,
Intent(this, clazz).also { setup.invoke(it) },
Intent(this, clazz).also { setup(it) },
pendingIntentFlags
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fun BaseViewModel.crashToZip(uri: Uri, appCrash: AppCrash) = toZip(uri) {
inline fun BaseViewModel.toZip(uri: Uri, crossinline block: OutputStream.() -> Unit) {
launchCatching(Dispatchers.IO) {
ctx.contentResolver.openOutputStream(uri)?.also {
block.invoke(it)
block(it)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import android.view.Menu
import android.view.MenuItem

fun Menu.setClickListenerOn(id: Int, block: (MenuItem) -> Unit) = findItem(id).setOnMenuItemClickListener {
block.invoke(it)
block(it)
return@setOnMenuItemClickListener true
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
fun Preference.setupAsEditTextPreference(setup: (DialogTextBinding) -> Unit, setupDialog: MaterialAlertDialogBuilder.() -> Unit, get: () -> String?, save: (String?) -> Unit) {
setOnPreferenceClickListener {
val dialogBinding = DialogTextBinding.inflate(LayoutInflater.from(context))
setup.invoke(dialogBinding)
setup(dialogBinding)

dialogBinding.text.setText(get.invoke())
dialogBinding.text.setText(get())

MaterialAlertDialogBuilder(context)
.setTitle(title)
.setView(dialogBinding.root)
.setPositiveButton(android.R.string.ok) { dialog, which ->
save.invoke(dialogBinding.text.text?.toString())
save(dialogBinding.text.text?.toString())
}
.setNegativeButton(R.string.close, null)
.apply(setupDialog)
Expand All @@ -40,7 +40,7 @@ fun Preference.setupAsListPreference(setupDialog: MaterialAlertDialogBuilder.()
.setTitle(title)
.setSingleChoiceItems(items, selected()) { dialog, which ->
dialog.cancel()
onSelected.invoke(which)
onSelected(which)
}
.setPositiveButton(R.string.close, null)
.apply(setupDialog)
Expand All @@ -63,6 +63,6 @@ inline fun <reified T> Preference.observeAndUpdateSummary(appPreferences: AppPre

inline fun <reified T> Preference.observeAndUpdateSummary(appPreferences: AppPreferences, observer: LifecycleOwner, defValue: T, crossinline block: (T) -> Unit) {
appPreferences.asLiveData(key, defValue).observe(observer) {
block.invoke(it)
block(it)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import kotlinx.coroutines.launch

abstract class BaseRepository {
protected fun onAppScope(block: suspend CoroutineScope.() -> Unit) = LogFoxApp.applicationScope.launch(Dispatchers.IO) {
block.invoke(this)
block(this)
}

protected fun runOnAppScope(block: suspend CoroutineScope.() -> Unit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class RecordingsRepository @Inject constructor(
}

withContext(Dispatchers.Main) {
recordingSaved.invoke(logRecording)
recordingSaved(logRecording)
}

recordingStateFlow.update { RecordingState.IDLE }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ abstract class BaseCrashDetector(private val collected: suspend (AppCrash) -> Un
if (stillCollecting(line)) {
collectedLines.add(line)
} else {
linesModifier.invoke(collectedLines)
collected.invoke(logsToAppCrash(packageFromCollected(collectedLines), crashType, collectedLines))
linesModifier(collectedLines)
collected(logsToAppCrash(packageFromCollected(collectedLines), crashType, collectedLines))

collecting = false
collectedFirstLine = null
Expand Down
105 changes: 77 additions & 28 deletions app/src/main/java/com/f0x1d/logfox/ui/fragment/LogsFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.hilt.navigation.fragment.hiltNavGraphViewModels
import androidx.lifecycle.asLiveData
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
Expand All @@ -26,17 +27,18 @@ import com.f0x1d.logfox.viewmodel.LogsViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import dev.chrisbanes.insetter.applyInsetter
import kotlinx.coroutines.flow.update

@AndroidEntryPoint
class LogsFragment: BaseViewModelFragment<LogsViewModel, FragmentLogsBinding>(), SharedPreferences.OnSharedPreferenceChangeListener {

override val viewModel by hiltNavGraphViewModels<LogsViewModel>(R.id.logsFragment)

private val adapter by lazy {
LogsAdapter(viewModel.appPreferences) {
LogsAdapter(viewModel.appPreferences, selectedItem = viewModel::selectLine, copyLog = {
requireContext().copyText(it.original)
snackbar(R.string.text_copied)
}
})
}
private var changingState = false

Expand All @@ -45,6 +47,13 @@ class LogsFragment: BaseViewModelFragment<LogsViewModel, FragmentLogsBinding>(),
viewModel.stopViewingFile()
}
}
private val clearSelectionBackPressedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
viewModel.selectedItems.update {
emptyList()
}
}
}

override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentLogsBinding.inflate(inflater, container, false)

Expand Down Expand Up @@ -80,14 +89,11 @@ class LogsFragment: BaseViewModelFragment<LogsViewModel, FragmentLogsBinding>(),
setClickListenerOn(R.id.selected_item) {
showSelectedDialog()
}
setClickListenerOn(R.id.clear_selected_item) {
adapter.clearSelected()
}
setClickListenerOn(R.id.clear_item) {
viewModel.clearLogs()

adapter.submitList(null)
adapter.clearSelected()
viewModel.selectedItems.update { emptyList() }
}
setClickListenerOn(R.id.service_status_item) {
requireContext().apply {
Expand All @@ -104,6 +110,14 @@ class LogsFragment: BaseViewModelFragment<LogsViewModel, FragmentLogsBinding>(),
requireContext().sendKillApp()
}
}
binding.toolbar.setNavigationOnClickListener {
viewModel.apply {
if (selectedItems.value.isNotEmpty()) selectedItems.update {
emptyList()
} else if (viewModel.viewingFile.value)
viewModel.stopViewingFile()
}
}

binding.logsRecycler.layoutManager = LinearLayoutManager(requireContext())
binding.logsRecycler.itemAnimator = null
Expand All @@ -128,37 +142,30 @@ class LogsFragment: BaseViewModelFragment<LogsViewModel, FragmentLogsBinding>(),
scrollLogToBottom()
}

viewModel.viewingFileData.observe(viewLifecycleOwner) {
viewModel.viewingFile.asLiveData().observe(viewLifecycleOwner) {
stopViewingFileBackPressedCallback.isEnabled = it
setupToolbarForFile(it)
}

binding.toolbar.apply {
menu.findItem(R.id.pause_item).isVisible = !it
viewModel.selectedItems.asLiveData().observe(viewLifecycleOwner) {
val selecting = it.isNotEmpty()

title = when (it) {
true -> viewModel.fileUri?.readFileName(requireContext())
clearSelectionBackPressedCallback.isEnabled = selecting
adapter.selectedItems = it

else -> getString(R.string.app_name)
}

if (it) setNavigationIcon(R.drawable.ic_clear)
else {
navigationIcon = null
}

setNavigationOnClickListener {
viewModel.stopViewingFile()
}
}
setupToolbarForSelection(selecting, it.size)
}

viewModel.logs.observe(viewLifecycleOwner) {
// TODO: Understand why all ListAdapters stop working after 15 minutes without submitting null

adapter.submitList(null)
adapter.submitList(it ?: return@observe) {
scrollLogToBottom()
}
}

viewModel.pausedData.observe(viewLifecycleOwner) { paused ->
viewModel.paused.asLiveData().observe(viewLifecycleOwner) { paused ->
changingState = true
binding.toolbar.menu.findItem(R.id.pause_item)
.setIcon(if (paused) R.drawable.ic_play else R.drawable.ic_pause)
Expand All @@ -176,10 +183,10 @@ class LogsFragment: BaseViewModelFragment<LogsViewModel, FragmentLogsBinding>(),
binding.toolbar.menu.findItem(R.id.service_status_item).setTitle(if (running) R.string.stop_service else R.string.start_service)
}

requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
stopViewingFileBackPressedCallback
)
requireActivity().onBackPressedDispatcher.apply {
addCallback(viewLifecycleOwner, stopViewingFileBackPressedCallback)
addCallback(viewLifecycleOwner, clearSelectionBackPressedCallback)
}
}

override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
Expand All @@ -196,6 +203,48 @@ class LogsFragment: BaseViewModelFragment<LogsViewModel, FragmentLogsBinding>(),
viewModel.appPreferences.unregisterListener(this)
}

private fun setupToolbarForFile(viewing: Boolean) = binding.toolbar.apply {
menu.findItem(R.id.pause_item).isVisible = !viewing

title = when (viewing) {
true -> viewModel.fileUri?.readFileName(requireContext())

else -> getString(R.string.app_name)
}

if (viewing) setNavigationIcon(R.drawable.ic_clear)
else {
navigationIcon = null
}
}

private fun setupToolbarForSelection(selecting: Boolean, count: Int) = binding.toolbar.apply {
val setVisibility = { itemId: Int, visible: Boolean ->
menu.findItem(itemId).isVisible = visible
}
val visibleDuringSelection = { itemId: Int -> setVisibility(itemId, selecting) }
val invisibleDuringSelection = { itemId: Int -> setVisibility(itemId, !selecting) }

invisibleDuringSelection(R.id.search_item)
invisibleDuringSelection(R.id.filters_item)
visibleDuringSelection(R.id.selected_item)
invisibleDuringSelection(R.id.clear_item)
invisibleDuringSelection(R.id.service_status_item)
invisibleDuringSelection(R.id.restart_logging_item)

title = when {
selecting -> count.toString()
viewModel.viewingFile.value -> viewModel.fileUri?.readFileName(requireContext())

else -> getString(R.string.app_name)
}

if (selecting) setNavigationIcon(R.drawable.ic_clear)
else if (!viewModel.viewingFile.value) {
navigationIcon = null
}
}

private fun scrollLogToBottom() {
binding.logsRecycler.stopScroll()
binding.logsRecycler.scrollToPosition(adapter.itemCount - 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ class CrashViewHolder(

init {
binding.root.setOnClickListener {
click.invoke(currentItem ?: return@setOnClickListener)
click(currentItem ?: return@setOnClickListener)
}
binding.deleteButton.setOnClickListener {
delete.invoke(currentItem ?: return@setOnClickListener)
delete(currentItem ?: return@setOnClickListener)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ class FilterViewHolder(
): BaseViewHolder<UserFilter, ItemFilterBinding>(binding) {

private val checkedListener = OnlyUserCheckedChangeListener(binding.enabledBox) { button, isChecked ->
checked.invoke(currentItem ?: return@OnlyUserCheckedChangeListener, isChecked)
checked(currentItem ?: return@OnlyUserCheckedChangeListener, isChecked)
}

init {
binding.root.setOnClickListener {
click.invoke(currentItem ?: return@setOnClickListener)
click(currentItem ?: return@setOnClickListener)
}
binding.deleteButton.setOnClickListener {
delete.invoke(currentItem ?: return@setOnClickListener)
delete(currentItem ?: return@setOnClickListener)
}
binding.enabledBox.setOnCheckedChangeListener(checkedListener)
}
Expand Down
Loading

0 comments on commit 48e65c8

Please sign in to comment.