diff --git a/app/build.gradle b/app/build.gradle
index 92787ac..a456f57 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -138,6 +138,9 @@ dependencies {
// Pager
implementation 'me.relex:circleindicator:2.0.0@aar'
+ //PhotoView
+ implementation 'com.github.chrisbanes:PhotoView:2.0.0'
+
implementation 'androidx.core:core-ktx:1.1.0-beta01'
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5575c92..748ff1b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -62,6 +62,9 @@
+
+
+ private var position : Int = 0
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(R.layout.activity_image)
+
+ setSupportActionBar(toolBar)
+ supportActionBar?.setDisplayShowTitleEnabled(false)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+
+ allImages = intent.getStringArrayListExtra("allImages")
+ position = intent.getIntExtra("position", 0)
+
+ pager.adapter = ScreenSlidePagerAdapter(supportFragmentManager)
+ pager.currentItem = position
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ android.R.id.home -> {
+ onBackPressed()
+ return true
+ }
+ }
+
+ return super.onOptionsItemSelected(item)
+ }
+
+ private inner class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+
+ override fun getCount(): Int {
+ return allImages.size
+ }
+
+ override fun getItem(position: Int): ImageFragment {
+ return ImageFragment.newInstance(allImages[position])
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossModels.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossModels.kt
index a0c33e0..516c78f 100644
--- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossModels.kt
+++ b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossModels.kt
@@ -5,9 +5,14 @@ import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import android.text.Html
+import android.webkit.URLUtil
+import org.jsoup.Jsoup
import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.request.RequestOptions
import com.google.gson.annotations.SerializedName
private fun constructUrl(config: Config?, path: String, file: String?): String {
@@ -128,6 +133,36 @@ data class Item(
return constructUrl(config, "thumbnails", thumbnail)
}
+ fun getImages() : ArrayList {
+ var allImages = ArrayList()
+
+ for ( image in Jsoup.parse(content).getElementsByTag("img")) {
+ allImages.add(image.attr("src"))
+ }
+ return allImages
+ }
+
+ fun preloadImages(context: Context) : Boolean {
+ val imageUrls = this.getImages()
+
+ val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
+
+
+ try {
+ for (url in imageUrls) {
+ if ( URLUtil.isValidUrl(url)) {
+ val image = Glide.with(context).asBitmap()
+ .apply(glideOptions)
+ .load(url).submit().get()
+ }
+ }
+ } catch (e : Error) {
+ return false
+ }
+
+ return true
+ }
+
fun getTitleDecoded(): String {
return Html.fromHtml(title).toString()
}
diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/background/background.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/background/background.kt
index 1bcddb5..a1ce7dc 100644
--- a/app/src/main/java/apps/amine/bou/readerforselfoss/background/background.kt
+++ b/app/src/main/java/apps/amine/bou/readerforselfoss/background/background.kt
@@ -105,6 +105,7 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
notificationManager.notify(2, newItemsNotification.build())
}
}
+ apiItems.map {it.preloadImages(context)}
}
Timer("", false).schedule(4000) {
notificationManager.cancel(1)
diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt
index 7bc6f5b..f5b10a9 100644
--- a/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt
+++ b/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt
@@ -1,28 +1,28 @@
package apps.amine.bou.readerforselfoss.fragments
import android.content.Context
+import android.content.Intent
import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.content.res.TypedArray
+import android.graphics.Bitmap
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
+import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.preference.PreferenceManager
-import android.view.InflateException
+import android.view.*
+import android.webkit.*
import androidx.browser.customtabs.CustomTabsIntent
import com.google.android.material.floatingactionbutton.FloatingActionButton
import androidx.fragment.app.Fragment
import androidx.core.content.ContextCompat
import androidx.core.widget.NestedScrollView
-import android.view.LayoutInflater
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import android.webkit.WebSettings
import androidx.appcompat.app.AlertDialog
import androidx.core.content.res.ResourcesCompat
import androidx.room.Room
+import apps.amine.bou.readerforselfoss.ImageActivity
import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
@@ -39,6 +39,7 @@ import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth
+import apps.amine.bou.readerforselfoss.utils.glide.getBitmapInputStream
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
import apps.amine.bou.readerforselfoss.utils.openItemUrl
@@ -46,6 +47,7 @@ import apps.amine.bou.readerforselfoss.utils.shareLink
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
import apps.amine.bou.readerforselfoss.utils.succeeded
import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import kotlinx.android.synthetic.main.fragment_article.view.*
@@ -54,6 +56,8 @@ import retrofit2.Callback
import retrofit2.Response
import java.net.MalformedURLException
import java.net.URL
+import java.util.concurrent.ExecutionException
+import kotlin.collections.ArrayList
import kotlin.concurrent.thread
class ArticleFragment : Fragment() {
@@ -66,6 +70,7 @@ class ArticleFragment : Fragment() {
private lateinit var contentSource: String
private lateinit var contentImage: String
private lateinit var contentTitle: String
+ private lateinit var allImages : ArrayList
private lateinit var editor: SharedPreferences.Editor
private lateinit var fab: FloatingActionButton
private lateinit var appColors: AppColors
@@ -117,6 +122,7 @@ class ArticleFragment : Fragment() {
contentTitle = allItems[pageNumber.toInt()].getTitleDecoded()
contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!)
contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
+ allImages = allItems[pageNumber.toInt()].getImages()
prefs = PreferenceManager.getDefaultSharedPreferences(activity)
editor = prefs.edit()
@@ -411,6 +417,47 @@ class ArticleFragment : Fragment() {
rootView!!.webcontent.settings.loadWithOverviewMode = true
rootView!!.webcontent.settings.javaScriptEnabled = false
+ rootView!!.webcontent.webViewClient = object : WebViewClient() {
+ override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
+ if (rootView!!.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
+ rootView!!.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
+ }
+ return true
+ }
+
+ override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? {
+ val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
+ if (url.toLowerCase().contains(".jpg") || url.toLowerCase().contains(".jpeg")) {
+ try {
+ val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
+ return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
+ }catch ( e : ExecutionException) {}
+ }
+ else if (url.toLowerCase().contains(".png")) {
+ try {
+ val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
+ return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
+ }catch ( e : ExecutionException) {}
+ }
+ else if (url.toLowerCase().contains(".webp")) {
+ try {
+ val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
+ return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
+ }catch ( e : ExecutionException) {}
+ }
+
+ return super.shouldInterceptRequest(view, url)
+ }
+ }
+
+ val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
+ override fun onSingleTapUp(e: MotionEvent?): Boolean {
+ return performClick()
+ }
+ })
+
+ rootView!!.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
rootView!!.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
@@ -526,5 +573,20 @@ class ArticleFragment : Fragment() {
}
}
+ fun performClick(): Boolean {
+ if (rootView!!.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
+ rootView!!.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
+
+ val position : Int = allImages.indexOf(rootView!!.webcontent.hitTestResult.extra)
+
+ val intent = Intent(activity, ImageActivity::class.java)
+ intent.putExtra("allImages", allImages)
+ intent.putExtra("position", position)
+ startActivity(intent)
+ return false
+ }
+ return false
+ }
+
}
diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ImageFragment.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ImageFragment.kt
new file mode 100644
index 0000000..57e26c8
--- /dev/null
+++ b/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ImageFragment.kt
@@ -0,0 +1,49 @@
+package apps.amine.bou.readerforselfoss.fragments
+
+import android.os.Bundle
+import android.view.*
+import androidx.fragment.app.Fragment
+import apps.amine.bou.readerforselfoss.R
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.request.RequestOptions
+import kotlinx.android.synthetic.main.fragment_image.view.*
+
+class ImageFragment : Fragment() {
+
+ private lateinit var imageUrl : String
+ private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ imageUrl = arguments!!.getString("imageUrl")
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ val view : View = inflater.inflate(R.layout.fragment_image, container, false)
+
+ view.photoView.visibility = View.VISIBLE
+ Glide.with(activity)
+ .asBitmap()
+ .apply(glideOptions)
+ .load(imageUrl)
+ .into(view.photoView)
+
+ return view
+ }
+
+ companion object {
+ private const val ARG_IMAGE = "imageUrl"
+
+ fun newInstance(
+ imageUrl : String
+ ): ImageFragment {
+ val fragment = ImageFragment()
+ val args = Bundle()
+ args.putString(ARG_IMAGE, imageUrl)
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/glide/GlideUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/glide/GlideUtils.kt
index ea29a70..9183126 100644
--- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/glide/GlideUtils.kt
+++ b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/glide/GlideUtils.kt
@@ -14,6 +14,9 @@ import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.BitmapImageViewTarget
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
fun Context.bitmapCenterCrop(config: Config, url: String, iv: ImageView) =
Glide.with(this)
@@ -56,4 +59,11 @@ fun RequestManager.loadMaybeBasicAuth(config: Config, url: String): RequestBuild
}
val glideUrl = GlideUrl(url, builder.build())
return this.load(glideUrl)
+}
+
+fun getBitmapInputStream(bitmap:Bitmap,compressFormat: Bitmap.CompressFormat): InputStream {
+ val byteArrayOutputStream = ByteArrayOutputStream()
+ bitmap.compress(compressFormat, 80, byteArrayOutputStream)
+ val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
+ return ByteArrayInputStream(bitmapData)
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_image.xml b/app/src/main/res/layout/activity_image.xml
new file mode 100644
index 0000000..d5e50c4
--- /dev/null
+++ b/app/src/main/res/layout/activity_image.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_image.xml b/app/src/main/res/layout/fragment_image.xml
new file mode 100644
index 0000000..ae2c7a8
--- /dev/null
+++ b/app/src/main/res/layout/fragment_image.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file