Compare commits
	
		
			15 Commits
		
	
	
		
			v172101010
			...
			d0557cf653
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d0557cf653 | |||
| f6999dd547 | |||
| 040c845c15 | |||
| cb07227cb7 | |||
| cda09a4e8a | |||
| 3de95ba6e4 | |||
| 47f00dc694 | |||
| 584c6869b5 | |||
| b0c4b010a2 | |||
| e08fc2604f | |||
| 41386adf4e | |||
| 59c307323f | |||
| 41e825bc50 | |||
| 0bab675560 | |||
| 55ced4a5fb | 
| @@ -102,6 +102,7 @@ dependencies { | ||||
|     implementation "androidx.browser:browser:$android_version" | ||||
|     implementation "androidx.cardview:cardview:$android_version" | ||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha5' | ||||
|     implementation 'org.jsoup:jsoup:1.13.1' | ||||
|  | ||||
|     //multidex | ||||
|     implementation 'androidx.multidex:multidex:2.0.1' | ||||
| @@ -137,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" | ||||
|   | ||||
| @@ -62,6 +62,9 @@ | ||||
|         <activity | ||||
|             android:name=".ReaderActivity"> | ||||
|         </activity> | ||||
|         <activity | ||||
|             android:name=".ImageActivity"> | ||||
|         </activity> | ||||
|  | ||||
|         <meta-data | ||||
|             android:name="apps.amine.bou.readerforselfoss.utils.glide.SelfSignedGlideModule" | ||||
|   | ||||
| @@ -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]) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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<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 { | ||||
|         return Html.fromHtml(title).toString() | ||||
|     } | ||||
|   | ||||
| @@ -104,6 +104,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) | ||||
|   | ||||
| @@ -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 | ||||
| @@ -38,6 +38,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 | ||||
| @@ -45,6 +46,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.* | ||||
| @@ -53,6 +55,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() { | ||||
| @@ -65,6 +69,7 @@ class ArticleFragment : Fragment() { | ||||
|     private lateinit var contentSource: String | ||||
|     private lateinit var contentImage: String | ||||
|     private lateinit var contentTitle: String | ||||
|     private lateinit var allImages : ArrayList<String> | ||||
|     private lateinit var editor: SharedPreferences.Editor | ||||
|     private lateinit var fab: FloatingActionButton | ||||
|     private lateinit var appColors: AppColors | ||||
| @@ -116,6 +121,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() | ||||
| @@ -410,6 +416,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 | ||||
| @@ -525,5 +572,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 | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
| } | ||||
							
								
								
									
										33
									
								
								app/src/main/res/layout/activity_image.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/src/main/res/layout/activity_image.xml
									
									
									
									
									
										Normal 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> | ||||
							
								
								
									
										16
									
								
								app/src/main/res/layout/fragment_image.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/src/main/res/layout/fragment_image.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.coordinatorlayout.widget.CoordinatorLayout 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.github.chrisbanes.photoview.PhotoView | ||||
|         android:id="@+id/photoView" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:layout_centerVertical="true" | ||||
|         android:layout_centerHorizontal="true" | ||||
|         android:background="@android:color/black" | ||||
|         app:srcCompat="@android:drawable/screen_background_dark" /> | ||||
|  | ||||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | ||||
		Reference in New Issue
	
	Block a user