Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2870] Add an in-app changelog #3810

Merged
Merged
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
12 changes: 12 additions & 0 deletions app/src/main/assets/changelog/changelog-ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"changelog": [
{
"date": "10/02/2021",
"code": 350,
"name": "3.5.0",
"items": [
"Добавили экран \"Что нового\""
]
}
]
}
12 changes: 12 additions & 0 deletions app/src/main/assets/changelog/changelog.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"changelog": [
{
"date": "10/02/2021",
"code": 350,
"name": "3.5.0",
"items": [
"Added changelog screen"
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import openfoodfacts.github.scrachx.openfood.customtabs.CustomTabActivityHelper
import openfoodfacts.github.scrachx.openfood.customtabs.CustomTabsHelper
import openfoodfacts.github.scrachx.openfood.customtabs.WebViewFallback
import openfoodfacts.github.scrachx.openfood.databinding.ActivityMainBinding
import openfoodfacts.github.scrachx.openfood.features.changelog.ChangelogDialog
import openfoodfacts.github.scrachx.openfood.features.LoginActivity.Companion.LoginContract
import openfoodfacts.github.scrachx.openfood.features.adapters.PhotosAdapter
import openfoodfacts.github.scrachx.openfood.features.additives.AdditiveListActivity
Expand Down Expand Up @@ -420,6 +421,10 @@ class MainActivity : BaseActivity(), NavigationDrawerListener {
binding.bottomNavigationInclude.bottomNavigation.selectNavigationItem(0)
binding.bottomNavigationInclude.bottomNavigation.installBottomNavigation(this)
handleIntent(intent)

if (isFlavors(AppFlavors.OFF)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only for OFF now

ChangelogDialog.newInstance(BuildConfig.DEBUG).presentAutomatically(this)
}
}

private fun swapToHomeFragment() {
Expand Down Expand Up @@ -894,4 +899,4 @@ class MainActivity : BaseActivity(), NavigationDrawerListener {
const val PRODUCT_SEARCH_KEY = "product_search"
private val LOG_TAG = MainActivity::class.simpleName!!
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package openfoodfacts.github.scrachx.openfood.features.changelog

import com.fasterxml.jackson.annotation.JsonProperty

data class Changelog(
@JsonProperty("changelog") val versions: List<Version>,
) {
data class Version(
@JsonProperty("date") var date: String,
@JsonProperty("code") var code: Long,
@JsonProperty("name") var name: String,
@JsonProperty("items") var items: List<String>,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package openfoodfacts.github.scrachx.openfood.features.changelog

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import openfoodfacts.github.scrachx.openfood.R

class ChangelogAdapter(private val items: List<ChangelogListItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

companion object {
private const val VIEW_TYPE_HEADER = 1
private const val VIEW_TYPE_ITEM = 2
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
VIEW_TYPE_HEADER -> HeaderViewHolder(inflater.inflate(R.layout.view_changelog_item_header, parent, false))
VIEW_TYPE_ITEM -> ItemViewHolder(inflater.inflate(R.layout.view_changelog_item, parent, false))
else -> throw IllegalStateException("Unexpected value: $viewType")
}
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder.itemViewType) {
VIEW_TYPE_HEADER -> (holder as HeaderViewHolder).bind((items[position] as ChangelogListItem.Header))
VIEW_TYPE_ITEM -> (holder as ItemViewHolder).bind((items[position] as ChangelogListItem.Item))
else -> throw IllegalStateException("Unexpected value: " + holder.itemViewType)
}
}

override fun getItemCount(): Int = items.size

override fun getItemViewType(position: Int): Int {
return if (items[position] is ChangelogListItem.Header) VIEW_TYPE_HEADER else VIEW_TYPE_ITEM
}

private class HeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val versionLabel: TextView = view.findViewById(R.id.changelog_list_header_version)
private val dateLabel: TextView = view.findViewById(R.id.changelog_list_header_date)

fun bind(item: ChangelogListItem.Header) {
versionLabel.text = item.version
dateLabel.text = item.date
}
}

private class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val itemLabel: TextView = itemView.findViewById(R.id.changelog_list_item_label)

fun bind(item: ChangelogListItem.Item) {
itemLabel.text = item.description
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package openfoodfacts.github.scrachx.openfood.features.changelog

import android.content.pm.PackageManager.NameNotFoundException
import android.net.Uri
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.pm.PackageInfoCompat
import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import openfoodfacts.github.scrachx.openfood.R
import openfoodfacts.github.scrachx.openfood.customtabs.CustomTabActivityHelper
import openfoodfacts.github.scrachx.openfood.customtabs.CustomTabsHelper
import openfoodfacts.github.scrachx.openfood.customtabs.WebViewFallback
import openfoodfacts.github.scrachx.openfood.utils.LocaleHelper.getLocaleFromContext

class ChangelogDialog : DialogFragment(R.layout.fragment_changelog) {

companion object {
private const val TAG = "changelog_dialog"
private const val FORCE_SHOW_KEY = "force_show"
private const val LAST_VERSION_CODE = "last_version_code"
private const val URL_CROWDIN = "https://crowdin.com/project/openfoodfacts"

fun newInstance(forceShow: Boolean): ChangelogDialog {
val args = Bundle().apply {
putBoolean(FORCE_SHOW_KEY, forceShow)
}
return ChangelogDialog().apply {
arguments = args
}
}
}

private lateinit var translationHelpLabel: TextView
private lateinit var recyclerView: RecyclerView
private val compositeDisposable = CompositeDisposable()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

translationHelpLabel = view.findViewById(R.id.changelog_label_help)
recyclerView = view.findViewById(R.id.changelog_recycler)
view.findViewById<View>(R.id.changelog_button_close).setOnClickListener { dismiss() }

applyWindowTweaks()
setupTranslationHelpLabel()
setupRecyclerView()
}

override fun onDestroyView() {
compositeDisposable.clear()
super.onDestroyView()
}

@Suppress("DEPRECATION")
fun presentAutomatically(activity: AppCompatActivity) {
arguments?.let {
if (it.getBoolean(FORCE_SHOW_KEY, false)) {
show(activity.supportFragmentManager, TAG)
} else {
try {
val lastVersionCode = getVersion(activity)
val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
val currentVersionCode = PackageInfoCompat.getLongVersionCode(packageInfo)
if (currentVersionCode >= 0 && currentVersionCode > lastVersionCode) {
show(activity.supportFragmentManager, TAG)
saveVersionCode(activity, currentVersionCode)
}
} catch (e: NameNotFoundException) {
e.printStackTrace()
}
}
}
}

private fun setupTranslationHelpLabel() {
val language = getLocaleFromContext(context).displayLanguage
translationHelpLabel.text = getString(R.string.changelog_translation_help, language)
translationHelpLabel.setOnClickListener { v: View? -> openDailyFoodFacts() }
}

private fun applyWindowTweaks() {
dialog?.window?.run {
setBackgroundDrawableResource(android.R.color.white)
decorView.setPadding(0, 0, 0, 0)
attributes.gravity = Gravity.BOTTOM
attributes.width = WindowManager.LayoutParams.MATCH_PARENT
attributes.height = WindowManager.LayoutParams.MATCH_PARENT
setWindowAnimations(R.style.ChangelogDialogAnimation)
}
}

private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
recyclerView.layoutManager = layoutManager
val changelogService = ChangelogService(requireContext())
compositeDisposable.add(
changelogService
.observeChangelog()
.map { changelog ->
val itemList = mutableListOf<ChangelogListItem>()
changelog.versions.forEach { version ->
itemList.add(ChangelogListItem.Header(version.name, version.date))
version.items.forEach { item ->
itemList.add(ChangelogListItem.Item("- $item"))
}
}
itemList
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ items -> recyclerView.adapter = ChangelogAdapter(items) },
{ throwable -> throwable.printStackTrace() }
)
)
}

private fun openDailyFoodFacts() {
val dailyFoodFactUri = Uri.parse(URL_CROWDIN)
val customTabActivityHelper = CustomTabActivityHelper().apply {
mayLaunchUrl(dailyFoodFactUri, null, null)
}
val customTabsIntent = CustomTabsHelper.getCustomTabsIntent(
requireActivity(),
customTabActivityHelper.session,
)
CustomTabActivityHelper.openCustomTab(
requireActivity(),
customTabsIntent,
dailyFoodFactUri,
WebViewFallback()
)
}

private fun saveVersionCode(activity: AppCompatActivity, versionCode: Long) {
PreferenceManager.getDefaultSharedPreferences(activity)
.edit()
.putLong(LAST_VERSION_CODE, versionCode)
.apply()
}

private fun getVersion(activity: AppCompatActivity): Long {
return PreferenceManager.getDefaultSharedPreferences(activity).getLong(LAST_VERSION_CODE, 0)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package openfoodfacts.github.scrachx.openfood.features.changelog

sealed class ChangelogListItem {
data class Header(val version: String, val date: String) : ChangelogListItem()
data class Item(val description: String) : ChangelogListItem()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package openfoodfacts.github.scrachx.openfood.features.changelog

import android.content.Context
import com.fasterxml.jackson.databind.ObjectMapper
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import openfoodfacts.github.scrachx.openfood.utils.LocaleHelper.getLanguage
import java.io.BufferedReader
import java.io.IOException

class ChangelogService(private val context: Context) {
companion object {
private const val FOLDER = "changelog/"
}

private var mapper: ObjectMapper = ObjectMapper()

fun observeChangelog(): Single<Changelog> = Single
.fromCallable { parseJsonFile() }
.subscribeOn(Schedulers.io())

@Throws(IOException::class)
private fun parseJsonFile(): Changelog {
val language = getLanguage(context)
val jsonString = if (translationExists("changelog-$language.json")) {
getJsonStringFromAsset("changelog-$language.json")
} else {
getJsonStringFromAsset("changelog.json")
}
return mapper.readValue(jsonString, Changelog::class.java)
}

@Throws(IOException::class)
private fun getJsonStringFromAsset(fileName: String): String {
return context.assets
.open(FOLDER + fileName)
.bufferedReader()
.use(BufferedReader::readText)
}

private fun translationExists(fileName: String): Boolean {
return try {
val versions = context.assets.list(FOLDER)
versions?.toList()?.contains(fileName) ?: false
} catch (ex: IOException) {
false
}
}
}
7 changes: 7 additions & 0 deletions app/src/main/res/anim/push_bottom_in.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromYDelta="100%"
android:toYDelta="0" />
</set>
7 changes: 7 additions & 0 deletions app/src/main/res/anim/push_bottom_out.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromYDelta="0"
android:toYDelta="100%" />
</set>
Loading