Commit dadfec6d authored by Christophe Henry's avatar Christophe Henry

Fix text selection context menu not showing + support images embedded inside a link

parent a9ff6a86
Pipeline #3330 failed with stage
in 0 seconds
# Development
# 2.0.0 (BREAKING: this version drops support of API version 21-22 (Android 5))
## Features
* Better handle images embedded in a link by showing the link seperatly ([!63](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/63))
* Display context options (copy/cut/etc.) on text selection in articles ([!63](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/63))
## Bug fixes
* Fix a bug preventing to refresh when subscription lists are empty ([!62](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/62/))
## Refactoring
* Refactor the waiting fragment to lighten the interface ([!62](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/62/))
# 1.2.2
## Refactoring
......
......@@ -11,7 +11,7 @@ android {
compileSdkVersion 28
defaultConfig {
applicationId "fr.chenry.android.freshrss"
minSdkVersion 21
minSdkVersion 23
targetSdkVersion 28
versionCode 12
versionName "1.2.2"
......@@ -106,16 +106,17 @@ spotless {
}
dependencies {
def lifecycle_version = "2.2.0-alpha05"
def room_version = "2.1.0"
def lifecycle_version = '2.2.0'
def room_version = '2.2.3'
def roomigrant_version = "0.1.7"
def fuel_version = "2.0.1"
def jackson_version = "2.9.6"
def jackson_version = '2.10.2'
def espresso_version = "3.2.0"
def test_runnner_version = "1.2.0"
def promise_version = "3.3.0"
def android_support_version = "28.0.0"
def android_navigation = "1.0.0"
def jsoup_version = "1.12.1"
// Linter
ktlint "com.github.shyiko:ktlint:0.31.0"
......@@ -138,9 +139,9 @@ dependencies {
// AndroidX layout
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.core:core-ktx:1.1.0"
implementation "com.google.android.material:material:1.1.0-alpha10"
implementation 'com.google.android.material:material:1.2.0-alpha04'
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.recyclerview:recyclerview:1.0.0"
implementation 'androidx.recyclerview:recyclerview:1.1.0'
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
......@@ -175,14 +176,15 @@ dependencies {
// Utils
implementation "org.apache.commons:commons-text:1.8"
implementation "joda-time:joda-time:2.10.4"
implementation 'joda-time:joda-time:2.10.5'
implementation "com.squareup.picasso:picasso:2.71828"
implementation "com.x5dev:chunk-templates:3.5.0"
implementation "eu.davidea:flexible-adapter:5.1.0"
implementation "eu.davidea:flexible-adapter-ui:1.0.0"
implementation "org.jsoup:jsoup:$jsoup_version"
// Tests
testImplementation "junit:junit:4.12"
testImplementation 'junit:junit:4.13'
androidTestImplementation "androidx.test:runner:$test_runnner_version"
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
......
......@@ -23,6 +23,14 @@ body h1 {
height: auto;
display: block;
margin: 0 auto;
max-width: 90%;
}
#main-content img ~ .link-to-original-image {
display: block;
font-size: 0.8rem;
margin: 0.5rem 0 0 0;
text-align: center;
}
#main-content pre {
......
......@@ -4,7 +4,6 @@ import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.view.*
import android.webkit.WebView
import android.widget.Toast
import android.widget.Toast.LENGTH_SHORT
import androidx.appcompat.app.AppCompatActivity
......@@ -14,7 +13,7 @@ import androidx.lifecycle.*
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.FRSSWebViewClient
import fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.FRSSWebView
import fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.ShareIntent
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.database.models.*
......@@ -49,27 +48,8 @@ class SubscriptionArticlesDetailFragment : Fragment() {
val view = inflater.inflate(R.layout.fragment_subscription_article_detail, container, false)
view.findViewById<WebView>(R.id.fragment_subscription_article_detail_web_view).let { wv ->
"""
|<!DOCTYPE html>
|<html>
| <head>
| <title>${article.title}</title>
| <meta charset="UTF-8" />
| <link rel="stylesheet" type="text/css" href="base-style.css" />
| </head>
| <body>
| <h1 id="article-title">${article.title}</h1>
| ${if (article.author.isNotEmpty()) "<h3 id='article-authors'>${article.author}</h3>" else ""}
| <div id="main-content">${article.content}</div>
| </body>
|</html>
""".trimMargin("|").let {
wv.settings.javaScriptEnabled = false
wv.settings.defaultTextEncodingName = "UTF-8"
wv.loadDataWithBaseURL("file:///android_asset/", it, "text/html; charset=utf-8", "UTF-8", null)
wv.webViewClient = FRSSWebViewClient(this, article.url)
}
view.findViewById<FRSSWebView>(R.id.fragment_subscription_article_detail_web_view).let {wv ->
wv.load(this, article)
}
setupShareAction(view)
......
package fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.webkit.WebSettings.MENU_ITEM_NONE
import android.webkit.WebView
import androidx.fragment.app.Fragment
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.database.models.Article
import org.jsoup.Jsoup
// WebView crashing on Lollipop devices
// See https://stackoverflow.com/a/49024931
class FRSSWebView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
): WebView(context.applicationContext, attrs, defStyleAttr, defStyleRes) {
init {
isFocusable = true
isFocusableInTouchMode = true
settings.javaScriptEnabled = false
settings.defaultTextEncodingName = "UTF-8"
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
settings.disabledActionModeMenuItems = MENU_ITEM_NONE
}
}
fun load(fragment: Fragment, article: Article) {
"""
|<!DOCTYPE html>
|<html>
| <head>
| <title>${article.title}</title>
| <meta charset="UTF-8" />
| <link rel="stylesheet" type="text/css" href="base-style.css" />
| </head>
| <body>
| <h1 id="article-title">${article.title}</h1>
| ${if(article.author.isNotEmpty()) "<h3 id='article-authors'>${article.author}</h3>" else ""}
| <div id="main-content">${article.content}</div>
| </body>
|</html>
""".trimMargin().let {
loadDataWithBaseURL("file:///android_asset/", processHTML(it), "text/html; charset=utf-8", "UTF-8", null)
webViewClient = FRSSWebViewClient(fragment, article.url)
}
}
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")
val type = link.attr("type")
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">
| ${FreshRSSApplication.getStringR(R.string.link_to_original_image)}
|</a>""".trimMargin())
}
return doc.html()
}
}
......@@ -3,9 +3,7 @@ package fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils
import android.content.Intent
import android.net.MailTo
import android.net.Uri
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import android.webkit.*
import androidx.fragment.app.Fragment
class FRSSWebViewClient(private val fragment: Fragment, private val sourceUrl: Uri?) : WebViewClient() {
......
......@@ -71,7 +71,7 @@ class MainSubscriptionFragment: Fragment(), BottomNavigationView.OnNavigationIte
setupBadgeStyle(Store.subscriptionsSection.value!!)
observer.onChanged(Store.totalUnreadCount.value)
Store.totalUnreadCount.observe(this, observer)
Store.totalUnreadCount.observe(viewLifecycleOwner, observer)
}
private fun setupBadgeStyle(activeSubscriptionSection: SubscriptionSection) {
......
......@@ -44,8 +44,8 @@ class SubscriptionsFragment : Fragment(), Observer<Subscriptions> {
initRecyclerView(view)
Store.refreshingPromise.observe(this, Observer { toggleProgressCircle(model.subscriptions.value ?: listOf()) })
model.subscriptions.observe(this, this)
Store.refreshingPromise.observe(viewLifecycleOwner, Observer { toggleProgressCircle(model.subscriptions.value ?: listOf()) })
model.subscriptions.observe(viewLifecycleOwner, this)
val contentDescription = if (category != VoidCategory) {
when (section) {
......@@ -72,7 +72,7 @@ class SubscriptionsFragment : Fragment(), Observer<Subscriptions> {
view.findViewById<EmotionnalImageSubtext>(R.id.fragment_subscriptions_empty_list).setImageResource(imageResource)
view.findViewById<SwipeRefreshLayout>(R.id.subcription_pull_to_refresh)?.let {
Store.refreshingPromise.observe(this, Observer<Promise<Unit, Exception>?> { v ->
Store.refreshingPromise.observe(viewLifecycleOwner, Observer<Promise<Unit, Exception>?> { v ->
it.isRefreshing = v != null
})
it.setOnRefreshListener {
......
......@@ -25,7 +25,7 @@ class SubscriptionsFlexibleAdapter(private val fragment: SubscriptionsFragment)
init {
setStickyHeaders(true)
setDisplayHeadersAtStartUp(true)
mediator.observe(fragment, Observer { updateDataSet(it, true) })
mediator.observe(fragment.viewLifecycleOwner, Observer { updateDataSet(it, true) })
mediator.addSource(fragment.model.subscriptions) {
computeNewDataSet(it, subscriptionCategories) success { res -> mediator.postValue(res) }
}
......
......@@ -111,5 +111,3 @@ fun URL.queryParameters(): Map<String, String> = this.query
?.split("&")
?.map {it.split("=").let {split -> split[0] to split[1]}}
?.toMap() ?: mapOf()
package fr.chenry.android.freshrss.utils
import android.annotation.TargetApi
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.util.AttributeSet
import android.webkit.WebView
// WebView crashing on Lollipop devices
// See https://stackoverflow.com/a/49024931
class LollipopFixedWebView : WebView {
constructor(context: Context) : super(getFixedContext(context))
constructor(context: Context, attrs: AttributeSet) : super(getFixedContext(context), attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
super(getFixedContext(context), attrs, defStyleAttr)
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) :
super(getFixedContext(context), attrs, defStyleAttr, defStyleRes)
@Suppress("DEPRECATION")
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, privateBrowsing: Boolean) :
super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing)
companion object {
fun getFixedContext(context: Context): Context = context.createConfigurationContext(Configuration())
}
}
......@@ -18,11 +18,11 @@
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/activity_main_navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/navigation_drawer_header"
app:menu="@menu/activity_main_drawer"/>
android:id="@+id/activity_main_navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/navigation_drawer_header"
app:menu="@menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
\ No newline at end of file
......@@ -4,7 +4,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fr.chenry.android.freshrss.utils.LollipopFixedWebView
<fr.chenry.android.freshrss.components.subscriptionarticles.webviewutils.FRSSWebView
android:id="@+id/fragment_subscription_article_detail_web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
......
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout
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"
......
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:tools="http://schemas.android.com/tools"
<merge
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<ProgressBar
android:id="@+id/loader_progress_bar"
......
......@@ -3,31 +3,31 @@
<string name="refresh_frequency_title">تردّد الإنعاش (%s)</string>
<string name="refresh_frequency">تردّد الإنعاش</string>
<string name="empty_subscriptions_section_all">إنّ المكان يبدو فارغا.
\nلِم لا تقم بالبحث عن اشتراك قصد الحصول على محتوى للقراءة؟</string>
\nلِم لا تقم بالبحث عن اشتراك قصد الحصول على محتوى للقراءة؟</string>
<string name="notification_channel_errors_description">لقد صادف FreshRSS خطأ وسيتم إخطارك به</string>
<string name="refresh_frequency_off">معطّل</string>
<string name="no_internet_connection_avaible">ليس هناك اتصال بالأنترنت حاليا، يرجى إعادة المحاولة لاحقا</string>
<string name="instance_url_malformed">إنّ صيغة هذا العنوان غير صالحة</string>
<string name="share_article">شارك مقال %s</string>
<string name="notification_refresh_failed_description">"لقد فشل FreshRSS في جلب المحتوى مِن خادم FreshRSS الخاص بك"</string>
<string name="notification_refresh_failed_description">"لقد فشل FreshRSS في جلب المحتوى مِن خادم FreshRSS الخاص بك"</string>
<string name="notification_channel_refresh_title">انعِش الأحداث</string>
<string name="notification_refresh_description">"يقوم FreshRSS حاليا بجلب المحتوى مِن خادم FreshRSS الخاص بك"</string>
<string name="notification_refresh_description">"يقوم FreshRSS حاليا بجلب المحتوى مِن خادم FreshRSS الخاص بك"</string>
<string name="empty_subscriptions_section_all_category">ليس لديك أي اشتراك بعد في فئة %s.
\nلِم لا تقم بإضافة واحد؟</string>
\nلِم لا تقم بإضافة واحد؟</string>
<string name="empty_subscriptions_section_favorites_category">ليس لديك أية مفضلة بعد في فئة %s.
\nلِم لا تقم بإضافة واحدة؟</string>
\nلِم لا تقم بإضافة واحدة؟</string>
<string name="empty_subscriptions_section_favorites">ليس لديك أية مفضلة بعد.
\nلِم لا تقم بإضافة واحدة؟</string>
\nلِم لا تقم بإضافة واحدة؟</string>
<string name="unable_to">المعذرة، لقد فشل التطبيق في %s</string>
<string name="request_already_ongoing">هناك طلب ساري. الرجاء إعادة المحاولة لاحقا.</string>
<string name="mark_read_status_authorization">ضع علامة %s على هذا المقال</string>
<string name="notification_channel_refresh_description">"يقوم FreshRSS حاليا بجلب المحتوى مِن خادم FreshRSS الخاص بك"</string>
<string name="notification_channel_refresh_description">"يقوم FreshRSS حاليا بجلب المحتوى مِن خادم FreshRSS الخاص بك"</string>
<string name="prompt_login">اسم المستخدم</string>
<string name="url_of_feed_or_website">عنوان التدفق أو موقع الويب</string>
<string name="application">التطبيق</string>
<string name="title_add_subscription">إضافة تدفّق</string>
<string name="user">المستخدم</string>
<string name="freshrss_logo">"شعار FreshRSS"</string>
<string name="freshrss_logo">"شعار FreshRSS"</string>
<string name="refresh_frequency_1d">يوم واحد</string>
<string name="refresh_frequency_12h">12سا</string>
<string name="refresh_frequency_5h">5سا</string>
......@@ -66,7 +66,7 @@
<string name="title_all">كلها</string>
<string name="title_favorites">المفضلات</string>
<string name="login_progress_text">جارٍ الاتصال بمثيل الخادم
\n%s</string>
\n%s</string>
<string name="instance_exemple">your-instance.com</string>
<string name="error_instance">إنّ هذا العنوان غير صحيح</string>
<string name="prompt_instance">مثيل خادم FreshRSS</string>
......
<resources>
<declare-styleable name="Loader">
<attr name="loader_text" format="string|reference" />
<attr
name="loader_text"
format="string|reference" />
</declare-styleable>
<declare-styleable name="EmotionnalImageSubtext">
<attr name="src" format="reference" />
<attr name="text" format="string|reference" />
<attr
name="src"
format="reference" />
<attr
name="text"
format="string|reference" />
</declare-styleable>
</resources>
<resources>
<!-- Do not translate -->
<string name="app_name" translatable="false">FreshRSS</string>
<string
name="app_name"
translatable="false">FreshRSS</string>
<string-array name="connection_options">
<item>https://</item>
<item>http://</item>
......@@ -25,8 +27,12 @@
</string-array>
<!-- Preference keys -->
<string name="refresh_frequency_preference" translatable="false">refresh_frequency_preference</string>
<string name="subscription_section_preference" translatable="false">subscription_section_preference</string>
<string
name="refresh_frequency_preference"
translatable="false">refresh_frequency_preference</string>
<string
name="subscription_section_preference"
translatable="false">subscription_section_preference</string>
<string name="prompt_login">Login</string>
<string name="prompt_password">Password</string>
......@@ -102,7 +108,12 @@
<string name="user">User</string>
<string name="title_add_subscription">Add feed</string>
<string name="application">Application</string>
<string name="navigation_drawer_open" translatable="false">Open drawer</string>
<string name="navigation_drawer_close" translatable="false">Close drawer</string>
<string
name="navigation_drawer_open"
translatable="false">Open drawer</string>
<string
name="navigation_drawer_close"
translatable="false">Close drawer</string>
<string name="url_of_feed_or_website">URL of feed or website</string>
<string name="link_to_original_image">Link to original image</string>
</resources>
......@@ -47,7 +47,7 @@
<item name="color">@color/design_default_color_on_primary</item>
</style>
<style name="SubText" >
<style name="SubText">
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center</item>
<item name="android:textColor">@color/light_grey</item>
......
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.3.50"
ext.kotlin_version = '1.3.61'
repositories {
google()
jcenter()
......@@ -9,7 +9,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:3.5.0"
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"
......
......@@ -19,4 +19,3 @@ android.useAndroidX=true
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.databinding.enableV2=true
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