Compare commits
	
		
			38 Commits
		
	
	
		
			v171811331
			...
			v171901030
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | a8721ad7a4 | ||
|  | bc5e882894 | ||
|  | e3460322b1 | ||
|  | 7e3288a076 | ||
|  | ddc754ec25 | ||
|  | 134a0766d6 | ||
|  | 69da932ab5 | ||
|  | 592fb6328a | ||
|  | a0aead6491 | ||
|  | 722b6cc06d | ||
|  | 6d7c4b40f6 | ||
|  | f538ed39fc | ||
|  | 65821492ad | ||
|  | 6ede718a9f | ||
|  | f1757937a4 | ||
| 2bd2e0a953 | |||
|  | b5aef28af0 | ||
|  | 45747a1506 | ||
|  | c6e2e08bcb | ||
|  | 25bf18661e | ||
|  | 6b088dcd24 | ||
|  | d2b18e1880 | ||
|  | eec7c94e98 | ||
|  | d1f8fcacc0 | ||
|  | 07e4a33cbd | ||
|  | f6317f566e | ||
|  | 9f51e4e6a5 | ||
|  | 750604a31f | ||
|  | 392eee0ad4 | ||
|  | 37e7b987ee | ||
|  | 9eac51e729 | ||
|  | fa9cce6783 | ||
|  | f0d4b63a97 | ||
|  | 83eeb11388 | ||
|  | 01f746f33d | ||
|  | 200851894b | ||
|  | 862e5cf4ab | ||
|  | 0b07f2a407 | 
| @@ -1,5 +1,13 @@ | ||||
| **1.7.x** | ||||
|  | ||||
| - Hiding tags with 0 articles | ||||
|  | ||||
| - Fixed issue with basic auth and images loading | ||||
|  | ||||
| - Added the ability to justify or left align the reader text | ||||
|  | ||||
| - Fixed #251 | ||||
|  | ||||
| - Added experimental issue to set a default timeout. Should work for #238. | ||||
|  | ||||
| - Closing #220. | ||||
|   | ||||
| @@ -2,7 +2,15 @@ buildscript { | ||||
| } | ||||
|  | ||||
| def gitVersion() { | ||||
|     def process = "git for-each-ref refs/tags --sort=-authordate --format='%(refname:short)' --count=1".execute() | ||||
|     def process | ||||
|     def maybeTagOfCurrentCommit = 'git describe --contains HEAD'.execute() | ||||
|     if (maybeTagOfCurrentCommit.text.isEmpty()) { | ||||
|         println "No tag on current commit. Will take the latest one." | ||||
|         process = "git for-each-ref refs/tags --sort=-authordate --format='%(refname:short)' --count=1".execute() | ||||
|     } else { | ||||
|         println "Tag found on current commit" | ||||
|         process = 'git describe --contains HEAD'.execute() | ||||
|     } | ||||
|     return process.text.replaceAll("'", "").substring(1).replaceAll("\\.", "").trim() | ||||
| } | ||||
|  | ||||
| @@ -59,7 +67,7 @@ android { | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled true | ||||
|             shrinkResources true | ||||
|             shrinkResources false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), | ||||
|                     'proguard-rules.pro' | ||||
|         } | ||||
| @@ -104,7 +112,7 @@ dependencies { | ||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2' | ||||
|  | ||||
|     //multidex | ||||
|     implementation 'androidx.multidex:multidex:2.0.0' | ||||
|     implementation 'androidx.multidex:multidex:2.0.1' | ||||
|  | ||||
|     // About | ||||
|     implementation('com.mikepenz:aboutlibraries:6.2.0@aar') { | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="apps.amine.bou.readerforselfoss" | ||||
|     xmlns:tools="http://schemas.android.com/tools"> | ||||
|     package="apps.amine.bou.readerforselfoss"> | ||||
|  | ||||
|     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
| @@ -19,11 +18,11 @@ | ||||
|             android:theme="@style/SplashTheme"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
|  | ||||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
|             </intent-filter> | ||||
|  | ||||
|             <meta-data android:name="android.app.shortcuts" | ||||
|             <meta-data | ||||
|                 android:name="android.app.shortcuts" | ||||
|                 android:resource="@xml/shortcuts" /> | ||||
|         </activity> | ||||
|         <activity | ||||
| @@ -38,7 +37,7 @@ | ||||
|             android:parentActivityName=".HomeActivity"> | ||||
|             <meta-data | ||||
|                 android:name="android.support.PARENT_ACTIVITY" | ||||
|                 android:value="apps.amine.bou.readerforselfoss.HomeActivity" /> | ||||
|                 android:value=".HomeActivity" /> | ||||
|         </activity> | ||||
|         <activity | ||||
|             android:name=".SourcesActivity" | ||||
| @@ -56,9 +55,7 @@ | ||||
|  | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.SEND" /> | ||||
|  | ||||
|                 <category android:name="android.intent.category.DEFAULT" /> | ||||
|  | ||||
|                 <data android:mimeType="text/plain" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
| @@ -77,6 +74,9 @@ | ||||
|             android:value="true" /> | ||||
|  | ||||
|         <meta-data android:name="android.max_aspect" android:value="2.1" /> | ||||
|         <meta-data | ||||
|             android:name="preloaded_fonts" | ||||
|             android:resource="@array/preloaded_fonts" /> | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package apps.amine.bou.readerforselfoss | ||||
|  | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.os.Build | ||||
| import android.os.Bundle | ||||
| @@ -85,10 +86,12 @@ class AddSourceActivity : AppCompatActivity() { | ||||
|  | ||||
|         try { | ||||
|             val prefs = PreferenceManager.getDefaultSharedPreferences(this) | ||||
|             val settings = | ||||
|                 getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) | ||||
|             api = SelfossApi( | ||||
|                 this, | ||||
|                 this@AddSourceActivity, | ||||
|                 prefs.getBoolean("isSelfSignedCert", false), | ||||
|                 settings.getBoolean("isSelfSignedCert", false), | ||||
|                 prefs.getString("api_timeout", "-1").toLong(), | ||||
|                 prefs.getBoolean("should_log_everything", false) | ||||
|             ) | ||||
|   | ||||
| @@ -18,17 +18,13 @@ import androidx.recyclerview.widget.RecyclerView | ||||
| import androidx.appcompat.widget.SearchView | ||||
| import androidx.recyclerview.widget.StaggeredGridLayoutManager | ||||
| import androidx.recyclerview.widget.ItemTouchHelper | ||||
| import android.util.Log | ||||
| import android.view.Menu | ||||
| import android.view.MenuItem | ||||
| import android.view.View | ||||
| import android.widget.Toast | ||||
| import androidx.room.Room | ||||
| import androidx.room.RoomDatabase | ||||
| import androidx.work.Constraints | ||||
| import androidx.work.ExistingPeriodicWorkPolicy | ||||
| import androidx.work.NetworkType | ||||
| import androidx.work.OneTimeWorkRequestBuilder | ||||
| import androidx.work.PeriodicWorkRequestBuilder | ||||
| import androidx.work.WorkManager | ||||
| import apps.amine.bou.readerforselfoss.adapters.ItemCardAdapter | ||||
| @@ -69,7 +65,6 @@ import com.ashokvarma.bottomnavigation.BottomNavigationItem | ||||
| import com.ashokvarma.bottomnavigation.TextBadgeItem | ||||
| import com.ftinc.scoop.Scoop | ||||
| import com.github.stkent.amplify.tracking.Amplify | ||||
| import com.google.gson.reflect.TypeToken | ||||
| import com.mikepenz.aboutlibraries.Libs | ||||
| import com.mikepenz.aboutlibraries.LibsBuilder | ||||
| import com.mikepenz.materialdrawer.Drawer | ||||
| @@ -79,7 +74,6 @@ import com.mikepenz.materialdrawer.model.DividerDrawerItem | ||||
| import com.mikepenz.materialdrawer.model.PrimaryDrawerItem | ||||
| import com.mikepenz.materialdrawer.model.SecondaryDrawerItem | ||||
| import kotlinx.android.synthetic.main.activity_home.* | ||||
| import kotlinx.android.synthetic.main.fragment_article.* | ||||
| import org.acra.ACRA | ||||
| import retrofit2.Call | ||||
| import retrofit2.Callback | ||||
| @@ -152,6 +146,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|  | ||||
|     private lateinit var db: AppDatabase | ||||
|  | ||||
|     private lateinit var config: Config | ||||
|  | ||||
|     data class DrawerData(val tags: List<Tag>?, val sources: List<Source>?) | ||||
|  | ||||
|     override fun onStart() { | ||||
| @@ -161,6 +157,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         appColors = AppColors(this@HomeActivity) | ||||
|         config = Config(this@HomeActivity) | ||||
|  | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | ||||
| @@ -230,7 +227,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                     recyclerView: RecyclerView, | ||||
|                     viewHolder: RecyclerView.ViewHolder | ||||
|                 ): Int = | ||||
|                     if (elementsShown != UNREAD_SHOWN) { | ||||
|                     if (elementsShown != UNREAD_SHOWN && elementsShown != READ_SHOWN) { | ||||
|                         0 | ||||
|                     } else { | ||||
|                         super.getSwipeDirs( | ||||
| @@ -250,14 +247,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                     val i = items.elementAtOrNull(position) | ||||
|  | ||||
|                     if (i != null) { | ||||
|                         val adapter = recyclerView.adapter | ||||
|                         val adapter = recyclerView.adapter as ItemsAdapter<*> | ||||
|  | ||||
|                         when (adapter) { | ||||
|                             is ItemCardAdapter -> adapter.removeItemAtIndex(position) | ||||
|                             is ItemListAdapter -> adapter.removeItemAtIndex(position) | ||||
|                         val wasItemUnread = adapter.unreadItemStatusAtIndex(position) | ||||
|  | ||||
|                         adapter.handleItemAtIndex(position) | ||||
|  | ||||
|                         if (wasItemUnread) { | ||||
|                             badgeNew-- | ||||
|                         } else { | ||||
|                             badgeNew++ | ||||
|                         } | ||||
|  | ||||
|                         badgeNew-- | ||||
|                         reloadBadgeContent() | ||||
|  | ||||
|                         val tagHashes = i.tags.tags.split(",").map { it.longHash() } | ||||
| @@ -304,19 +305,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|  | ||||
|         val tabNew = | ||||
|             BottomNavigationItem( | ||||
|                 R.drawable.ic_fiber_new_black_24dp, | ||||
|                 R.drawable.ic_tab_fiber_new_black_24dp, | ||||
|                 getString(R.string.tab_new) | ||||
|             ).setActiveColor(appColors.colorAccent) | ||||
|                 .setBadgeItem(tabNewBadge) | ||||
|         val tabArchive = | ||||
|             BottomNavigationItem( | ||||
|                 R.drawable.ic_archive_black_24dp, | ||||
|                 R.drawable.ic_tab_archive_black_24dp, | ||||
|                 getString(R.string.tab_read) | ||||
|             ).setActiveColor(appColors.colorAccentDark) | ||||
|                 .setBadgeItem(tabArchiveBadge) | ||||
|         val tabStarred = | ||||
|             BottomNavigationItem( | ||||
|                 R.drawable.ic_favorite_black_24dp, | ||||
|                 R.drawable.ic_tab_favorite_black_24dp, | ||||
|                 getString(R.string.tab_favs) | ||||
|             ).setActiveColorResource(R.color.pink) | ||||
|                 .setBadgeItem(tabStarredBadge) | ||||
| @@ -478,7 +479,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|  | ||||
|             footer { | ||||
|                 primaryItem(R.string.drawer_report_bug) { | ||||
|                     icon = R.drawable.ic_bug_report | ||||
|                     icon = R.drawable.ic_bug_report_black_24dp | ||||
|                     iconTintingEnabled = true | ||||
|                     onClick { _ -> | ||||
|                         val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(Config.trackerUrl)) | ||||
| @@ -488,7 +489,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                 } | ||||
|  | ||||
|                 primaryItem(R.string.title_activity_settings) { | ||||
|                     icon = R.drawable.ic_settings | ||||
|                     icon = R.drawable.ic_settings_black_24dp | ||||
|                     iconTintingEnabled = true | ||||
|                     onClick { _ -> | ||||
|                         startActivityForResult( | ||||
| @@ -518,7 +519,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                         ) | ||||
|                     } | ||||
|                 } else { | ||||
|                     val filteredTags = maybeTags.filterNot { hiddenTags.contains(it.tag) } | ||||
|                     val filteredTags = maybeTags | ||||
|                         .filterNot { hiddenTags.contains(it.tag) } | ||||
|                         .sortedBy { it.unread == 0 } | ||||
|                     tagsBadge = filteredTags.map { | ||||
|                         val gd = GradientDrawable() | ||||
|                         val color = try { | ||||
| @@ -531,12 +534,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                         gd.shape = GradientDrawable.RECTANGLE | ||||
|                         gd.setSize(30, 30) | ||||
|                         gd.cornerRadius = 30F | ||||
|                         drawer.addItem( | ||||
|                         var drawerItem = | ||||
|                             PrimaryDrawerItem() | ||||
|                                 .withName(it.tag) | ||||
|                                 .withIdentifier(it.tag.longHash()) | ||||
|                                 .withIcon(gd) | ||||
|                                 .withBadge("${it.unread}") | ||||
|                                 .withBadgeStyle( | ||||
|                                     BadgeStyle().withTextColor(Color.WHITE) | ||||
|                                         .withColor(appColors.colorAccent) | ||||
| @@ -547,6 +549,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                                     getElementsAccordingToTab() | ||||
|                                     false | ||||
|                                 } | ||||
|                         if (it.unread > 0) { | ||||
|                             drawerItem = drawerItem.withBadge("${it.unread}") | ||||
|                         } | ||||
|                         drawer.addItem( | ||||
|                             drawerItem | ||||
|                         ) | ||||
|  | ||||
|                         (it.tag.longHash() to it.unread) | ||||
| @@ -578,12 +585,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                         gd.shape = GradientDrawable.RECTANGLE | ||||
|                         gd.setSize(30, 30) | ||||
|                         gd.cornerRadius = 30F | ||||
|                         drawer.addItem( | ||||
|                         var drawerItem = | ||||
|                             PrimaryDrawerItem() | ||||
|                                 .withName(it.tag) | ||||
|                                 .withIdentifier(it.tag.longHash()) | ||||
|                                 .withIcon(gd) | ||||
|                                 .withBadge("${it.unread}") | ||||
|                                 .withBadgeStyle( | ||||
|                                     BadgeStyle().withTextColor(Color.WHITE) | ||||
|                                         .withColor(appColors.colorAccent) | ||||
| @@ -594,6 +600,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                                     getElementsAccordingToTab() | ||||
|                                     false | ||||
|                                 } | ||||
|                         if (it.unread > 0) { | ||||
|                             drawerItem = drawerItem.withBadge("${it.unread}") | ||||
|                         } | ||||
|                         drawer.addItem( | ||||
|                             drawerItem | ||||
|                         ) | ||||
|  | ||||
|                         (it.tag.longHash() to it.unread) | ||||
| @@ -680,7 +691,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                     PrimaryDrawerItem() | ||||
|                         .withName(R.string.action_about) | ||||
|                         .withSelectable(false) | ||||
|                         .withIcon(R.drawable.ic_info_outline) | ||||
|                         .withIcon(R.drawable.ic_info_outline_white_24dp) | ||||
|                         .withIconTintingEnabled(true) | ||||
|                         .withOnDrawerItemClickListener { _, _, _ -> | ||||
|                             LibsBuilder() | ||||
| @@ -1136,7 +1147,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                             fullHeightCards, | ||||
|                             appColors, | ||||
|                             debugReadingItems, | ||||
|                             userIdentifier | ||||
|                             userIdentifier, | ||||
|                             config | ||||
|                         ) { | ||||
|                             updateItems(it) | ||||
|                         } | ||||
| @@ -1152,7 +1164,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                             articleViewer, | ||||
|                             debugReadingItems, | ||||
|                             userIdentifier, | ||||
|                             appColors | ||||
|                             appColors, | ||||
|                             config | ||||
|                         ) { | ||||
|                             updateItems(it) | ||||
|                         } | ||||
| @@ -1341,12 +1354,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { | ||||
|                                         ).show() | ||||
|                                         tabNewBadge.removeBadge() | ||||
|  | ||||
|  | ||||
|                                         tagsBadge = itemsByTag.map { | ||||
|                                             (it.key to ((tagsBadge[it.key] ?: it.value) - it.value)) | ||||
|                                         }.toMap() | ||||
|  | ||||
|                                         reloadTagsBadges() | ||||
|                                         handleDrawerItems() | ||||
|  | ||||
|                                         getElementsAccordingToTab() | ||||
|                                     } else { | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import android.preference.PreferenceManager | ||||
| import androidx.multidex.MultiDexApplication | ||||
| import android.widget.ImageView | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth | ||||
| import com.bumptech.glide.Glide | ||||
| import com.bumptech.glide.request.RequestOptions | ||||
| import com.ftinc.scoop.Scoop | ||||
| @@ -42,10 +43,11 @@ import java.util.UUID.randomUUID | ||||
|     ReportField.USER_APP_START_DATE, ReportField.USER_COMMENT, ReportField.USER_CRASH_DATE, ReportField.USER_EMAIL, ReportField.CUSTOM_DATA], | ||||
|           buildConfigClass = BuildConfig::class) | ||||
| class MyApp : MultiDexApplication() { | ||||
|     private lateinit var config: Config | ||||
|  | ||||
|     override fun onCreate() { | ||||
|         super.onCreate() | ||||
|  | ||||
|         config = Config(baseContext) | ||||
|         initAmplify() | ||||
|  | ||||
|         val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) | ||||
| @@ -105,7 +107,7 @@ class MyApp : MultiDexApplication() { | ||||
|                 tag: String? | ||||
|             ) { | ||||
|                 Glide.with(imageView?.context) | ||||
|                     .load(uri) | ||||
|                     .loadMaybeBasicAuth(config, uri.toString()) | ||||
|                     .apply(RequestOptions.fitCenterTransform().placeholder(placeholder)) | ||||
|                     .into(imageView) | ||||
|             } | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package apps.amine.bou.readerforselfoss | ||||
|  | ||||
| import android.content.Context | ||||
| import android.content.SharedPreferences | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.os.Build | ||||
| import android.os.Bundle | ||||
| @@ -13,6 +15,7 @@ import android.view.Menu | ||||
| import android.view.MenuItem | ||||
| import android.view.ViewGroup | ||||
| import android.widget.Toast | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.room.Room | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.Item | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi | ||||
| @@ -25,6 +28,7 @@ import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3 | ||||
| import apps.amine.bou.readerforselfoss.themes.AppColors | ||||
| import apps.amine.bou.readerforselfoss.themes.Toppings | ||||
| import apps.amine.bou.readerforselfoss.transformers.DepthPageTransformer | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible | ||||
| import apps.amine.bou.readerforselfoss.utils.persistence.toEntity | ||||
| @@ -51,6 +55,11 @@ class ReaderActivity : AppCompatActivity() { | ||||
|     private lateinit var toolbarMenu: Menu | ||||
|  | ||||
|     private lateinit var db: AppDatabase | ||||
|     private lateinit var prefs: SharedPreferences | ||||
|  | ||||
|     private var activeAlignment: Int = 1 | ||||
|     val JUSTIFY = 1 | ||||
|     val ALIGN_LEFT = 2 | ||||
|  | ||||
|     private fun showMenuItem(willAddToFavorite: Boolean) { | ||||
|         toolbarMenu.findItem(R.id.save).isVisible = willAddToFavorite | ||||
| @@ -65,6 +74,8 @@ class ReaderActivity : AppCompatActivity() { | ||||
|         showMenuItem(false) | ||||
|     } | ||||
|  | ||||
|     private lateinit var editor: SharedPreferences.Editor | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | ||||
| @@ -85,16 +96,21 @@ class ReaderActivity : AppCompatActivity() { | ||||
|         supportActionBar?.setDisplayHomeAsUpEnabled(true) | ||||
|         supportActionBar?.setDisplayShowHomeEnabled(true) | ||||
|  | ||||
|         val prefs = PreferenceManager.getDefaultSharedPreferences(this) | ||||
|         val settings = | ||||
|             getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) | ||||
|  | ||||
|         prefs = PreferenceManager.getDefaultSharedPreferences(this) | ||||
|         editor = prefs.edit() | ||||
|  | ||||
|         debugReadingItems = prefs.getBoolean("read_debug", false) | ||||
|         userIdentifier = prefs.getString("unique_id", "") | ||||
|         markOnScroll = prefs.getBoolean("mark_on_scroll", false) | ||||
|         activeAlignment = prefs.getInt("text_align", JUSTIFY) | ||||
|  | ||||
|         api = SelfossApi( | ||||
|             this, | ||||
|             this@ReaderActivity, | ||||
|             prefs.getBoolean("isSelfSignedCert", false), | ||||
|             settings.getBoolean("isSelfSignedCert", false), | ||||
|             prefs.getString("api_timeout", "-1").toLong(), | ||||
|             prefs.getBoolean("should_log_everything", false) | ||||
|         ) | ||||
| @@ -223,6 +239,11 @@ class ReaderActivity : AppCompatActivity() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun alignmentMenu(showJustify: Boolean) { | ||||
|         toolbarMenu.findItem(R.id.align_left).isVisible = !showJustify | ||||
|         toolbarMenu.findItem(R.id.align_justify).isVisible = showJustify | ||||
|     } | ||||
|  | ||||
|     override fun onCreateOptionsMenu(menu: Menu): Boolean { | ||||
|         val inflater = menuInflater | ||||
|         inflater.inflate(R.menu.reader_menu, menu) | ||||
| @@ -233,6 +254,11 @@ class ReaderActivity : AppCompatActivity() { | ||||
|         } else { | ||||
|             canFavorite() | ||||
|         } | ||||
|         if (activeAlignment == JUSTIFY) { | ||||
|             alignmentMenu(false) | ||||
|         } else { | ||||
|             alignmentMenu(true) | ||||
|         } | ||||
|  | ||||
|         return true | ||||
|     } | ||||
| @@ -314,10 +340,29 @@ class ReaderActivity : AppCompatActivity() { | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             R.id.align_left -> { | ||||
|                 editor.putInt("text_align", ALIGN_LEFT) | ||||
|                 editor.apply() | ||||
|                 alignmentMenu(true) | ||||
|                 refreshFragment() | ||||
|             } | ||||
|             R.id.align_justify -> { | ||||
|                 editor.putInt("text_align", JUSTIFY) | ||||
|                 editor.apply() | ||||
|                 alignmentMenu(false) | ||||
|                 refreshFragment() | ||||
|             } | ||||
|         } | ||||
|         return super.onOptionsItemSelected(item) | ||||
|     } | ||||
|  | ||||
|     private fun refreshFragment() { | ||||
|         finish() | ||||
|         overridePendingTransition(0, 0) | ||||
|         startActivity(intent) | ||||
|         overridePendingTransition(0, 0) | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         var allItems: ArrayList<Item> = ArrayList() | ||||
|     } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package apps.amine.bou.readerforselfoss | ||||
|  | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.content.res.ColorStateList | ||||
| import android.os.Build | ||||
| @@ -13,6 +14,7 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.Source | ||||
| import apps.amine.bou.readerforselfoss.themes.AppColors | ||||
| import apps.amine.bou.readerforselfoss.themes.Toppings | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible | ||||
| import com.ftinc.scoop.Scoop | ||||
| import kotlinx.android.synthetic.main.activity_sources.* | ||||
| @@ -54,12 +56,14 @@ class SourcesActivity : AppCompatActivity() { | ||||
|         super.onResume() | ||||
|         val mLayoutManager = LinearLayoutManager(this) | ||||
|  | ||||
|         val settings = | ||||
|             getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) | ||||
|         val prefs = PreferenceManager.getDefaultSharedPreferences(this) | ||||
|  | ||||
|         val api = SelfossApi( | ||||
|             this, | ||||
|             this@SourcesActivity, | ||||
|             prefs.getBoolean("isSelfSignedCert", false), | ||||
|             settings.getBoolean("isSelfSignedCert", false), | ||||
|             prefs.getString("api_timeout", "-1").toLong(), | ||||
|             prefs.getBoolean("should_log_everything", false) | ||||
|         ) | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse | ||||
| import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase | ||||
| import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity | ||||
| import apps.amine.bou.readerforselfoss.themes.AppColors | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.LinkOnTouchListener | ||||
| import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent | ||||
| import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper | ||||
| @@ -51,6 +52,7 @@ class ItemCardAdapter( | ||||
|     override val appColors: AppColors, | ||||
|     override val debugReadingItems: Boolean, | ||||
|     override val userIdentifier: String, | ||||
|     override val config: Config, | ||||
|     override val updateItems: (ArrayList<Item>) -> Unit | ||||
| ) : ItemsAdapter<ItemCardAdapter.ViewHolder>() { | ||||
|     private val c: Context = app.baseContext | ||||
| @@ -86,7 +88,7 @@ class ItemCardAdapter( | ||||
|             holder.mView.itemImage.setImageDrawable(null) | ||||
|         } else { | ||||
|             holder.mView.itemImage.visibility = View.VISIBLE | ||||
|             c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage) | ||||
|             c.bitmapCenterCrop(config, itm.getThumbnail(c), holder.mView.itemImage) | ||||
|         } | ||||
|  | ||||
|         if (itm.getIcon(c).isEmpty()) { | ||||
| @@ -99,7 +101,7 @@ class ItemCardAdapter( | ||||
|                     .build(itm.sourcetitle.toTextDrawableString(c), color) | ||||
|             holder.mView.sourceImage.setImageDrawable(drawable) | ||||
|         } else { | ||||
|             c.circularBitmapDrawable(itm.getIcon(c), holder.mView.sourceImage) | ||||
|             c.circularBitmapDrawable(config, itm.getIcon(c), holder.mView.sourceImage) | ||||
|         } | ||||
|  | ||||
|         holder.mView.favButton.isLiked = itm.starred | ||||
|   | ||||
| @@ -20,6 +20,7 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse | ||||
| import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase | ||||
| import apps.amine.bou.readerforselfoss.themes.AppColors | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.LinkOnTouchListener | ||||
| import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent | ||||
| import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper | ||||
| @@ -52,6 +53,7 @@ class ItemListAdapter( | ||||
|     override val debugReadingItems: Boolean, | ||||
|     override val userIdentifier: String, | ||||
|     override val appColors: AppColors, | ||||
|     override val config: Config, | ||||
|     override val updateItems: (ArrayList<Item>) -> Unit | ||||
| ) : ItemsAdapter<ItemListAdapter.ViewHolder>() { | ||||
|     private val generator: ColorGenerator = ColorGenerator.MATERIAL | ||||
| @@ -108,10 +110,10 @@ class ItemListAdapter( | ||||
|  | ||||
|                 holder.mView.itemImage.setImageDrawable(drawable) | ||||
|             } else { | ||||
|                 c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage) | ||||
|                 c.circularBitmapDrawable(config, itm.getIcon(c), holder.mView.itemImage) | ||||
|             } | ||||
|         } else { | ||||
|             c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage) | ||||
|             c.bitmapCenterCrop(config, itm.getThumbnail(c), holder.mView.itemImage) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse | ||||
| import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase | ||||
| import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity | ||||
| import apps.amine.bou.readerforselfoss.themes.AppColors | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible | ||||
| import apps.amine.bou.readerforselfoss.utils.persistence.toEntity | ||||
| @@ -31,6 +32,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|     abstract val userIdentifier: String | ||||
|     abstract val app: Activity | ||||
|     abstract val appColors: AppColors | ||||
|     abstract val config: Config | ||||
|     abstract val updateItems: (ArrayList<Item>) -> Unit | ||||
|  | ||||
|     fun updateAllItems(newItems: ArrayList<Item>) { | ||||
| @@ -39,7 +41,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|         updateItems(items) | ||||
|     } | ||||
|  | ||||
|     private fun doUnmark(i: Item, position: Int) { | ||||
|     private fun unmarkSnackbar(i: Item, position: Int) { | ||||
|         val s = Snackbar | ||||
|             .make( | ||||
|                 app.findViewById(R.id.coordLayout), | ||||
| @@ -69,12 +71,11 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|                             } | ||||
|                             notifyItemRemoved(position) | ||||
|                             updateItems(items) | ||||
|                             doUnmark(i, position) | ||||
|                         } | ||||
|                     }) | ||||
|                 } else { | ||||
|                     thread { | ||||
|                         db.actionsDao().deleteReadActionForArticle(i.id) | ||||
|                         db.actionsDao().insertAllActions(ActionEntity(i.id, false, true, false, false)) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -85,7 +86,64 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|         s.show() | ||||
|     } | ||||
|  | ||||
|     fun removeItemAtIndex(position: Int) { | ||||
|     private fun markSnackbar(i: Item, position: Int) { | ||||
|         val s = Snackbar | ||||
|             .make( | ||||
|                 app.findViewById(R.id.coordLayout), | ||||
|                 R.string.marked_as_unread, | ||||
|                 Snackbar.LENGTH_LONG | ||||
|             ) | ||||
|             .setAction(R.string.undo_string) { | ||||
|                 items.add(position, i) | ||||
|                 thread { | ||||
|                     db.itemsDao().delete(i.toEntity()) | ||||
|                 } | ||||
|                 notifyItemInserted(position) | ||||
|                 updateItems(items) | ||||
|  | ||||
|                 if (app.isNetworkAccessible(null)) { | ||||
|                     api.markItem(i.id).enqueue(object : Callback<SuccessResponse> { | ||||
|                         override fun onResponse( | ||||
|                             call: Call<SuccessResponse>, | ||||
|                             response: Response<SuccessResponse> | ||||
|                         ) { | ||||
|                         } | ||||
|  | ||||
|                         override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
|                             items.remove(i) | ||||
|                             thread { | ||||
|                                 db.itemsDao().insertAllItems(i.toEntity()) | ||||
|                             } | ||||
|                             notifyItemRemoved(position) | ||||
|                             updateItems(items) | ||||
|                         } | ||||
|                     }) | ||||
|                 } else { | ||||
|                     thread { | ||||
|                         db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false)) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         val view = s.view | ||||
|         val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text) | ||||
|         tv.setTextColor(Color.WHITE) | ||||
|         s.show() | ||||
|     } | ||||
|  | ||||
|     fun handleItemAtIndex(position: Int) { | ||||
|         if (unreadItemStatusAtIndex(position)) { | ||||
|             readItemAtIndex(position) | ||||
|         } else { | ||||
|             unreadItemAtIndex(position) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun unreadItemStatusAtIndex(position: Int): Boolean { | ||||
|         return items[position].unread | ||||
|     } | ||||
|  | ||||
|     private fun readItemAtIndex(position: Int) { | ||||
|         val i = items[position] | ||||
|         items.remove(i) | ||||
|         notifyItemRemoved(position) | ||||
| @@ -103,7 +161,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|                 ) { | ||||
|                     if (!response.succeeded() && debugReadingItems) { | ||||
|                         val message = | ||||
|                             "message: ${response.message()} " + | ||||
|                             "MARK message: ${response.message()} " + | ||||
|                                     "response isSuccess: ${response.isSuccessful} " + | ||||
|                                     "response code: ${response.code()} " + | ||||
|                                     "response message: ${response.message()} " + | ||||
| @@ -114,7 +172,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|                         Toast.makeText(app.baseContext, message, Toast.LENGTH_LONG).show() | ||||
|                     } | ||||
|  | ||||
|                     doUnmark(i, position) | ||||
|                     unmarkSnackbar(i, position) | ||||
|                 } | ||||
|  | ||||
|                 override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
| @@ -127,7 +185,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|                         app.getString(R.string.cant_mark_read), | ||||
|                         Toast.LENGTH_SHORT | ||||
|                     ).show() | ||||
|                     items.add(i) | ||||
|                     items.add(position, i) | ||||
|                     notifyItemInserted(position) | ||||
|                     updateItems(items) | ||||
|  | ||||
| @@ -139,7 +197,64 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|         } else { | ||||
|             thread { | ||||
|                 db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false)) | ||||
|                 doUnmark(i, position) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun unreadItemAtIndex(position: Int) { | ||||
|         val i = items[position] | ||||
|         items.remove(i) | ||||
|         notifyItemRemoved(position) | ||||
|         updateItems(items) | ||||
|  | ||||
|         thread { | ||||
|             db.itemsDao().insertAllItems(i.toEntity()) | ||||
|         } | ||||
|  | ||||
|         if (app.isNetworkAccessible(null)) { | ||||
|             api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> { | ||||
|                 override fun onResponse( | ||||
|                     call: Call<SuccessResponse>, | ||||
|                     response: Response<SuccessResponse> | ||||
|                 ) { | ||||
|                     if (!response.succeeded() && debugReadingItems) { | ||||
|                         val message = | ||||
|                             "UNMARK message: ${response.message()} " + | ||||
|                                     "response isSuccess: ${response.isSuccessful} " + | ||||
|                                     "response code: ${response.code()} " + | ||||
|                                     "response message: ${response.message()} " + | ||||
|                                     "response errorBody: ${response.errorBody()?.string()} " + | ||||
|                                     "body success: ${response.body()?.success} " + | ||||
|                                     "body isSuccess: ${response.body()?.isSuccess}" | ||||
|                         ACRA.getErrorReporter().maybeHandleSilentException(Exception(message), app) | ||||
|                         Toast.makeText(app.baseContext, message, Toast.LENGTH_LONG).show() | ||||
|                     } | ||||
|  | ||||
|                     markSnackbar(i, position) | ||||
|                 } | ||||
|  | ||||
|                 override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
|                     if (debugReadingItems) { | ||||
|                         ACRA.getErrorReporter().maybeHandleSilentException(t, app) | ||||
|                         Toast.makeText(app.baseContext, t.message, Toast.LENGTH_LONG).show() | ||||
|                     } | ||||
|                     Toast.makeText( | ||||
|                         app, | ||||
|                         app.getString(R.string.cant_mark_unread), | ||||
|                         Toast.LENGTH_SHORT | ||||
|                     ).show() | ||||
|                     items.add(i) | ||||
|                     notifyItemInserted(position) | ||||
|                     updateItems(items) | ||||
|  | ||||
|                     thread { | ||||
|                         db.itemsDao().delete(i.toEntity()) | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|         } else { | ||||
|             thread { | ||||
|                 db.actionsDao().insertAllActions(ActionEntity(i.id, false, true, false, false)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import apps.amine.bou.readerforselfoss.R | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.Source | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible | ||||
| import apps.amine.bou.readerforselfoss.utils.toTextDrawableString | ||||
| @@ -29,6 +30,7 @@ class SourcesListAdapter( | ||||
| ) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() { | ||||
|     private val c: Context = app.baseContext | ||||
|     private val generator: ColorGenerator = ColorGenerator.MATERIAL | ||||
|     private lateinit var config: Config | ||||
|  | ||||
|     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { | ||||
|         val v = LayoutInflater.from(c).inflate( | ||||
| @@ -41,6 +43,7 @@ class SourcesListAdapter( | ||||
|  | ||||
|     override fun onBindViewHolder(holder: ViewHolder, position: Int) { | ||||
|         val itm = items[position] | ||||
|         config = Config(c) | ||||
|  | ||||
|         if (itm.getIcon(c).isEmpty()) { | ||||
|             val color = generator.getColor(itm.title) | ||||
| @@ -52,7 +55,7 @@ class SourcesListAdapter( | ||||
|                     .build(itm.title.toTextDrawableString(c), color) | ||||
|             holder.mView.itemImage.setImageDrawable(drawable) | ||||
|         } else { | ||||
|             c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage) | ||||
|             c.circularBitmapDrawable(config, itm.getIcon(c), holder.mView.itemImage) | ||||
|         } | ||||
|  | ||||
|         holder.mView.sourceTitle.text = itm.title | ||||
|   | ||||
| @@ -12,7 +12,10 @@ import com.burgstaller.okhttp.digest.CachingAuthenticator | ||||
| import com.burgstaller.okhttp.digest.Credentials | ||||
| import com.burgstaller.okhttp.digest.DigestAuthenticator | ||||
| import com.google.gson.GsonBuilder | ||||
| import okhttp3.Interceptor | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import okhttp3.logging.HttpLoggingInterceptor | ||||
| import retrofit2.Call | ||||
| import retrofit2.Retrofit | ||||
| @@ -62,6 +65,17 @@ class SelfossApi( | ||||
|             .maybeWithSelfSigned(isWithSelfSignedCert) | ||||
|             .authenticator(CachingAuthenticatorDecorator(this, authCache)) | ||||
|             .addInterceptor(AuthenticationCacheInterceptor(authCache)) | ||||
|             .addInterceptor(object: Interceptor { | ||||
|                 override fun intercept(chain: Interceptor.Chain): Response { | ||||
|                     val request: Request = chain.request() | ||||
|                     val response: Response = chain.proceed(request) | ||||
|  | ||||
|                     if (response.code() == 408) { | ||||
|                         return response | ||||
|                     } | ||||
|                     return response | ||||
|                 } | ||||
|             }) | ||||
|     } | ||||
|  | ||||
|     init { | ||||
|   | ||||
| @@ -46,7 +46,7 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con | ||||
|                 .setOngoing(true) | ||||
|                 .setPriority(PRIORITY_LOW) | ||||
|                 .setChannelId(Config.syncChannelId) | ||||
|                 .setSmallIcon(R.drawable.ic_cloud_download) | ||||
|                 .setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp) | ||||
|  | ||||
|             notificationManager.notify(1, notification.build()) | ||||
|  | ||||
| @@ -101,7 +101,7 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con | ||||
|                                     .setChannelId(Config.newItemsChannelId) | ||||
|                                     .setContentIntent(pendingIntent) | ||||
|                                     .setAutoCancel(true) | ||||
|                                     .setSmallIcon(R.drawable.ic_fiber_new_black_24dp) | ||||
|                                     .setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp) | ||||
|  | ||||
|                                 Timer("", false).schedule(4000) { | ||||
|                                     notificationManager.notify(2, newItemsNotification.build()) | ||||
|   | ||||
| @@ -3,6 +3,8 @@ package apps.amine.bou.readerforselfoss.fragments | ||||
| import android.content.Context | ||||
| import android.content.SharedPreferences | ||||
| import android.content.res.ColorStateList | ||||
| import android.content.res.TypedArray | ||||
| import android.graphics.Typeface | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.os.Build | ||||
| import android.os.Bundle | ||||
| @@ -19,6 +21,7 @@ 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.R | ||||
| import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi | ||||
| @@ -34,6 +37,7 @@ import apps.amine.bou.readerforselfoss.themes.AppColors | ||||
| 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.isEmptyOrNullOrNullString | ||||
| import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible | ||||
| @@ -44,6 +48,7 @@ import apps.amine.bou.readerforselfoss.utils.succeeded | ||||
| import com.bumptech.glide.Glide | ||||
| import com.bumptech.glide.request.RequestOptions | ||||
| import com.github.rubensousa.floatingtoolbar.FloatingToolbar | ||||
| import kotlinx.android.synthetic.main.fragment_article.* | ||||
| import kotlinx.android.synthetic.main.fragment_article.view.* | ||||
| import org.acra.ACRA | ||||
| import retrofit2.Call | ||||
| @@ -67,6 +72,16 @@ class ArticleFragment : Fragment() { | ||||
|     private lateinit var fab: FloatingActionButton | ||||
|     private lateinit var appColors: AppColors | ||||
|     private lateinit var db: AppDatabase | ||||
|     private lateinit var textAlignment: String | ||||
|     private lateinit var config: Config | ||||
|  | ||||
|     private var rootView: ViewGroup? = null | ||||
|  | ||||
|     private lateinit var prefs: SharedPreferences | ||||
|  | ||||
|     private var typeface: Typeface? = null | ||||
|     private var resId: Int = 0 | ||||
|     private var font = "" | ||||
|  | ||||
|     override fun onStop() { | ||||
|         super.onStop() | ||||
| @@ -77,6 +92,7 @@ class ArticleFragment : Fragment() { | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         appColors = AppColors(activity!!) | ||||
|         config = Config(activity!!) | ||||
|  | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | ||||
| @@ -89,9 +105,6 @@ class ArticleFragment : Fragment() { | ||||
|         ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build() | ||||
|     } | ||||
|  | ||||
|     private var rootView: ViewGroup? = null | ||||
|  | ||||
|  | ||||
|     override fun onCreateView( | ||||
|         inflater: LayoutInflater, | ||||
|         container: ViewGroup?, | ||||
| @@ -107,10 +120,24 @@ class ArticleFragment : Fragment() { | ||||
|             contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!) | ||||
|             contentSource = allItems[pageNumber.toInt()].sourceAndDateText() | ||||
|  | ||||
|             val prefs = PreferenceManager.getDefaultSharedPreferences(activity) | ||||
|             prefs = PreferenceManager.getDefaultSharedPreferences(activity) | ||||
|             editor = prefs.edit() | ||||
|             fontSize = prefs.getString("reader_font_size", "16").toInt() | ||||
|  | ||||
|             font = prefs.getString("reader_font", "") | ||||
|             if (font.isNotEmpty()) { | ||||
|                 resId = context!!.resources.getIdentifier(font, "font", context!!.packageName) | ||||
|                 typeface = try { | ||||
|                     ResourcesCompat.getFont(context!!, resId)!! | ||||
|                 } catch (e: java.lang.Exception) { | ||||
|                     // ACRA.getErrorReporter().maybeHandleSilentException(Throwable("Font loading issue: ${e.message}"), context!!) | ||||
|                     // Just to be sure | ||||
|                     null | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             refreshAlignment() | ||||
|  | ||||
|             val settings = activity!!.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) | ||||
|             val debugReadingItems = prefs.getBoolean("read_debug", false) | ||||
|  | ||||
| @@ -198,20 +225,26 @@ class ArticleFragment : Fragment() { | ||||
|             ) | ||||
|  | ||||
|             rootView!!.source.text = contentSource | ||||
|             if (typeface != null) { | ||||
|                 rootView!!.source.typeface = typeface | ||||
|             } | ||||
|  | ||||
|             if (contentText.isEmptyOrNullOrNullString()) { | ||||
|                 getContentFromMercury(customTabsIntent, prefs) | ||||
|             } else { | ||||
|                 rootView!!.titleView.text = contentTitle | ||||
|                 if (typeface != null) { | ||||
|                     rootView!!.titleView.typeface = typeface | ||||
|                 } | ||||
|  | ||||
|                 htmlToWebview(contentText, prefs) | ||||
|                 htmlToWebview() | ||||
|  | ||||
|                 if (!contentImage.isEmptyOrNullOrNullString() && context != null) { | ||||
|                     rootView!!.imageView.visibility = View.VISIBLE | ||||
|                     Glide | ||||
|                         .with(context!!) | ||||
|                         .asBitmap() | ||||
|                         .load(contentImage) | ||||
|                         .loadMaybeBasicAuth(config, contentImage) | ||||
|                         .apply(RequestOptions.fitCenterTransform()) | ||||
|                         .into(rootView!!.imageView) | ||||
|                 } else { | ||||
| @@ -248,6 +281,14 @@ class ArticleFragment : Fragment() { | ||||
|         return rootView | ||||
|     } | ||||
|  | ||||
|     private fun refreshAlignment() { | ||||
|         textAlignment = when (prefs.getInt("text_align", 1)) { | ||||
|             1 -> "justify" | ||||
|             2 -> "left" | ||||
|             else -> "justify" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun getContentFromMercury( | ||||
|         customTabsIntent: CustomTabsIntent, | ||||
|         prefs: SharedPreferences | ||||
| @@ -269,12 +310,15 @@ class ArticleFragment : Fragment() { | ||||
|                             if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) { | ||||
|                                 try { | ||||
|                                     rootView!!.titleView.text = response.body()!!.title | ||||
|                                     if (typeface != null) { | ||||
|                                         rootView!!.titleView.typeface = typeface | ||||
|                                     } | ||||
|                                     try { | ||||
|                                         // Note: Mercury may return relative urls... If it does the url val will not be changed. | ||||
|                                         URL(response.body()!!.url) | ||||
|                                         url = response.body()!!.url | ||||
|                                     } catch (e: MalformedURLException) { | ||||
|                                         ACRA.getErrorReporter().maybeHandleSilentException(e, activity!!) | ||||
|                                         // Mercury returned a relative url. We do nothing. | ||||
|                                     } | ||||
|                                 } catch (e: Exception) { | ||||
|                                     if (context != null) { | ||||
| @@ -283,7 +327,8 @@ class ArticleFragment : Fragment() { | ||||
|                                 } | ||||
|  | ||||
|                                 try { | ||||
|                                     htmlToWebview(response.body()!!.content.orEmpty(), prefs) | ||||
|                                     contentText = response.body()!!.content.orEmpty() | ||||
|                                     htmlToWebview() | ||||
|                                 } catch (e: Exception) { | ||||
|                                     if (context != null) { | ||||
|                                         ACRA.getErrorReporter().maybeHandleSilentException(e, context!!) | ||||
| @@ -297,7 +342,7 @@ class ArticleFragment : Fragment() { | ||||
|                                             Glide | ||||
|                                                 .with(context!!) | ||||
|                                                 .asBitmap() | ||||
|                                                 .load(response.body()!!.lead_image_url) | ||||
|                                                 .loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty()) | ||||
|                                                 .apply(RequestOptions.fitCenterTransform()) | ||||
|                                                 .into(rootView!!.imageView) | ||||
|                                         } catch (e: Exception) { | ||||
| @@ -346,9 +391,14 @@ class ArticleFragment : Fragment() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun htmlToWebview(c: String, prefs: SharedPreferences) { | ||||
|     private fun htmlToWebview() { | ||||
|         val stringColor = String.format("#%06X", 0xFFFFFF and appColors.colorAccent) | ||||
|  | ||||
|         val attrs: IntArray = intArrayOf(android.R.attr.fontFamily) | ||||
|         val a: TypedArray = context!!.obtainStyledAttributes(resId, attrs) | ||||
|  | ||||
|  | ||||
|         rootView!!.webcontent.settings.standardFontFamily = a.getString(0) | ||||
|         rootView!!.webcontent.visibility = View.VISIBLE | ||||
|         val (textColor, backgroundColor) = if (appColors.isDarkTheme) { | ||||
|             if (context != null) { | ||||
| @@ -408,10 +458,29 @@ class ArticleFragment : Fragment() { | ||||
|             ACRA.getErrorReporter().maybeHandleSilentException(e, activity!!) | ||||
|         } | ||||
|  | ||||
|         val fontName =  when (font) { | ||||
|             getString(R.string.open_sans_font_id) -> "Open Sans" | ||||
|             getString(R.string.roboto_font_id) -> "Roboto" | ||||
|             else -> "" | ||||
|         } | ||||
|  | ||||
|         val fontLinkAndStyle = if (font.isNotEmpty()) { | ||||
|             """<link href="https://fonts.googleapis.com/css?family=${fontName.replace(" ", "+")}" rel="stylesheet"> | ||||
|                 |<style> | ||||
|                 |   * { | ||||
|                 |       font-family: '$fontName'; | ||||
|                 |   } | ||||
|                 |</style> | ||||
|             """.trimMargin() | ||||
|         } else { | ||||
|             "" | ||||
|         } | ||||
|  | ||||
|         rootView!!.webcontent.loadDataWithBaseURL( | ||||
|             baseUrl, | ||||
|             """<html> | ||||
|                 |<head> | ||||
|                 |   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|                 |   <style> | ||||
|                 |      img { | ||||
|                 |        display: inline-block; | ||||
| @@ -427,12 +496,13 @@ class ArticleFragment : Fragment() { | ||||
|                 |      } | ||||
|                 |      * { | ||||
|                 |        font-size: ${fontSize}px; | ||||
|                 |        text-align: justify; | ||||
|                 |        text-align: $textAlignment; | ||||
|                 |        word-break: break-word; | ||||
|                 |        overflow:hidden; | ||||
|                 |        line-height: 1.5em; | ||||
|                 |      } | ||||
|                 |      a, pre, code { | ||||
|                 |        text-align: left; | ||||
|                 |        text-align: $textAlignment; | ||||
|                 |      } | ||||
|                 |      pre, code { | ||||
|                 |        white-space: pre-wrap; | ||||
| @@ -440,9 +510,10 @@ class ArticleFragment : Fragment() { | ||||
|                 |        background-color: $stringBackgroundColor; | ||||
|                 |      } | ||||
|                 |   </style> | ||||
|                 |   $fontLinkAndStyle | ||||
|                 |</head> | ||||
|                 |<body> | ||||
|                 |   $c | ||||
|                 |   $contentText | ||||
|                 |</body>""".trimMargin(), | ||||
|             "text/html", | ||||
|             "utf-8", | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.res.Configuration; | ||||
| import android.content.res.Resources; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| @@ -24,6 +25,7 @@ import android.text.Editable; | ||||
| import android.text.InputFilter; | ||||
| import android.text.Spanned; | ||||
| import android.text.TextWatcher; | ||||
| import android.util.Log; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.MenuItem; | ||||
| @@ -124,6 +126,27 @@ public class SettingsActivity extends AppCompatPreferenceActivity { | ||||
|     @TargetApi(Build.VERSION_CODES.HONEYCOMB) | ||||
|     public void onBuildHeaders(List<Header> target) { | ||||
|         loadHeadersFromResource(R.xml.pref_headers, target); | ||||
|  | ||||
|         AppColors appColors = new AppColors(this); | ||||
|         if (appColors != null && appColors.isDarkTheme()) { | ||||
|             for (Header header : target) { | ||||
|                 tryLoadIconDark(header); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void tryLoadIconDark(Header header){ | ||||
|         try{ | ||||
|             if (header.fragmentArguments != null) { | ||||
|                 String iconDark = header.fragmentArguments.getString("iconDark"); | ||||
|                 int iconDarkId = getResources().getIdentifier(iconDark, "drawable", getPackageName()); | ||||
|                 if (iconDarkId != 0) { | ||||
|                     header.iconRes = iconDarkId; | ||||
|                 } | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             Log.e("SettingsActivity", "Can not load dark icon", e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -148,11 +148,7 @@ fun String.isBaseUrlValid(logErrors: Boolean, ctx: Context): Boolean { | ||||
|         existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1] | ||||
|     } | ||||
|  | ||||
|     val isValid = Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash | ||||
|     if (!isValid && logErrors) { | ||||
|         ACRA.getErrorReporter().doHandleSilentException(java.lang.Exception("Patterns.WEB_URL.matcher(this).matches() == ${Patterns.WEB_URL.matcher(this).matches()} && existsAndEndsWithSlash == $existsAndEndsWithSlash && baseUrl.pathSegments() == ${baseUrl?.pathSegments()}"), ctx) | ||||
|     } | ||||
|     return isValid | ||||
|     return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash | ||||
| } | ||||
|  | ||||
| fun Context.openInBrowserAsNewTask(i: Item) { | ||||
|   | ||||
| @@ -2,30 +2,30 @@ package apps.amine.bou.readerforselfoss.utils.glide | ||||
|  | ||||
| import android.content.Context | ||||
| import android.graphics.Bitmap | ||||
| import android.graphics.drawable.Drawable | ||||
| import android.util.Base64 | ||||
| import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory | ||||
| import android.widget.ImageView | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import com.bumptech.glide.Glide | ||||
| import com.bumptech.glide.RequestBuilder | ||||
| import com.bumptech.glide.RequestManager | ||||
| 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 | ||||
|  | ||||
| fun Context.bitmapCenterCrop(url: String, iv: ImageView) = | ||||
| fun Context.bitmapCenterCrop(config: Config, url: String, iv: ImageView) = | ||||
|     Glide.with(this) | ||||
|         .asBitmap() | ||||
|         .load(url) | ||||
|         .loadMaybeBasicAuth(config, url) | ||||
|         .apply(RequestOptions.centerCropTransform()) | ||||
|         .into(iv) | ||||
|  | ||||
| fun Context.bitmapFitCenter(url: String, iv: ImageView) = | ||||
| fun Context.circularBitmapDrawable(config: Config, url: String, iv: ImageView) = | ||||
|     Glide.with(this) | ||||
|         .asBitmap() | ||||
|         .load(url) | ||||
|         .apply(RequestOptions.fitCenterTransform()) | ||||
|         .into(iv) | ||||
|  | ||||
| fun Context.circularBitmapDrawable(url: String, iv: ImageView) = | ||||
|     Glide.with(this) | ||||
|         .asBitmap() | ||||
|         .load(url) | ||||
|         .loadMaybeBasicAuth(config, url) | ||||
|         .apply(RequestOptions.centerCropTransform()) | ||||
|         .into(object : BitmapImageViewTarget(iv) { | ||||
|             override fun setResource(resource: Bitmap?) { | ||||
| @@ -37,3 +37,23 @@ fun Context.circularBitmapDrawable(url: String, iv: ImageView) = | ||||
|                 iv.setImageDrawable(circularBitmapDrawable) | ||||
|             } | ||||
|         }) | ||||
|  | ||||
| fun RequestBuilder<Bitmap>.loadMaybeBasicAuth(config: Config, url: String): RequestBuilder<Bitmap> { | ||||
|     val builder: LazyHeaders.Builder = LazyHeaders.Builder() | ||||
|     if (config.httpUserLogin.isNotEmpty() || config.httpUserPassword.isNotEmpty()) { | ||||
|         val basicAuth = "Basic " + Base64.encodeToString("${config.httpUserLogin}:${config.httpUserPassword}".toByteArray(), Base64.NO_WRAP) | ||||
|         builder.addHeader("Authorization", basicAuth) | ||||
|     } | ||||
|     val glideUrl = GlideUrl(url, builder.build()) | ||||
|     return this.load(glideUrl) | ||||
| } | ||||
|  | ||||
| fun RequestManager.loadMaybeBasicAuth(config: Config, url: String): RequestBuilder<Drawable> { | ||||
|     val builder: LazyHeaders.Builder = LazyHeaders.Builder() | ||||
|     if (config.httpUserLogin.isNotEmpty() || config.httpUserPassword.isNotEmpty()) { | ||||
|         val basicAuth = "Basic " + Base64.encodeToString("${config.httpUserLogin}:${config.httpUserPassword}".toByteArray(), Base64.NO_WRAP) | ||||
|         builder.addHeader("Authorization", basicAuth) | ||||
|     } | ||||
|     val glideUrl = GlideUrl(url, builder.build()) | ||||
|     return this.load(glideUrl) | ||||
| } | ||||
| @@ -0,0 +1,9 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|         android:fillColor="#FFFFFFFF" | ||||
|         android:pathData="M3,21h18v-2L3,19v2zM3,17h18v-2L3,15v2zM3,13h18v-2L3,11v2zM3,9h18L21,7L3,7v2zM3,3v2h18L21,3L3,3z"/> | ||||
| </vector> | ||||
| @@ -0,0 +1,9 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|         android:fillColor="#FFFFFFFF" | ||||
|         android:pathData="M15,15L3,15v2h12v-2zM15,7L3,7v2h12L15,7zM3,13h18v-2L3,11v2zM3,21h18v-2L3,19v2zM3,3v2h18L21,3L3,3z"/> | ||||
| </vector> | ||||
| Before Width: | Height: | Size: 683 B | 
| Before Width: | Height: | Size: 680 B | 
| Before Width: | Height: | Size: 134 B | 
| Before Width: | Height: | Size: 239 B | 
| Before Width: | Height: | Size: 271 B | 
| Before Width: | Height: | Size: 216 B | 
| Before Width: | Height: | Size: 206 B | 
| Before Width: | Height: | Size: 221 B | 
| Before Width: | Height: | Size: 334 B | 
| Before Width: | Height: | Size: 458 B | 
| Before Width: | Height: | Size: 275 B | 
| Before Width: | Height: | Size: 361 B | 
| Before Width: | Height: | Size: 324 B | 
| Before Width: | Height: | Size: 301 B | 
| Before Width: | Height: | Size: 551 B | 
| Before Width: | Height: | Size: 355 B | 
| Before Width: | Height: | Size: 551 B | 
| Before Width: | Height: | Size: 204 B | 
| Before Width: | Height: | Size: 187 B | 
| Before Width: | Height: | Size: 422 B | 
| Before Width: | Height: | Size: 473 B | 
| Before Width: | Height: | Size: 498 B | 
| Before Width: | Height: | Size: 453 B | 
| Before Width: | Height: | Size: 398 B | 
| Before Width: | Height: | Size: 397 B | 
| Before Width: | Height: | Size: 523 B | 
| Before Width: | Height: | Size: 409 B | 
| Before Width: | Height: | Size: 442 B | 
| Before Width: | Height: | Size: 116 B | 
| Before Width: | Height: | Size: 174 B | 
| Before Width: | Height: | Size: 212 B | 
| Before Width: | Height: | Size: 136 B | 
| Before Width: | Height: | Size: 134 B | 
| Before Width: | Height: | Size: 175 B | 
| Before Width: | Height: | Size: 228 B | 
| Before Width: | Height: | Size: 268 B | 
| Before Width: | Height: | Size: 213 B | 
| Before Width: | Height: | Size: 247 B | 
| Before Width: | Height: | Size: 215 B | 
| Before Width: | Height: | Size: 208 B | 
| Before Width: | Height: | Size: 352 B | 
| Before Width: | Height: | Size: 241 B | 
| Before Width: | Height: | Size: 355 B | 
| Before Width: | Height: | Size: 157 B | 
| Before Width: | Height: | Size: 144 B | 
| Before Width: | Height: | Size: 276 B | 
| Before Width: | Height: | Size: 309 B | 
| Before Width: | Height: | Size: 339 B | 
| Before Width: | Height: | Size: 322 B | 
| Before Width: | Height: | Size: 262 B | 
| Before Width: | Height: | Size: 268 B | 
| Before Width: | Height: | Size: 361 B | 
| Before Width: | Height: | Size: 871 B | 
| Before Width: | Height: | Size: 634 B | 
| Before Width: | Height: | Size: 168 B | 
| Before Width: | Height: | Size: 261 B | 
| Before Width: | Height: | Size: 312 B | 
| Before Width: | Height: | Size: 171 B | 
| Before Width: | Height: | Size: 174 B | 
| Before Width: | Height: | Size: 257 B | 
| Before Width: | Height: | Size: 380 B | 
| Before Width: | Height: | Size: 504 B | 
| Before Width: | Height: | Size: 300 B | 
| Before Width: | Height: | Size: 437 B | 
| Before Width: | Height: | Size: 327 B | 
| Before Width: | Height: | Size: 308 B | 
| Before Width: | Height: | Size: 684 B | 
| Before Width: | Height: | Size: 464 B | 
| Before Width: | Height: | Size: 725 B | 
| Before Width: | Height: | Size: 230 B | 
| Before Width: | Height: | Size: 208 B | 
| Before Width: | Height: | Size: 557 B | 
| Before Width: | Height: | Size: 625 B | 
| Before Width: | Height: | Size: 606 B | 
| Before Width: | Height: | Size: 557 B | 
| Before Width: | Height: | Size: 483 B | 
| Before Width: | Height: | Size: 496 B | 
| Before Width: | Height: | Size: 660 B | 
| Before Width: | Height: | Size: 1.3 KiB | 
| Before Width: | Height: | Size: 1.2 KiB |