Compare commits

..

69 Commits

Author SHA1 Message Date
a8721ad7a4 Should intercept socket timeout errors. 2019-01-30 21:11:24 +01:00
bc5e882894 New translations strings.xml (Chinese Traditional) (#280) 2019-01-29 08:39:28 +01:00
e3460322b1 Closes #279. 2019-01-27 13:59:28 +01:00
7e3288a076 For now not sending issue report for #278. 2019-01-27 13:47:41 +01:00
ddc754ec25 Closes #270. 2019-01-15 19:54:46 +01:00
134a0766d6 Updates. 2019-01-14 20:56:12 +01:00
69da932ab5 Just to be sure. 2019-01-14 19:39:54 +01:00
592fb6328a Workaround for #275. 2019-01-14 19:37:43 +01:00
a0aead6491 Closes #271. 2019-01-13 15:59:38 +01:00
722b6cc06d Fixed issue with read/unread cont when swiping. 2019-01-13 15:59:38 +01:00
6d7c4b40f6 Wip fixing the badges reloading when reading all the items. 2019-01-13 15:59:38 +01:00
f538ed39fc New translations strings.xml (Chinese Traditional) (#274) 2019-01-11 14:20:35 +01:00
65821492ad New Crowdin translations (#273)
* New translations strings.xml (Catalan)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Dutch)

* New translations strings.xml (French)

* New translations strings.xml (Galician)

* New translations strings.xml (German)

* New translations strings.xml (Indonesian)

* New translations strings.xml (Italian)

* New translations strings.xml (Korean)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Spanish)

* New translations strings.xml (Turkish)

* New translations strings.xml (Galician)

* New translations strings.xml (Spanish)

* New translations strings.xml (French)
2019-01-10 11:19:46 +01:00
6ede718a9f Closes #269. 2019-01-09 21:28:55 +01:00
f1757937a4 Disabled resources shrinking after #262. 2019-01-09 13:33:02 +01:00
2bd2e0a953 Vector assets (#262) 2019-01-09 10:43:49 +01:00
b5aef28af0 New Crowdin translations (#268)
* New translations strings.xml (Galician)

* New translations strings.xml (Spanish)
2019-01-08 10:00:28 +01:00
45747a1506 New Crowdin translations (#267)
* New translations strings.xml (Catalan)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Dutch)

* New translations strings.xml (French)

* New translations strings.xml (Galician)

* New translations strings.xml (German)

* New translations strings.xml (Indonesian)

* New translations strings.xml (Italian)

* New translations strings.xml (Korean)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Spanish)

* New translations strings.xml (Turkish)

* New translations strings.xml (French)
2019-01-06 18:46:38 +01:00
c6e2e08bcb Merge pull request #265 from aminecmi/feature/261
WIP: Webview font
2019-01-06 18:25:26 +01:00
25bf18661e Adde a bigger line-height. 2019-01-06 18:17:10 +01:00
6b088dcd24 Font family. 2019-01-06 18:12:18 +01:00
d2b18e1880 Removed lazy loaded fonts. 2019-01-06 16:26:40 +01:00
eec7c94e98 WIP. 2019-01-06 16:16:35 +01:00
d1f8fcacc0 Changelog. 2019-01-05 21:47:45 +01:00
07e4a33cbd Closes #266. 2019-01-05 21:43:09 +01:00
f6317f566e Same change for hidden tags. 2019-01-02 21:30:26 +01:00
9f51e4e6a5 Closes #264 2019-01-02 21:21:32 +01:00
750604a31f Languages cleaning. (#260) 2018-12-12 21:49:38 +01:00
392eee0ad4 New Crowdin translations (#258)
* New translations strings.xml (Catalan)

* New translations strings.xml (Japanese)

* New translations strings.xml (Vietnamese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Afrikaans)

* New translations strings.xml (Indonesian)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (Greek)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Arabic)

* New translations strings.xml (Galician)

* New translations strings.xml (French)

* New translations strings.xml (Spanish)

* New translations strings.xml (Galician)
2018-12-10 19:26:48 +01:00
37e7b987ee Changed color of alignment icons. 2018-12-10 19:25:18 +01:00
9eac51e729 Closes #257. 2018-12-09 16:38:35 +01:00
fa9cce6783 Removed some logs. 2018-12-09 14:26:20 +01:00
f0d4b63a97 Merge pull request #256 from aminecmi/fdroid/build
Fixes #254 and #255.
2018-12-02 13:22:37 +01:00
83eeb11388 Fixes #254 and #255. 2018-12-02 13:13:19 +01:00
01f746f33d Merge pull request #255 from aminecmi/fdroid/build
A fix for #254.
2018-12-02 03:54:36 +01:00
200851894b See #254. 2018-12-01 19:32:28 +01:00
862e5cf4ab New Crowdin translations (#253)
* New translations strings.xml (Spanish)

* New translations strings.xml (Galician)

* New translations strings.xml (Spanish)
2018-11-29 06:06:32 +01:00
0b07f2a407 Closes #174. Closes #248. 2018-11-28 21:03:00 +01:00
9ba6feef0b Removed density calculation to solve #248. 2018-11-27 21:44:34 +01:00
63a0638522 Removed throw... 2018-11-27 21:36:12 +01:00
f9a4e6e363 New Crowdin translations (#252)
* New translations strings.xml (Catalan)

* New translations strings.xml (Japanese)

* New translations strings.xml (Vietnamese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Afrikaans)

* New translations strings.xml (Indonesian)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (Greek)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Arabic)

* New translations strings.xml (Galician)

* New translations strings.xml (Catalan)

* New translations strings.xml (Japanese)

* New translations strings.xml (Vietnamese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Afrikaans)

* New translations strings.xml (Indonesian)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (Greek)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Arabic)

* New translations strings.xml (Galician)

* New translations strings.xml (French)
2018-11-27 21:32:12 +01:00
6b40fd4bdc Typos. 2018-11-27 21:25:18 +01:00
04c7776466 Trying to fix an issue with webview not available with the article viewer. Fixed an issue when the browser isn't available. 2018-11-27 21:14:03 +01:00
92c335b4e1 Closes #251. 2018-11-27 20:02:53 +01:00
17251e576b New Crowdin translations (#250)
* New translations strings.xml (Catalan)

* New translations strings.xml (Catalan)

* New translations strings.xml (Catalan)
2018-11-27 15:27:24 +01:00
62ea782429 Update CONTRIBUTING.md 2018-11-26 16:22:34 +01:00
f99474e3c1 Update CONTRIBUTING.md 2018-11-26 16:21:39 +01:00
57ac8f428f Update README.md 2018-11-26 16:14:48 +01:00
9cc1adbf15 Changing acra errors handling. 2018-11-24 09:47:58 +01:00
1d9a440ae7 Do not log for test labs devices. 2018-11-22 19:50:15 +01:00
511553806c No idea why this was changed. 2018-11-21 09:57:44 +01:00
87e7d7c4fe New Crowdin translations (#247)
* New translations strings.xml (German)

* New translations strings.xml (German)

* New translations strings.xml (German)
2018-11-21 08:51:38 +01:00
ec87089310 Fixed issue with Android 9 and CLEARTEXT communication issue. 2018-11-20 21:06:05 +01:00
d8478ebb01 Added loging url validation. 2018-11-20 19:44:10 +01:00
600adc81b5 Merge pull request #246 from Binnette/ExtraSubject
Add a vertical scrollbar to article fragment
2018-11-17 20:51:10 +01:00
ddac2870af Changed git tag sort for it to work on the jenkins server. 2018-11-17 20:48:18 +01:00
8d9c8c1394 Add a vertical scrollbar to article fragment 2018-11-17 20:37:21 +01:00
b59c3bcb23 Merge pull request #245 from Binnette/ExtraSubject
Add EXTRA_SUBJECT when sharing link
2018-11-17 19:46:51 +01:00
7f554adba5 Add EXTRA_SUBJECT when sharing link 2018-11-17 18:07:38 +01:00
21ce061282 Better handling for version code automation. 2018-11-15 21:11:15 +01:00
bdb71e9b14 Note for build. 2018-11-13 22:02:44 +01:00
df22e7de15 Still not working. 2018-11-13 22:01:41 +01:00
6b3550396b Jenkins not executing the rest of the script. 2018-11-13 21:59:28 +01:00
c70f1e31a6 Added fetch to the build script. 2018-11-13 21:57:36 +01:00
695670e944 Still fixing the local publish issue. 2018-11-13 21:47:12 +01:00
1028826788 No more local publish. 2018-11-13 21:45:10 +01:00
82a8977c96 Closes #244. 2018-11-13 20:24:06 +01:00
07d9ce1054 New Crowdin translations (#243)
* New translations strings.xml (Spanish)

* New translations strings.xml (Galician)
2018-11-13 15:51:16 +01:00
7da7d49277 New Crowdin translations (#242)
* New translations strings.xml (Catalan)

* New translations strings.xml (Japanese)

* New translations strings.xml (Vietnamese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Afrikaans)

* New translations strings.xml (Indonesian)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (Greek)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Arabic)

* New translations strings.xml (Galician)
2018-11-11 17:17:37 +01:00
242 changed files with 1190 additions and 3191 deletions

View File

@ -41,6 +41,12 @@ Always check if the web version of your instance is working.
* Remember that PR review can take time.
# Install Selfoss (if you don't have an instance)
I won't provide any selfoss instance url. If you want to help, but to not have one, you'll have to install one, and use it.
All the details to need are [here](https://selfoss.aditu.de/).
# Build the project
You can directly import this project into IntellIJ/Android Studio.

View File

@ -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.

View File

@ -18,7 +18,11 @@ Also, the last APK built from source is available [here](https://jenkins.amine-b
## Want to help ?
Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob/master/.github/CONTRIBUTING.md)
1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/).
2. Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob/master/.github/CONTRIBUTING.md).
3. Build the project by following [these steps](https://github.com/aminecmi/ReaderforSelfoss/blob/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
## Useful links

View File

@ -1,17 +1,17 @@
buildscript {
}
ext {
configuration = [
buildDate: new Date()
]
// This will make me able to build multiple times a day. May break thinks. I may forget it.
todaysBuilds = "1"
}
def gitVersion() {
def process = "git describe --abbrev=0 --tags".execute()
return process.text.substring(1).replaceAll("\\.", "").trim()
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()
}
def versionCodeFromGit() {
@ -67,7 +67,7 @@ android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
@ -112,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') {
@ -178,4 +178,4 @@ def initAppLoginPropertiesIfNeeded() {
entry(key: "appLoginPassword", value: System.getProperty("appLoginPassword"))
}
}
}
}

View File

@ -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" />
@ -12,17 +11,18 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/NoBar">
<activity
android:name=".MainActivity"
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
@ -37,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"
@ -55,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>
@ -76,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>

View File

@ -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)
)
@ -109,7 +112,7 @@ class AddSourceActivity : AppCompatActivity() {
super.onResume()
val config = Config(this)
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid()) {
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid(false, this@AddSourceActivity)) {
mustLoginToAddSource()
} else {
handleSpoutsSpinner(spoutsSpinner, api, progress, formContainer)

View File

@ -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 {

View File

@ -54,7 +54,6 @@ class LoginActivity : AppCompatActivity() {
handleBaseUrlFail()
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
userIdentifier = settings.getString("unique_id", "")
logErrors = settings.getBoolean("login_debug", false)
@ -145,7 +144,7 @@ class LoginActivity : AppCompatActivity() {
var cancel = false
var focusView: View? = null
if (!url.isBaseUrlValid()) {
if (!url.isBaseUrlValid(logErrors, this@LoginActivity)) {
urlView.error = getString(R.string.login_url_problem)
focusView = urlView
cancel = true
@ -164,7 +163,7 @@ class LoginActivity : AppCompatActivity() {
}
}
if (isWithLogin || isWithHTTPLogin) {
if (isWithLogin) {
if (TextUtils.isEmpty(password)) {
passwordView.error = getString(R.string.error_invalid_password)
focusView = passwordView
@ -178,6 +177,20 @@ class LoginActivity : AppCompatActivity() {
}
}
if (isWithHTTPLogin) {
if (TextUtils.isEmpty(httpPassword)) {
httpPasswordView.error = getString(R.string.error_invalid_password)
focusView = httpPasswordView
cancel = true
}
if (TextUtils.isEmpty(httpLogin)) {
httpLoginView.error = getString(R.string.error_field_required)
focusView = httpLoginView
cancel = true
}
}
if (cancel) {
focusView?.requestFocus()
} else {

View File

@ -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
@ -28,10 +29,8 @@ import java.io.IOException
import java.util.UUID.randomUUID
@AcraHttpSender(uri = "http://amine-bou.fr:5984/acra-selfoss/_design/acra-storage/_update/report",
basicAuthLogin = "selfoss",
basicAuthPassword = "selfoss",
httpMethod = HttpSender.Method.PUT)
@AcraHttpSender(uri = "http://37.187.110.167/amine/acra/simplest-acra.php",
httpMethod = HttpSender.Method.POST)
@AcraDialog(resText = R.string.crash_dialog_text,
resCommentPrompt = R.string.crash_dialog_comment,
resTheme = android.R.style.Theme_DeviceDefault_Dialog)
@ -44,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)
@ -107,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)
}

View File

@ -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()
}

View File

@ -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)
)

View File

@ -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
@ -179,7 +181,8 @@ class ItemCardAdapter(
})
mView.shareBtn.setOnClickListener {
c.shareLink(items[adapterPosition].getLinkDecoded())
val item = items[adapterPosition]
c.shareLink(item.getLinkDecoded(), item.title)
}
mView.browserBtn.setOnClickListener {

View File

@ -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)
}
}

View File

@ -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))
}
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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())

View File

@ -3,10 +3,13 @@ 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
import android.preference.PreferenceManager
import android.view.InflateException
import androidx.browser.customtabs.CustomTabsIntent
import com.google.android.material.floatingactionbutton.FloatingActionButton
import androidx.fragment.app.Fragment
@ -17,6 +20,8 @@ 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.R
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
@ -32,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
@ -39,10 +45,10 @@ import apps.amine.bou.readerforselfoss.utils.openItemUrl
import apps.amine.bou.readerforselfoss.utils.shareLink
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
import apps.amine.bou.readerforselfoss.utils.succeeded
import apps.amine.bou.readerforselfoss.utils.toPx
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
@ -54,9 +60,9 @@ import kotlin.concurrent.thread
class ArticleFragment : Fragment() {
private lateinit var pageNumber: Number
private var fontSize: Int = 14
private var fontSize: Int = 16
private lateinit var allItems: ArrayList<Item>
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
private var mCustomTabActivityHelper: CustomTabActivityHelper? = null;
private lateinit var url: String
private lateinit var contentText: String
private lateinit var contentSource: String
@ -66,14 +72,27 @@ 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()
mCustomTabActivityHelper.unbindCustomTabsService(activity)
if (mCustomTabActivityHelper != null) {
mCustomTabActivityHelper!!.unbindCustomTabsService(activity)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(activity!!)
config = Config(activity!!)
super.onCreate(savedInstanceState)
@ -86,154 +105,196 @@ class ArticleFragment : Fragment() {
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build()
}
private lateinit var rootView: ViewGroup
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
rootView = inflater
.inflate(R.layout.fragment_article, container, false) as ViewGroup
try {
rootView = inflater
.inflate(R.layout.fragment_article, container, false) as ViewGroup
url = allItems[pageNumber.toInt()].getLinkDecoded()
contentText = allItems[pageNumber.toInt()].content
contentTitle = allItems[pageNumber.toInt()].title
contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!)
contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
url = allItems[pageNumber.toInt()].getLinkDecoded()
contentText = allItems[pageNumber.toInt()].content
contentTitle = allItems[pageNumber.toInt()].title
contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!)
contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
editor = prefs.edit()
fontSize = prefs.getString("reader_font_size", "14").toInt()
prefs = PreferenceManager.getDefaultSharedPreferences(activity)
editor = prefs.edit()
fontSize = prefs.getString("reader_font_size", "16").toInt()
val settings = activity!!.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
val debugReadingItems = prefs.getBoolean("read_debug", false)
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
}
}
val api = SelfossApi(
context!!,
activity!!,
settings.getBoolean("isSelfSignedCert", false),
prefs.getString("api_timeout", "-1").toLong(),
prefs.getBoolean("should_log_everything", false)
)
refreshAlignment()
fab = rootView.fab
val settings = activity!!.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
val debugReadingItems = prefs.getBoolean("read_debug", false)
fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
val api = SelfossApi(
context!!,
activity!!,
settings.getBoolean("isSelfSignedCert", false),
prefs.getString("api_timeout", "-1").toLong(),
prefs.getBoolean("should_log_everything", false)
)
fab.rippleColor = appColors.colorAccentDark
fab = rootView!!.fab
val floatingToolbar: FloatingToolbar = rootView.floatingToolbar
floatingToolbar.attachFab(fab)
fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
floatingToolbar.background = ColorDrawable(appColors.colorAccent)
fab.rippleColor = appColors.colorAccentDark
val customTabsIntent = activity!!.buildCustomTabsIntent()
mCustomTabActivityHelper = CustomTabActivityHelper()
mCustomTabActivityHelper.bindCustomTabsService(activity)
val floatingToolbar: FloatingToolbar = rootView!!.floatingToolbar
floatingToolbar.attachFab(fab)
floatingToolbar.background = ColorDrawable(appColors.colorAccent)
val customTabsIntent = activity!!.buildCustomTabsIntent()
mCustomTabActivityHelper = CustomTabActivityHelper()
mCustomTabActivityHelper!!.bindCustomTabsService(activity)
floatingToolbar.setClickListener(
object : FloatingToolbar.ItemClickListener {
override fun onItemClick(item: MenuItem) {
when (item.itemId) {
R.id.more_action -> getContentFromMercury(customTabsIntent, prefs)
R.id.share_action -> activity!!.shareLink(url)
R.id.open_action -> activity!!.openItemUrl(
allItems,
pageNumber.toInt(),
url,
customTabsIntent,
false,
false,
activity!!
)
R.id.unread_action -> if ((context != null && context!!.isNetworkAccessible(null)) || context == null) {
api.unmarkItem(allItems[pageNumber.toInt()].id).enqueue(
object : Callback<SuccessResponse> {
override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
if (!response.succeeded() && debugReadingItems) {
val message =
"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), activity!!)
}
}
override fun onFailure(
call: Call<SuccessResponse>,
t: Throwable
) {
if (debugReadingItems) {
ACRA.getErrorReporter().maybeHandleSilentException(t, activity!!)
}
}
}
floatingToolbar.setClickListener(
object : FloatingToolbar.ItemClickListener {
override fun onItemClick(item: MenuItem) {
when (item.itemId) {
R.id.more_action -> getContentFromMercury(customTabsIntent, prefs)
R.id.share_action -> activity!!.shareLink(url, contentTitle)
R.id.open_action -> activity!!.openItemUrl(
allItems,
pageNumber.toInt(),
url,
customTabsIntent,
false,
false,
activity!!
)
} else {
thread {
db.actionsDao().insertAllActions(ActionEntity(allItems[pageNumber.toInt()].id, false, true, false, false))
R.id.unread_action -> if ((context != null && context!!.isNetworkAccessible(null)) || context == null) {
api.unmarkItem(allItems[pageNumber.toInt()].id).enqueue(
object : Callback<SuccessResponse> {
override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
if (!response.succeeded() && debugReadingItems) {
val message =
"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), activity!!)
}
}
override fun onFailure(
call: Call<SuccessResponse>,
t: Throwable
) {
if (debugReadingItems) {
ACRA.getErrorReporter().maybeHandleSilentException(t, activity!!)
}
}
}
)
} else {
thread {
db.actionsDao().insertAllActions(ActionEntity(allItems[pageNumber.toInt()].id, false, true, false, false))
}
}
else -> Unit
}
else -> Unit
}
override fun onItemLongClick(item: MenuItem?) {
}
}
)
override fun onItemLongClick(item: MenuItem?) {
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()
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
rootView!!.imageView.visibility = View.VISIBLE
Glide
.with(context!!)
.asBitmap()
.loadMaybeBasicAuth(config, contentImage)
.apply(RequestOptions.fitCenterTransform())
.into(rootView!!.imageView)
} else {
rootView!!.imageView.visibility = View.GONE
}
}
)
rootView.source.text = contentSource
rootView!!.nestedScrollView.setOnScrollChangeListener(
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) {
fab.hide()
} else {
if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
}
}
)
if (contentText.isEmptyOrNullOrNullString()) {
getContentFromMercury(customTabsIntent, prefs)
} else {
rootView.titleView.text = contentTitle
htmlToWebview(contentText, prefs)
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
rootView.imageView.visibility = View.VISIBLE
Glide
.with(context!!)
.asBitmap()
.load(contentImage)
.apply(RequestOptions.fitCenterTransform())
.into(rootView.imageView)
} else {
rootView.imageView.visibility = View.GONE
}
} catch (e: InflateException) {
AlertDialog.Builder(context!!)
.setMessage(context!!.getString(R.string.webview_dialog_issue_message))
.setTitle(context!!.getString(R.string.webview_dialog_issue_title))
.setPositiveButton(android.R.string.ok
) { dialog, which ->
val sharedPref = PreferenceManager.getDefaultSharedPreferences(context!!)
val editor = sharedPref.edit()
editor.putBoolean("prefer_article_viewer", false)
editor.commit()
activity!!.finish()
}
.create()
.show()
}
rootView.nestedScrollView.setOnScrollChangeListener(
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) {
fab.hide()
} else {
if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
}
}
)
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
) {
if ((context != null && context!!.isNetworkAccessible(null)) || context == null) {
rootView.progressBar.visibility = View.VISIBLE
rootView!!.progressBar.visibility = View.VISIBLE
val parser = MercuryApi(
prefs.getBoolean("should_log_everything", false)
)
@ -248,13 +309,16 @@ class ArticleFragment : Fragment() {
try {
if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) {
try {
rootView.titleView.text = response.body()!!.title
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) {
@ -263,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!!)
@ -272,19 +337,19 @@ class ArticleFragment : Fragment() {
try {
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) {
rootView.imageView.visibility = View.VISIBLE
rootView!!.imageView.visibility = View.VISIBLE
try {
Glide
.with(context!!)
.asBitmap()
.load(response.body()!!.lead_image_url)
.loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty())
.apply(RequestOptions.fitCenterTransform())
.into(rootView.imageView)
.into(rootView!!.imageView)
} catch (e: Exception) {
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
}
} else {
rootView.imageView.visibility = View.GONE
rootView!!.imageView.visibility = View.GONE
}
} catch (e: Exception) {
if (context != null) {
@ -293,9 +358,9 @@ class ArticleFragment : Fragment() {
}
try {
rootView.nestedScrollView.scrollTo(0, 0)
rootView!!.nestedScrollView.scrollTo(0, 0)
rootView.progressBar.visibility = View.GONE
rootView!!.progressBar.visibility = View.GONE
} catch (e: Exception) {
if (context != null) {
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
@ -326,13 +391,18 @@ class ArticleFragment : Fragment() {
}
}
private fun htmlToWebview(c: String, prefs: SharedPreferences) {
private fun htmlToWebview() {
val stringColor = String.format("#%06X", 0xFFFFFF and appColors.colorAccent)
rootView.webcontent.visibility = View.VISIBLE
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) {
rootView.webcontent.setBackgroundColor(
rootView!!.webcontent.setBackgroundColor(
ContextCompat.getColor(
context!!,
R.color.dark_webview
@ -344,7 +414,7 @@ class ArticleFragment : Fragment() {
}
} else {
if (context != null) {
rootView.webcontent.setBackgroundColor(
rootView!!.webcontent.setBackgroundColor(
ContextCompat.getColor(
context!!,
R.color.light_webview
@ -368,15 +438,15 @@ class ArticleFragment : Fragment() {
"#FFFFFF"
}
rootView.webcontent.settings.useWideViewPort = true
rootView.webcontent.settings.loadWithOverviewMode = true
rootView.webcontent.settings.javaScriptEnabled = false
rootView!!.webcontent.settings.useWideViewPort = true
rootView!!.webcontent.settings.loadWithOverviewMode = true
rootView!!.webcontent.settings.javaScriptEnabled = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
rootView.webcontent.settings.layoutAlgorithm =
rootView!!.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
} else {
rootView.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN
rootView!!.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN
}
var baseUrl: String? = null
@ -388,10 +458,29 @@ class ArticleFragment : Fragment() {
ACRA.getErrorReporter().maybeHandleSilentException(e, activity!!)
}
rootView.webcontent.loadDataWithBaseURL(
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;
@ -406,13 +495,14 @@ class ArticleFragment : Fragment() {
| color: $stringTextColor;
| }
| * {
| font-size: ${fontSize.toPx}px;
| text-align: justify;
| font-size: ${fontSize}px;
| 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;
@ -420,9 +510,10 @@ class ArticleFragment : Fragment() {
| background-color: $stringBackgroundColor;
| }
| </style>
| $fontLinkAndStyle
|</head>
|<body>
| $c
| $contentText
|</body>""".trimMargin(),
"text/html",
"utf-8",
@ -431,7 +522,7 @@ class ArticleFragment : Fragment() {
}
private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) {
rootView.progressBar.visibility = View.GONE
rootView!!.progressBar.visibility = View.GONE
activity!!.openItemUrl(
allItems,
pageNumber.toInt(),

View File

@ -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);
}
}
/**

View File

@ -2,11 +2,21 @@ package apps.amine.bou.readerforselfoss.utils
import android.content.Context
import android.preference.PreferenceManager
import android.provider.Settings
import org.acra.ErrorReporter
fun ErrorReporter.maybeHandleSilentException(throwable: Throwable, ctx: Context) {
val sharedPref = PreferenceManager.getDefaultSharedPreferences(ctx)
if (sharedPref.getBoolean("acra_should_log", false)) {
val isTestLab = Settings.System.getString(ctx.contentResolver, "firebase.test.lab") == "true"
if (sharedPref.getBoolean("acra_should_log", false) && !isTestLab) {
this.handleSilentException(throwable)
}
}
fun ErrorReporter.doHandleSilentException(throwable: Throwable, ctx: Context) {
val isTestLab = Settings.System.getString(ctx.contentResolver, "firebase.test.lab") == "true"
if (!isTestLab) {
this.handleSilentException(throwable)
}
}

View File

@ -25,11 +25,12 @@ fun String.toStringUriWithHttp(): String =
this
}
fun Context.shareLink(itemUrl: String) {
fun Context.shareLink(itemUrl: String, itemTitle: String) {
val sendIntent = Intent()
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
sendIntent.action = Intent.ACTION_SEND
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
sendIntent.type = "text/plain"
startActivity(
Intent.createChooser(

View File

@ -2,6 +2,7 @@ package apps.amine.bou.readerforselfoss.utils
import android.app.Activity
import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
@ -19,6 +20,7 @@ import apps.amine.bou.readerforselfoss.ReaderActivity
import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import okhttp3.HttpUrl
import org.acra.ACRA
fun Context.buildCustomTabsIntent(): CustomTabsIntent {
@ -128,13 +130,17 @@ fun Context.openItemUrl(
private fun openInBrowser(linkDecoded: String, app: Activity) {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(linkDecoded)
app.startActivity(intent)
try {
app.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(app.baseContext, e.message, Toast.LENGTH_LONG).show()
}
}
fun String.isUrlValid(): Boolean =
HttpUrl.parse(this) != null && Patterns.WEB_URL.matcher(this).matches()
fun String.isBaseUrlValid(): Boolean {
fun String.isBaseUrlValid(logErrors: Boolean, ctx: Context): Boolean {
val baseUrl = HttpUrl.parse(this)
var existsAndEndsWithSlash = false
if (baseUrl != null) {

View File

@ -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?) {
@ -36,4 +36,24 @@ fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
circularBitmapDrawable.isCircular = true
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)
}

View File

@ -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>

View File

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

Some files were not shown because too many files have changed in this diff Show More