Compare commits

..

5 Commits

10 changed files with 201 additions and 43 deletions

View File

@ -102,6 +102,7 @@ dependencies {
implementation "androidx.browser:browser:$android_version" implementation "androidx.browser:browser:$android_version"
implementation "androidx.cardview:cardview:$android_version" implementation "androidx.cardview:cardview:$android_version"
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha5' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha5'
implementation 'org.jsoup:jsoup:1.13.1'
//multidex //multidex
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
@ -137,6 +138,9 @@ dependencies {
// Pager // Pager
implementation 'me.relex:circleindicator:2.0.0@aar' 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.core:core-ktx:1.1.0-beta01'
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"

View File

@ -62,6 +62,9 @@
<activity <activity
android:name=".ReaderActivity"> android:name=".ReaderActivity">
</activity> </activity>
<activity
android:name=".ImageActivity">
</activity>
<meta-data <meta-data
android:name="apps.amine.bou.readerforselfoss.utils.glide.SelfSignedGlideModule" android:name="apps.amine.bou.readerforselfoss.utils.glide.SelfSignedGlideModule"

View File

@ -0,0 +1,52 @@
package apps.amine.bou.readerforselfoss
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import apps.amine.bou.readerforselfoss.fragments.ImageFragment
import kotlinx.android.synthetic.main.activity_reader.*
class ImageActivity : AppCompatActivity() {
private lateinit var allImages : ArrayList<String>
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])
}
}
}

View File

@ -5,9 +5,14 @@ import android.net.Uri
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import android.text.Html 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.Config
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString 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 import com.google.gson.annotations.SerializedName
private fun constructUrl(config: Config?, path: String, file: String?): String { private fun constructUrl(config: Config?, path: String, file: String?): String {
@ -128,6 +133,36 @@ data class Item(
return constructUrl(config, "thumbnails", thumbnail) return constructUrl(config, "thumbnails", thumbnail)
} }
fun getImages() : ArrayList<String> {
var allImages = ArrayList<String>()
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 { fun getTitleDecoded(): String {
return Html.fromHtml(title).toString() return Html.fromHtml(title).toString()
} }

View File

@ -104,6 +104,7 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
notificationManager.notify(2, newItemsNotification.build()) notificationManager.notify(2, newItemsNotification.build())
} }
} }
apiItems.map {it.preloadImages(context)}
} }
Timer("", false).schedule(4000) { Timer("", false).schedule(4000) {
notificationManager.cancel(1) notificationManager.cancel(1)

View File

@ -5,6 +5,7 @@ import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.TypedArray import android.content.res.TypedArray
import android.graphics.Bitmap
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.net.Uri import android.net.Uri
@ -12,15 +13,16 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.view.* import android.view.*
import android.webkit.*
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
import android.webkit.WebSettings
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.room.Room import androidx.room.Room
import apps.amine.bou.readerforselfoss.ImageActivity
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
@ -36,15 +38,15 @@ import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth 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.isEmptyOrNullOrNullString
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
import apps.amine.bou.readerforselfoss.utils.openItemUrl import apps.amine.bou.readerforselfoss.utils.openItemUrl
import apps.amine.bou.readerforselfoss.utils.shareLink import apps.amine.bou.readerforselfoss.utils.shareLink
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
import apps.amine.bou.readerforselfoss.utils.succeeded import apps.amine.bou.readerforselfoss.utils.succeeded
import android.webkit.WebView
import android.webkit.WebViewClient
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.github.rubensousa.floatingtoolbar.FloatingToolbar import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import kotlinx.android.synthetic.main.fragment_article.view.* import kotlinx.android.synthetic.main.fragment_article.view.*
@ -53,6 +55,8 @@ import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import java.net.MalformedURLException import java.net.MalformedURLException
import java.net.URL import java.net.URL
import java.util.concurrent.ExecutionException
import kotlin.collections.ArrayList
import kotlin.concurrent.thread import kotlin.concurrent.thread
class ArticleFragment : Fragment() { class ArticleFragment : Fragment() {
@ -65,6 +69,7 @@ class ArticleFragment : Fragment() {
private lateinit var contentSource: String private lateinit var contentSource: String
private lateinit var contentImage: String private lateinit var contentImage: String
private lateinit var contentTitle: String private lateinit var contentTitle: String
private lateinit var allImages : ArrayList<String>
private lateinit var editor: SharedPreferences.Editor private lateinit var editor: SharedPreferences.Editor
private lateinit var fab: FloatingActionButton private lateinit var fab: FloatingActionButton
private lateinit var appColors: AppColors private lateinit var appColors: AppColors
@ -116,6 +121,7 @@ class ArticleFragment : Fragment() {
contentTitle = allItems[pageNumber.toInt()].getTitleDecoded() contentTitle = allItems[pageNumber.toInt()].getTitleDecoded()
contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!) contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!)
contentSource = allItems[pageNumber.toInt()].sourceAndDateText() contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
allImages = allItems[pageNumber.toInt()].getImages()
prefs = PreferenceManager.getDefaultSharedPreferences(activity) prefs = PreferenceManager.getDefaultSharedPreferences(activity)
editor = prefs.edit() editor = prefs.edit()
@ -417,6 +423,30 @@ class ArticleFragment : Fragment() {
} }
return true 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() { val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
@ -545,12 +575,13 @@ class ArticleFragment : Fragment() {
fun performClick(): Boolean { fun performClick(): Boolean {
if (rootView!!.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || if (rootView!!.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
rootView!!.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { rootView!!.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
//TODO: Transfer all images in the webpage to the Image fragment
var allImages = ArrayList<String>()
allImages.add(rootView!!.webcontent.hitTestResult.extra.toString())
val position : Int = 0
fragmentManager!!.beginTransaction().replace(R.id.reader_activity_view, ImageFragment.newInstance(position, allImages)).addToBackStack(null).commit() 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
} }
return false return false

View File

@ -2,61 +2,46 @@ package apps.amine.bou.readerforselfoss.fragments
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import kotlinx.android.synthetic.main.fragment_article.view.webcontent 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() { class ImageFragment : Fragment() {
private lateinit var position: Number private lateinit var imageUrl : String
private lateinit var allImages: ArrayList<String> private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
position = arguments!!.getInt("position")
allImages = arguments!!.getStringArrayList("allImages")
imageUrl = arguments!!.getString("imageUrl")
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
(activity as AppCompatActivity).supportActionBar?.setDisplayShowTitleEnabled(false)
val view : View = inflater.inflate(R.layout.fragment_image, container, false) val view : View = inflater.inflate(R.layout.fragment_image, container, false)
view.webcontent.visibility = View.VISIBLE view.photoView.visibility = View.VISIBLE
view.webcontent.settings.setLoadWithOverviewMode(true) Glide.with(activity)
view.webcontent.settings.setUseWideViewPort(true) .asBitmap()
view.webcontent.settings.setSupportZoom(true) .apply(glideOptions)
view.webcontent.settings.setBuiltInZoomControls(true) .load(imageUrl)
view.webcontent.settings.setDisplayZoomControls(false) .into(view.photoView)
view.webcontent.loadUrl(allImages[0])
return view return view
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu.clear()
}
override fun onDestroy() {
(activity as AppCompatActivity).supportActionBar?.setDisplayShowTitleEnabled(true)
super.onDestroy()
}
companion object { companion object {
private const val ARG_POSITION = "position" private const val ARG_IMAGE = "imageUrl"
private const val ARG_IMAGES = "allImages"
fun newInstance( fun newInstance(
position: Int, imageUrl : String
allImages: ArrayList<String>
): ImageFragment { ): ImageFragment {
val fragment = ImageFragment() val fragment = ImageFragment()
val args = Bundle() val args = Bundle()
args.putInt(ARG_POSITION, position) args.putString(ARG_IMAGE, imageUrl)
args.putStringArrayList(ARG_IMAGES, allImages)
fragment.arguments = args fragment.arguments = args
return fragment return fragment
} }

View File

@ -14,6 +14,9 @@ import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders import com.bumptech.glide.load.model.LazyHeaders
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.BitmapImageViewTarget 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) = fun Context.bitmapCenterCrop(config: Config, url: String, iv: ImageView) =
Glide.with(this) Glide.with(this)
@ -56,4 +59,11 @@ fun RequestManager.loadMaybeBasicAuth(config: Config, url: String): RequestBuild
} }
val glideUrl = GlideUrl(url, builder.build()) val glideUrl = GlideUrl(url, builder.build())
return this.load(glideUrl) 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)
} }

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="?attr/toolbarPopupTheme"
app:theme="@style/ToolBarStyle" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -4,9 +4,13 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<WebView <com.github.chrisbanes.photoview.PhotoView
android:id="@+id/webcontent" android:id="@+id/photoView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" > android:layout_height="match_parent"
</WebView> android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:background="@android:color/black"
app:srcCompat="@android:drawable/screen_background_dark" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>