Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.fsck.k9.ui.changelog

import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.PreviewLightDark
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import de.cketti.changelog.ReleaseItem
import kotlinx.collections.immutable.persistentListOf

@Composable
@PreviewLightDark
fun ChangelogScreenPreview() {
PreviewWithThemesLightDark {
ChangelogScreen(
releaseItems = persistentListOf(
ReleaseItem.newInstance(
904,
"6.0.904",
"2024-06-27",
listOf(
"Fixed crash when opening an attachment",
"Updated Portuguese translation",
"Enhanced About screen UI",
),
),
ReleaseItem.newInstance(
903,
"6.0.903",
"2024-05-18",
listOf(
"Improved About screen Compose layout",
"Updated French translation",
"Minor UI polish",
),
),
ReleaseItem.newInstance(
902,
"6.0.902",
"2024-05-02",
listOf(
"Fixed alignment issue in About screen",
"Updated German translation",
"Improved accessibility labels",
),
),
ReleaseItem.newInstance(
901,
"6.0.901",
"2024-04-20",
listOf(
"Optimized Compose rendering for About screen",
"Updated Spanish translation",
"Reduced app startup time",
),
),
ReleaseItem.newInstance(
900,
"6.0.900",
"2024-04-05",
listOf(
"Initial About screen migration to Jetpack Compose",
"Updated French translation",
"Improved theme consistency",
),
),
ReleaseItem.newInstance(
899,
"6.0.899",
"2024-03-22",
listOf(
"Improved attachment preview performance",
"Updated Italian translation",
"Minor bug fixes",
),
),
ReleaseItem.newInstance(
888,
"6.0.888",
"2024-03-10",
listOf(
"Improved settings screen stability",
"Updated Japanese translation",
"UI improvements",
),
),
ReleaseItem.newInstance(
887,
"6.0.887",
"2024-02-28",
listOf(
"Improved notification handling",
"Updated Korean translation",
"General stability improvements",
),
),
),
showRecentChanges = true,
onShowRecentChangesCheck = {},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.fsck.k9.ui.changelog

import androidx.compose.runtime.Stable
import de.cketti.changelog.ReleaseItem
import kotlinx.collections.immutable.ImmutableList
import net.thunderbird.core.ui.contract.mvi.UnidirectionalViewModel

interface ChangelogContract {

interface ViewModel : UnidirectionalViewModel<State, Event, Effect>

@Stable
data class State(
val releaseItems: ImmutableList<ReleaseItem>,
val showRecentChanges: Boolean,
)

sealed interface Event {
data class OnShowRecentChangesCheck(val isChecked: Boolean) : Event
}

sealed interface Effect
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import app.k9mail.core.android.common.compat.BundleCompat
import com.fsck.k9.ui.R
import com.fsck.k9.ui.base.loader.observeLoading
import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.textview.MaterialTextView
import de.cketti.changelog.ReleaseItem
import kotlin.getValue
import net.thunderbird.core.ui.contract.mvi.observe
import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf

/**
* Displays the changelog entries in a scrolling list
*/
class ChangelogFragment : Fragment() {
private val themeProvider: FeatureThemeProvider by inject()
private val viewModel: ChangelogViewModel by viewModel {
val mode = arguments?.let {
BundleCompat.getSerializable(it, ARG_MODE, ChangeLogMode::class.java)
Expand All @@ -28,90 +28,27 @@ class ChangelogFragment : Fragment() {
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_changelog, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val listView = view.findViewById<RecyclerView>(R.id.changelog_list)

viewModel.changelogState.observeLoading(
owner = viewLifecycleOwner,
loadingView = view.findViewById(R.id.changelog_loading),
errorView = view.findViewById(R.id.changelog_error),
dataView = listView,
) { changeLog ->
listView.adapter = ChangelogAdapter(changeLog)
}

setUpShowRecentChangesCheckbox(view)
}

private fun setUpShowRecentChangesCheckbox(view: View) {
val showRecentChangesCheckBox = view.findViewById<MaterialCheckBox>(R.id.show_recent_changes_checkbox)
var isInitialValue = true
viewModel.showRecentChangesState.observe(viewLifecycleOwner) { showRecentChanges ->
showRecentChangesCheckBox.isChecked = showRecentChanges
if (isInitialValue) {
// Don't animate when setting initial value
showRecentChangesCheckBox.jumpDrawablesToCurrentState()
isInitialValue = false
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed,
)

setContent {
val (state, dispatch) = viewModel.observe {}
themeProvider.WithTheme {
ChangelogScreen(
releaseItems = state.value.releaseItems,
showRecentChanges = state.value.showRecentChanges,
onShowRecentChangesCheck = {
dispatch(ChangelogContract.Event.OnShowRecentChangesCheck(state.value.showRecentChanges))
},
)
}
}
}
showRecentChangesCheckBox.setOnCheckedChangeListener { _, isChecked ->
viewModel.setShowRecentChanges(isChecked)
}
}

companion object {
const val ARG_MODE = "mode"
}
}

class ChangelogAdapter(releaseItems: List<ReleaseItem>) : RecyclerView.Adapter<ViewHolder>() {
private val items = releaseItems.flatMap { listOf(it) + it.changes }

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(viewType, parent, false)
return when (viewType) {
R.layout.changelog_list_release_item -> ReleaseItemViewHolder(view)
R.layout.changelog_list_change_item -> ChangeItemViewHolder(view)
else -> error("Unsupported view type: $viewType")
}
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (val item = items[position]) {
is ReleaseItem -> {
val viewHolder = holder as ReleaseItemViewHolder
val context = viewHolder.versionName.context
viewHolder.versionName.text = context.getString(R.string.changelog_version_title, item.versionName)
viewHolder.versionDate.text = item.date
}

is String -> {
val viewHolder = holder as ChangeItemViewHolder
viewHolder.changeText.text = item
}
}
}

override fun getItemViewType(position: Int): Int {
return when (items[position]) {
is ReleaseItem -> R.layout.changelog_list_release_item
is String -> R.layout.changelog_list_change_item
else -> error("Unsupported item type: ${items[position]}")
}
}

override fun getItemCount(): Int = items.size
}

class ReleaseItemViewHolder(view: View) : ViewHolder(view) {
val versionName: MaterialTextView = view.findViewById(R.id.version_name)
val versionDate: MaterialTextView = view.findViewById(R.id.version_date)
}

class ChangeItemViewHolder(view: View) : ViewHolder(view) {
val changeText: MaterialTextView = view.findViewById(R.id.change_text)
}
Loading
Loading