Commit 125c2045 authored by Christophe Henry's avatar Christophe Henry

Merge branch 'fix-npe-errors' into 'develop'

Fix some NPE errors and concurrent crashes

See merge request !118
parents 93198583 d3c0e425
Pipeline #4776 canceled with stage
in 0 seconds
......@@ -11,6 +11,7 @@
* Fix some context crashes in fragments ([!110](https://git.feneas.org/christophehenry/freshrss-android/-/merge_requests/110))
* Fix [#103](https://git.feneas.org/christophehenry/freshrss-android/-/issues/103): retrieving articles restults in 404 ([!110](https://git.feneas.org/christophehenry/freshrss-android/-/merge_requests/110))
* Make english sentence more natural to native speakers ([!109](https://git.feneas.org/christophehenry/freshrss-android/-/merge_requests/109))
* Fix a few more crashes and concurrent problems ([!118](https://git.feneas.org/christophehenry/freshrss-android/-/merge_requests/118))
# 1.3.2
......
......@@ -6,7 +6,6 @@ import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.MutableLiveData
import androidx.room.Room
import androidx.work.*
import fr.chenry.android.freshrss.store.FreshRSSPreferences
......@@ -28,8 +27,8 @@ import kotlin.math.max
@AcraCore(reportFormat = StringFormat.JSON)
@AcraHttpSender(
uri = "https://android-report.christophe-henry.dev/report",
basicAuthLogin = "4qEGdZXlUxV2vqoN",
basicAuthPassword = "oYq9b2rumfVALboE",
basicAuthLogin = "3lJP3VyigIr9nxuF",
basicAuthPassword = "pjhEv0b0rtIM7rrp",
httpMethod = HttpSender.Method.POST
)
@AcraNotification(
......@@ -40,7 +39,6 @@ import kotlin.math.max
)
open class FreshRSSApplication: Application(), SharedPreferences.OnSharedPreferenceChangeListener {
open val preferences: FreshRSSPreferences by lazy {FreshRSSPreferences(this)}
open val workManager by lazy {WorkManager.getInstance(this)}
open val notificationManager: NotificationManagerCompat by lazy {NotificationManagerCompat.from(this)}
......@@ -135,6 +133,7 @@ open class FreshRSSApplication: Application(), SharedPreferences.OnSharedPrefere
).await()
companion object {
const val DB_NAME = "freshrss.db"
private lateinit var appInstance: FreshRSSApplication
val app: FreshRSSApplication get() = appInstance
......
package fr.chenry.android.freshrss.activities
import android.app.ActivityOptions
import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.databinding.DataBindingUtil
import androidx.databinding.ObservableField
import fr.chenry.android.freshrss.R
......@@ -28,20 +30,22 @@ class StartActivity: AppCompatActivity() {
override fun onResume() {
super.onResume()
model.account.observe(this) {account ->
GlobalScope.launch(Dispatchers.Main) {startNextActivityDelayed(account)}.unit()
val activity = this
model.account.observe(this) {
GlobalScope.launch(Dispatchers.Main) {startNextActivityDelayed(activity, it)}
}
}
private suspend fun startNextActivityDelayed(account: Account) {
private suspend fun startNextActivityDelayed(act: AppCompatActivity, account: Account) {
val now = DateTime.now(getUserTimeZone())
val login = if(account == VoidAccount) "" else " ${account.login}"
val compl = if(account == VoidAccount) "" else act.getString(R.string.greeting_user_complement, account.login)
val text = when {
now.isBefore(now.withTime(11, 0, 0, 0)) ->
requireF().getString(R.string.good_morning_user, login)
act.getString(R.string.good_morning_user, compl)
now.isBefore(now.withTime(18, 0, 0, 0)) ->
requireF().getString(R.string.hello_user, login)
else -> requireF().getString(R.string.good_evening_user, login)
act.getString(R.string.hello_user, compl)
else -> act.getString(R.string.good_evening_user, compl)
}
withContext(Dispatchers.IO) {
......@@ -55,7 +59,11 @@ class StartActivity: AppCompatActivity() {
else -> MainActivity::class.java
}
startActivity(Intent(this@StartActivity, klass))
val anim = ActivityOptions
.makeCustomAnimation(act, R.anim.slide_in_right, R.anim.slide_out_left)
.toBundle()
ActivityCompat.startActivity(act, Intent(act, klass), anim)
finish()
}
}
......@@ -6,6 +6,7 @@ import android.view.*
import android.widget.Toast
import android.widget.Toast.LENGTH_SHORT
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
......@@ -35,6 +36,8 @@ class ArticleDetailFragment: Fragment(), View.OnClickListener {
private val feedTitle: String inline get() = model.feedTitle.value!!
private val supportActionBar inline get() = (requireActivity() as? AppCompatActivity)?.supportActionBar
private val browserIntent by lazy {Intent(Intent.ACTION_VIEW, article.url)}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
......@@ -45,9 +48,12 @@ class ArticleDetailFragment: Fragment(), View.OnClickListener {
supportActionBar?.subtitle = article.title
val canOpenBrowser = browserIntent.resolveActivity(requireActivity().packageManager) != null
FragmentSubscriptionArticleDetailBinding.bind(view).apply {
article = this@ArticleDetailFragment.article
onClickListener = this@ArticleDetailFragment
this.canOpenBrowser = canOpenBrowser
}
view.findViewById<FRSSWebView>(R.id.fragment_subscription_article_detail_web_view).apply {
......@@ -93,21 +99,23 @@ class ArticleDetailFragment: Fragment(), View.OnClickListener {
val chooserTitle = requireF().getString(R.string.share_article, feedTitle)
startActivity(Intent.createChooser(ShareIntent.create(feedTitle, article), chooserTitle))
}
R.id.fab_open_browser -> startActivity(Intent(Intent.ACTION_VIEW, article.url))
R.id.fab_open_browser -> startActivity(browserIntent)
else -> NOOP()
}
private fun setUpReadStatusButton(menu: Menu) {
val ctx = requireContext()
menu.findItem(R.id.action_mark_read_status)?.let {
fun mutateUi(article: Article) {
when(article.readStatus) {
ReadStatus.READ -> {
it.icon = context?.getDrawable(R.drawable.ic_is_read_24dp)
it.title = context?.getString(R.string.mark_unread)
it.icon = ContextCompat.getDrawable(ctx, R.drawable.ic_is_read_24dp)
it.title = ctx.getString(R.string.mark_unread)
}
ReadStatus.UNREAD -> {
it.icon = context?.getDrawable(R.drawable.ic_is_unread_24dp)
it.title = context?.getString(R.string.mark_read)
it.icon = ContextCompat.getDrawable(ctx, R.drawable.ic_is_unread_24dp)
it.title = ctx.getString(R.string.mark_read)
}
}
it.isVisible = true
......
......@@ -4,9 +4,9 @@ import android.content.Context
import android.util.AttributeSet
import android.webkit.WebView
import androidx.fragment.app.Fragment
import fr.chenry.android.freshrss.F
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.database.models.Article
import org.acra.ACRA
import org.jsoup.Jsoup
class FRSSWebView @JvmOverloads constructor(
......@@ -47,6 +47,7 @@ class FRSSWebView @JvmOverloads constructor(
private fun processHTML(html: String): String {
val doc = Jsoup.parse(html)
val imagesInLinks = doc.select("a > img")
imagesInLinks.forEach {
val link = it.parent()
val linkToOriginalImage = link.attr("abs:href")
......@@ -55,16 +56,21 @@ class FRSSWebView @JvmOverloads constructor(
it.attr("data-original-href", linkToOriginalImage)
if(type.isNotBlank()) it.attr("data-original-type", type)
val newImage = it.clone()
link.after(newImage).remove()
newImage.after(
"""
|<a href="$linkToOriginalImage" class="link-to-original-image">
| ${rootView.context.getString(R.string.link_to_original_image)}
|</a>""".trimMargin()
)
runCatching {
val newImage = it.clone()
link.after(newImage).remove()
newImage.after(
"""<a href="$linkToOriginalImage" class="link-to-original-image">
| ${rootView.context.getString(R.string.link_to_original_image)}
|</a>""".trimMargin()
)
}.recover {t ->
ACRA.getErrorReporter().handleSilentException(HTMLProcessException(html, t))
}
}
return doc.html()
}
private class HTMLProcessException(html: String, ex: Throwable): Exception(html, ex)
}
......@@ -49,7 +49,7 @@ data class Article(
@Ignore
val url = runCatching {Uri.parse(href)}.getOrNull()
val url: Uri = Uri.parse(href)
@Ignore
private val mReadStatusRequestOnGoing = AtomicBoolean(false)
......
......@@ -4,6 +4,7 @@
<import type="android.view.View" />
<variable name="onClickListener" type="android.view.View.OnClickListener" />
<variable name="article" type="fr.chenry.android.freshrss.store.database.models.Article" />
<variable name="canOpenBrowser" type="Boolean" />
</data>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
......@@ -41,7 +42,7 @@
android:layout_margin="16dp"
android:onClick="@{onClickListener::onClick}"
android:text="@string/original_page"
android:visibility="@{article.url == null ? View.GONE : View.VISIBLE, default=visible}"
android:visibility="@{canOpenBrowser ? View.GONE : View.VISIBLE, default=visible}"
app:icon="@drawable/ic_web_black_24dp" />
</LinearLayout>
</FrameLayout>
......
......@@ -36,9 +36,9 @@
<string name="title_settings">الإعدادات</string>
<string name="nav_header_subtitle">القائمة</string>
<string name="subscription_categories">الفئات</string>
<string name="good_evening_user">عمّ مساؤك %s</string>
<string name="good_morning_user">أسعدت صباحًا %s</string>
<string name="hello_user">مرحبًا %s</string>
<string name="good_evening_user">عمّ مساؤك%s</string>
<string name="good_morning_user">أسعدت صباحًا%s</string>
<string name="hello_user">مرحبًا%s</string>
<string name="human_time_grouping_old_articles">المقالات القديمة</string>
<string name="human_time_grouping_this_year">هذا العام</string>
<string name="human_time_grouping_last_month">الشهر الماضي</string>
......@@ -92,4 +92,5 @@
<string name="last_sync_absolute_date_text">آخر مزامنة منذ %s</string>
<string name="last_sync_a_few_seconds_ago">آخر مزامنة منذ ثواني</string>
<string name="appearance_setting_title">المظهر</string>
<string name="greeting_user_complement">" %s"</string>
</resources>
\ No newline at end of file
......@@ -99,4 +99,5 @@
<string name="advanced_preference_section">Avancé</string>
<string name="send_report">Envoyer un rapport</string>
<string name="detail_error_message">\"Si vous pensez que c\'est une erreur qui doit être remontée, cliquez le bouton \'%s\'\"</string>
<string name="greeting_user_complement">" %s"</string>
</resources>
\ No newline at end of file
......@@ -100,9 +100,23 @@
<string name="human_time_grouping_last_month">Last month</string>
<string name="human_time_grouping_this_year">This year</string>empty_subscription_list
<string name="human_time_grouping_old_articles">Old articles</string>
<string name="hello_user">Hello,%s</string>
<string name="good_morning_user">Good morning,%s</string>
<string name="good_evening_user">Good evening,%s</string>
<!--
`hello_user`, `good_morning_user`, `good_evening_user` are
used differently in two different contexts.
When no user is logged in, the `%s` part is replaced by an
empty string so that welcoming message is just *Hello* or *Good morning*.
When a user is logged in, the `%s` is replaced by a complement that is
`greeting_user_complement` resource. That part that may differ depending
on the languages. The `%s` part is then replace by the user's login.
So, in english, that would be *Good morning, Asha*.
-->
<string name="hello_user">Hello%s</string>
<string name="good_morning_user">Good morning%s</string>
<string name="good_evening_user">Good evening%s</string>
<string name="greeting_user_complement">, %s</string>
<string name="subscription_categories">Categories</string>
<string name="share_article">Share article - %s</string>
<string name="instance_url_malformed">This URL doesn\'t look right</string>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment