Show snackbar in MessageList to get to "What's new" when the app was updated
This commit is contained in:
parent
1a12ab3177
commit
0073913279
14 changed files with 302 additions and 12 deletions
|
@ -2,6 +2,8 @@ package com.fsck.k9
|
|||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.fsck.k9.ui.changelog.ChangeLogMode
|
||||
import com.fsck.k9.ui.changelog.ChangelogViewModel
|
||||
import com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity
|
||||
import com.fsck.k9.ui.endtoend.AutocryptKeyTransferPresenter
|
||||
import com.fsck.k9.ui.folders.FolderNameFormatter
|
||||
|
@ -39,6 +41,7 @@ class DependencyInjectionTest : AutoCloseKoinTest() {
|
|||
create<AutocryptKeyTransferPresenter> { parametersOf(lifecycleOwner, autocryptTransferView) }
|
||||
create<FolderNameFormatter> { parametersOf(RuntimeEnvironment.application) }
|
||||
create<SizeFormatter> { parametersOf(RuntimeEnvironment.application) }
|
||||
create<ChangelogViewModel> { parametersOf(ChangeLogMode.CHANGE_LOG) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -290,6 +290,10 @@
|
|||
android:name=".ui.messagesource.MessageSourceActivity"
|
||||
android:label="@string/show_headers_action" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.changelog.RecentChangesActivity"
|
||||
android:label="@string/changelog_recent_changes_title" />
|
||||
|
||||
<receiver
|
||||
android:name=".service.StorageReceiver"
|
||||
android:enabled="true">
|
||||
|
|
|
@ -2,6 +2,8 @@ package com.fsck.k9
|
|||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.fsck.k9.ui.changelog.ChangeLogMode
|
||||
import com.fsck.k9.ui.changelog.ChangelogViewModel
|
||||
import com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity
|
||||
import com.fsck.k9.ui.endtoend.AutocryptKeyTransferPresenter
|
||||
import com.fsck.k9.ui.folders.FolderNameFormatter
|
||||
|
@ -39,6 +41,7 @@ class DependencyInjectionTest : AutoCloseKoinTest() {
|
|||
create<AutocryptKeyTransferPresenter> { parametersOf(lifecycleOwner, autocryptTransferView) }
|
||||
create<FolderNameFormatter> { parametersOf(RuntimeEnvironment.application) }
|
||||
create<SizeFormatter> { parametersOf(RuntimeEnvironment.application) }
|
||||
create<ChangelogViewModel> { parametersOf(ChangeLogMode.CHANGE_LOG) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.fsck.k9.activity
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
@ -21,6 +22,7 @@ import androidx.drawerlayout.widget.DrawerLayout
|
|||
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.lifecycle.Observer
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.Account.SortType
|
||||
import com.fsck.k9.K9
|
||||
|
@ -47,6 +49,8 @@ import com.fsck.k9.ui.K9Drawer
|
|||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.ui.base.K9Activity
|
||||
import com.fsck.k9.ui.base.Theme
|
||||
import com.fsck.k9.ui.changelog.RecentChangesActivity
|
||||
import com.fsck.k9.ui.changelog.RecentChangesViewModel
|
||||
import com.fsck.k9.ui.managefolders.ManageFoldersActivity
|
||||
import com.fsck.k9.ui.messagelist.DefaultFolderProvider
|
||||
import com.fsck.k9.ui.messagesource.MessageSourceActivity
|
||||
|
@ -59,8 +63,10 @@ import com.fsck.k9.ui.permissions.Permission
|
|||
import com.fsck.k9.ui.permissions.PermissionUiHelper
|
||||
import com.fsck.k9.view.ViewSwitcher
|
||||
import com.fsck.k9.view.ViewSwitcher.OnSwitchCompleteListener
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.mikepenz.materialdrawer.util.getOptimalDrawerWidth
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import timber.log.Timber
|
||||
|
@ -78,6 +84,8 @@ open class MessageList :
|
|||
OnSwitchCompleteListener,
|
||||
PermissionUiHelper {
|
||||
|
||||
private val recentChangesViewModel: RecentChangesViewModel by viewModel()
|
||||
|
||||
protected val searchStatusManager: SearchStatusManager by inject()
|
||||
private val preferences: Preferences by inject()
|
||||
private val channelUtils: NotificationChannelManager by inject()
|
||||
|
@ -119,6 +127,7 @@ open class MessageList :
|
|||
*/
|
||||
private var messageListWasDisplayed = false
|
||||
private var viewSwitcher: ViewSwitcher? = null
|
||||
private lateinit var recentChangesSnackbar: Snackbar
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -182,6 +191,7 @@ open class MessageList :
|
|||
initializeLayout()
|
||||
initializeFragments()
|
||||
displayViews()
|
||||
initializeRecentChangesSnackbar()
|
||||
channelUtils.updateChannels()
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
|
@ -324,6 +334,31 @@ open class MessageList :
|
|||
}
|
||||
}
|
||||
|
||||
private val shouldShowRecentChangesHintObserver = Observer<Boolean> { showRecentChangesHint ->
|
||||
val recentChangesSnackbarVisible = recentChangesSnackbar.isShown
|
||||
if (showRecentChangesHint && !recentChangesSnackbarVisible) {
|
||||
recentChangesSnackbar.show()
|
||||
} else if (!showRecentChangesHint && recentChangesSnackbarVisible) {
|
||||
recentChangesSnackbar.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ShowToast")
|
||||
private fun initializeRecentChangesSnackbar() {
|
||||
recentChangesSnackbar = Snackbar
|
||||
.make(findViewById(R.id.container), R.string.changelog_snackbar_text, Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.okay_action) { launchRecentChangesActivity() }
|
||||
|
||||
recentChangesViewModel.shouldShowRecentChangesHint.observe(this, shouldShowRecentChangesHintObserver)
|
||||
}
|
||||
|
||||
private fun launchRecentChangesActivity() {
|
||||
recentChangesViewModel.shouldShowRecentChangesHint.removeObserver(shouldShowRecentChangesHintObserver)
|
||||
|
||||
val intent = Intent(this, RecentChangesActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun decodeExtras(intent: Intent): Boolean {
|
||||
val launchData = decodeExtrasToLaunchData(intent)
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package com.fsck.k9.ui.changelog
|
||||
|
||||
import android.content.Context
|
||||
import de.cketti.changelog.ChangeLog
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Manages a [ChangeLog] instance and notifies when its state changes.
|
||||
*/
|
||||
class ChangeLogManager(
|
||||
private val context: Context,
|
||||
private val appCoroutineScope: CoroutineScope,
|
||||
private val backgroundDispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) {
|
||||
private val mutableChangeLogFlow = MutableSharedFlow<ChangeLog>(replay = 1)
|
||||
|
||||
val changeLog: ChangeLog by lazy {
|
||||
ChangeLog.newInstance(context).also { changeLog ->
|
||||
mutableChangeLogFlow.tryEmit(changeLog)
|
||||
}
|
||||
}
|
||||
|
||||
val changeLogFlow: Flow<ChangeLog> by lazy {
|
||||
mutableChangeLogFlow.onSubscription {
|
||||
withContext(backgroundDispatcher) {
|
||||
// Make sure the changeLog property is initialized now if it hasn't happened before
|
||||
changeLog
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun writeCurrentVersion() {
|
||||
appCoroutineScope.launch(backgroundDispatcher) {
|
||||
changeLog.writeCurrentVersion()
|
||||
|
||||
mutableChangeLogFlow.emit(changeLog)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CheckBox
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -11,13 +12,17 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
|||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.ui.base.loader.observeLoading
|
||||
import de.cketti.changelog.ReleaseItem
|
||||
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 viewModel: ChangelogViewModel by inject()
|
||||
private val viewModel: ChangelogViewModel by viewModel {
|
||||
val mode = arguments?.getSerializable(ARG_MODE) as? ChangeLogMode ?: error("Missing argument '$ARG_MODE'")
|
||||
parametersOf(mode)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_changelog, container, false)
|
||||
|
@ -34,6 +39,28 @@ class ChangelogFragment : Fragment() {
|
|||
) { changeLog ->
|
||||
listView.adapter = ChangelogAdapter(changeLog)
|
||||
}
|
||||
|
||||
setUpShowRecentChangesCheckbox(view)
|
||||
}
|
||||
|
||||
private fun setUpShowRecentChangesCheckbox(view: View) {
|
||||
val showRecentChangesCheckBox = view.findViewById<CheckBox>(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
|
||||
}
|
||||
}
|
||||
showRecentChangesCheckBox.setOnCheckedChangeListener { _, isChecked ->
|
||||
viewModel.setShowRecentChanges(isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ARG_MODE = "mode"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,46 @@
|
|||
package com.fsck.k9.ui.changelog
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import com.fsck.k9.preferences.GeneralSettingsManager
|
||||
import com.fsck.k9.ui.base.loader.LoaderState
|
||||
import com.fsck.k9.ui.base.loader.liveDataLoader
|
||||
import de.cketti.changelog.ChangeLog
|
||||
import de.cketti.changelog.ReleaseItem
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
private typealias ChangeLogState = LoaderState<List<ReleaseItem>>
|
||||
|
||||
class ChangelogViewModel(private val context: Context) : ViewModel() {
|
||||
class ChangelogViewModel(
|
||||
private val generalSettingsManager: GeneralSettingsManager,
|
||||
private val changeLogManager: ChangeLogManager,
|
||||
private val mode: ChangeLogMode
|
||||
) : ViewModel() {
|
||||
val showRecentChangesState: LiveData<Boolean> =
|
||||
generalSettingsManager.getSettingsFlow()
|
||||
.map { it.showRecentChanges }
|
||||
.distinctUntilChanged()
|
||||
.asLiveData()
|
||||
|
||||
val changelogState: LiveData<ChangeLogState> = liveDataLoader {
|
||||
val changeLog = ChangeLog.newInstance(context)
|
||||
changeLog.changeLog
|
||||
val changeLog = changeLogManager.changeLog
|
||||
when (mode) {
|
||||
ChangeLogMode.CHANGE_LOG -> changeLog.changeLog
|
||||
ChangeLogMode.RECENT_CHANGES -> changeLog.recentChanges
|
||||
}
|
||||
}
|
||||
|
||||
fun setShowRecentChanges(showRecentChanges: Boolean) {
|
||||
generalSettingsManager.setShowRecentChanges(showRecentChanges)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
changeLogManager.writeCurrentVersion()
|
||||
}
|
||||
}
|
||||
|
||||
enum class ChangeLogMode {
|
||||
CHANGE_LOG,
|
||||
RECENT_CHANGES
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package com.fsck.k9.ui.changelog
|
||||
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
||||
val changelogUiModule = module {
|
||||
viewModel { ChangelogViewModel(context = get()) }
|
||||
single { ChangeLogManager(context = get(), appCoroutineScope = get(named("AppCoroutineScope"))) }
|
||||
viewModel { (mode: ChangeLogMode) ->
|
||||
ChangelogViewModel(generalSettingsManager = get(), changeLogManager = get(), mode = mode)
|
||||
}
|
||||
viewModel { RecentChangesViewModel(generalSettingsManager = get(), changeLogManager = get()) }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package com.fsck.k9.ui.changelog
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.commit
|
||||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.ui.base.K9Activity
|
||||
|
||||
class RecentChangesActivity : K9Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setLayout(R.layout.activity_recent_changes)
|
||||
setTitle(R.string.changelog_recent_changes_title)
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
val fragment = ChangelogFragment().apply {
|
||||
arguments = bundleOf(ChangelogFragment.ARG_MODE to ChangeLogMode.RECENT_CHANGES)
|
||||
}
|
||||
supportFragmentManager.commit {
|
||||
add(R.id.fragment_container, fragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == android.R.id.home) {
|
||||
finish()
|
||||
true
|
||||
} else {
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.fsck.k9.ui.changelog
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import com.fsck.k9.preferences.GeneralSettingsManager
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class RecentChangesViewModel(
|
||||
private val generalSettingsManager: GeneralSettingsManager,
|
||||
changeLogManager: ChangeLogManager
|
||||
) : ViewModel() {
|
||||
val shouldShowRecentChangesHint = changeLogManager.changeLogFlow.flatMapLatest { changeLog ->
|
||||
if (changeLog.isFirstRun && !changeLog.isFirstRunEver) {
|
||||
getShowRecentChangesFlow()
|
||||
} else {
|
||||
flowOf(false)
|
||||
}
|
||||
}.asLiveData()
|
||||
|
||||
private fun getShowRecentChangesFlow(): Flow<Boolean> {
|
||||
return generalSettingsManager.getSettingsFlow()
|
||||
.map { generalSettings -> generalSettings.showRecentChanges }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/toolbar" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -8,10 +8,13 @@
|
|||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/changelog_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:overScrollMode="ifContentScrolls"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toTopOf="@+id/show_recent_changes_checkbox"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ProgressBar
|
||||
|
@ -22,6 +25,10 @@
|
|||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
|
@ -31,5 +38,34 @@
|
|||
android:layout_gravity="center"
|
||||
android:text="@string/changelog_loading_error"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/bottomBar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="?attr/bottomBarBackground"
|
||||
android:elevation="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/changelog_list" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/show_recent_changes_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:elevation="8dp"
|
||||
android:text="@string/changelog_show_recent_changes"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -86,6 +86,24 @@
|
|||
android:id="@+id/changelogScreen"
|
||||
android:name="com.fsck.k9.ui.changelog.ChangelogFragment"
|
||||
android:label="@string/changelog_title"
|
||||
tools:layout="@layout/fragment_changelog"/>
|
||||
tools:layout="@layout/fragment_changelog">
|
||||
|
||||
<argument
|
||||
android:name="mode"
|
||||
app:argType="com.fsck.k9.ui.changelog.ChangeLogMode"
|
||||
android:defaultValue="CHANGE_LOG" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/recentChangesScreen"
|
||||
android:name="com.fsck.k9.ui.changelog.ChangelogFragment"
|
||||
android:label="@string/changelog_recent_changes_title"
|
||||
tools:layout="@layout/fragment_changelog">
|
||||
|
||||
<argument
|
||||
android:name="mode"
|
||||
app:argType="com.fsck.k9.ui.changelog.ChangeLogMode"
|
||||
android:defaultValue="RECENT_CHANGES" />
|
||||
</fragment>
|
||||
|
||||
</navigation>
|
||||
|
|
|
@ -26,6 +26,9 @@
|
|||
<string name="changelog_title">Changelog</string>
|
||||
<string name="changelog_loading_error">Couldn\'t load the changelog.</string>
|
||||
<string name="changelog_version_title">Version %s</string>
|
||||
<string name="changelog_recent_changes_title">What\'s new</string>
|
||||
<string name="changelog_show_recent_changes">Show recent changes when app was updated</string>
|
||||
<string name="changelog_snackbar_text">Find out what\'s new in this release</string>
|
||||
|
||||
|
||||
<!-- Welcome message -->
|
||||
|
|
Loading…
Reference in a new issue