Compare commits

..

79 Commits

Author SHA1 Message Date
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
9b45365441 Changelog. Previous commit should close #238. 2018-11-11 12:25:45 +01:00
91a7464bce Added experimental settings with a custom timeout setting. 2018-11-11 12:23:59 +01:00
51add226eb New Crowdin translations (#241)
* New translations strings.xml (Spanish)

* New translations strings.xml (Galician)
2018-11-08 08:59:37 +01:00
332e9f5108 New Crowdin translations (#240)
* 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-07 21:25:14 +01:00
0b91087c07 CHANGELOG. 2018-11-07 21:22:19 +01:00
ebbb1ba0f8 Closes #220. 2018-11-07 21:22:06 +01:00
e9143ae852 Initial changes for #238. 2018-11-07 21:07:29 +01:00
42e8ecee78 Offline shortcut. 2018-11-07 21:05:23 +01:00
4efd76fcbc Tab selection from app shortcut. 2018-11-07 20:45:51 +01:00
fb1614070e Inital app shortcuts. 2018-11-07 20:25:48 +01:00
c473dd7227 Fixes #239. 2018-11-07 19:32:10 +01:00
76bddb195d New Crowdin translations (#237)
* 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-06 20:35:30 +01:00
1e02ad2041 Closes #201. 2018-11-06 20:29:00 +01:00
f6ab909f8b Fixed issue. 2018-11-06 20:11:07 +01:00
7e520e9bed Still fixing selfoss version issues. 2018-11-05 21:11:25 +01:00
32e2d05014 CHANGELOG. 2018-11-05 20:30:58 +01:00
40d9c97f73 Fixes #216. 2018-11-05 20:30:37 +01:00
1aa68d3449 New Crowdin translations (#234)
* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Galician)

* New translations strings.xml (Spanish)

* New translations strings.xml (Galician)
2018-11-05 13:44:22 +01:00
243 changed files with 1508 additions and 3147 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. * 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 # Build the project
You can directly import this project into IntellIJ/Android Studio. You can directly import this project into IntellIJ/Android Studio.

View File

@ -1,5 +1,21 @@
**1.7.x** **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.
- Start of #238. "Add a quick shortcut to open the app on offline mode ?"
- Closes #216. Issue with selfoss version 2.19.
- Closes #179. Sync of read/unread/star/unstar items on background task or on app reload with network available. - Closes #179. Sync of read/unread/star/unstar items on background task or on app reload with network available.
- Closes #33. Background sync with settings. - Closes #33. Background sync with settings.

View File

@ -18,7 +18,11 @@ Also, the last APK built from source is available [here](https://jenkins.amine-b
## Want to help ? ## 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 ## Useful links

View File

@ -1,17 +1,17 @@
buildscript { 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 gitVersion() {
def process = "git describe --abbrev=0 --tags".execute() def process
return process.text.substring(1).replaceAll("\\.", "").trim() 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() { def versionCodeFromGit() {
@ -67,7 +67,7 @@ android {
buildTypes { buildTypes {
release { release {
minifyEnabled true minifyEnabled true
shrinkResources true shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro' 'proguard-rules.pro'
} }
@ -75,7 +75,6 @@ android {
buildConfigField "String", "LOGIN_URL", appLoginUrl buildConfigField "String", "LOGIN_URL", appLoginUrl
buildConfigField "String", "LOGIN_USERNAME", appLoginUsername buildConfigField "String", "LOGIN_USERNAME", appLoginUsername
buildConfigField "String", "LOGIN_PASSWORD", appLoginPassword buildConfigField "String", "LOGIN_PASSWORD", appLoginPassword
applicationIdSuffix ".dev"
} }
} }
flavorDimensions "build" flavorDimensions "build"
@ -113,7 +112,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
//multidex //multidex
implementation 'androidx.multidex:multidex:2.0.0' implementation 'androidx.multidex:multidex:2.0.1'
// About // About
implementation('com.mikepenz:aboutlibraries:6.2.0@aar') { implementation('com.mikepenz:aboutlibraries:6.2.0@aar') {
@ -179,4 +178,4 @@ def initAppLoginPropertiesIfNeeded() {
entry(key: "appLoginPassword", value: System.getProperty("appLoginPassword")) entry(key: "appLoginPassword", value: System.getProperty("appLoginPassword"))
} }
} }
} }

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="apps.amine.bou.readerforselfoss" package="apps.amine.bou.readerforselfoss">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@ -12,15 +11,19 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/NoBar"> android:theme="@style/NoBar">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:theme="@style/SplashTheme"> android:theme="@style/SplashTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity> </activity>
<activity <activity
android:name=".LoginActivity" android:name=".LoginActivity"
@ -34,7 +37,7 @@
android:parentActivityName=".HomeActivity"> android:parentActivityName=".HomeActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="apps.amine.bou.readerforselfoss.HomeActivity" /> android:value=".HomeActivity" />
</activity> </activity>
<activity <activity
android:name=".SourcesActivity" android:name=".SourcesActivity"
@ -52,9 +55,7 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" /> <data android:mimeType="text/plain" />
</intent-filter> </intent-filter>
</activity> </activity>
@ -73,6 +74,9 @@
android:value="true" /> android:value="true" />
<meta-data android:name="android.max_aspect" android:value="2.1" /> <meta-data android:name="android.max_aspect" android:value="2.1" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application> </application>
</manifest> </manifest>

View File

@ -89,6 +89,7 @@ class AddSourceActivity : AppCompatActivity() {
this, this,
this@AddSourceActivity, this@AddSourceActivity,
prefs.getBoolean("isSelfSignedCert", false), prefs.getBoolean("isSelfSignedCert", false),
prefs.getString("api_timeout", "-1").toLong(),
prefs.getBoolean("should_log_everything", false) prefs.getBoolean("should_log_everything", false)
) )
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
@ -108,7 +109,7 @@ class AddSourceActivity : AppCompatActivity() {
super.onResume() super.onResume()
val config = Config(this) val config = Config(this)
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid()) { if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid(false, this@AddSourceActivity)) {
mustLoginToAddSource() mustLoginToAddSource()
} else { } else {
handleSpoutsSpinner(spoutsSpinner, api, progress, formContainer) handleSpoutsSpinner(spoutsSpinner, api, progress, formContainer)

View File

@ -18,17 +18,13 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.StaggeredGridLayoutManager import androidx.recyclerview.widget.StaggeredGridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import apps.amine.bou.readerforselfoss.adapters.ItemCardAdapter import apps.amine.bou.readerforselfoss.adapters.ItemCardAdapter
@ -69,7 +65,6 @@ import com.ashokvarma.bottomnavigation.BottomNavigationItem
import com.ashokvarma.bottomnavigation.TextBadgeItem import com.ashokvarma.bottomnavigation.TextBadgeItem
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import com.github.stkent.amplify.tracking.Amplify import com.github.stkent.amplify.tracking.Amplify
import com.google.gson.reflect.TypeToken
import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.aboutlibraries.LibsBuilder
import com.mikepenz.materialdrawer.Drawer 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.PrimaryDrawerItem
import com.mikepenz.materialdrawer.model.SecondaryDrawerItem import com.mikepenz.materialdrawer.model.SecondaryDrawerItem
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.android.synthetic.main.fragment_article.*
import org.acra.ACRA import org.acra.ACRA
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
@ -145,10 +139,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private var badgeAll: Int = -1 private var badgeAll: Int = -1
private var badgeFavs: Int = -1 private var badgeFavs: Int = -1
private var fromTabShortcut: Boolean = false
private var offlineShortcut: Boolean = false
private lateinit var tagsBadge: Map<Long, Int> private lateinit var tagsBadge: Map<Long, Int>
private lateinit var db: AppDatabase private lateinit var db: AppDatabase
private lateinit var config: Config
data class DrawerData(val tags: List<Tag>?, val sources: List<Source>?) data class DrawerData(val tags: List<Tag>?, val sources: List<Source>?)
override fun onStart() { override fun onStart() {
@ -158,9 +157,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@HomeActivity) appColors = AppColors(this@HomeActivity)
config = Config(this@HomeActivity)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
offlineShortcut = intent.getBooleanExtra("startOffline", false)
if (fromTabShortcut) {
elementsShown = intent.getIntExtra("shortcutTab", UNREAD_SHOWN)
}
setContentView(R.layout.activity_home) setContentView(R.layout.activity_home)
handleThemeBinding() handleThemeBinding()
@ -185,6 +192,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
this, this,
this@HomeActivity, this@HomeActivity,
settings.getBoolean("isSelfSignedCert", false), settings.getBoolean("isSelfSignedCert", false),
sharedPref.getString("api_timeout", "-1").toLong(),
shouldLogEverything shouldLogEverything
) )
items = ArrayList() items = ArrayList()
@ -203,6 +211,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
R.color.refresh_progress_3 R.color.refresh_progress_3
) )
swipeRefreshLayout.setOnRefreshListener { swipeRefreshLayout.setOnRefreshListener {
offlineShortcut = false
allItems = ArrayList() allItems = ArrayList()
lastFetchDone = false lastFetchDone = false
handleDrawerItems() handleDrawerItems()
@ -218,7 +227,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
recyclerView: RecyclerView, recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder viewHolder: RecyclerView.ViewHolder
): Int = ): Int =
if (elementsShown != UNREAD_SHOWN) { if (elementsShown != UNREAD_SHOWN && elementsShown != READ_SHOWN) {
0 0
} else { } else {
super.getSwipeDirs( super.getSwipeDirs(
@ -238,17 +247,21 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
val i = items.elementAtOrNull(position) val i = items.elementAtOrNull(position)
if (i != null) { if (i != null) {
val adapter = recyclerView.adapter val adapter = recyclerView.adapter as ItemsAdapter<*>
when (adapter) { val wasItemUnread = adapter.unreadItemStatusAtIndex(position)
is ItemCardAdapter -> adapter.removeItemAtIndex(position)
is ItemListAdapter -> adapter.removeItemAtIndex(position) adapter.handleItemAtIndex(position)
if (wasItemUnread) {
badgeNew--
} else {
badgeNew++
} }
badgeNew--
reloadBadgeContent() reloadBadgeContent()
val tagHashes = i.tags.split(",").map { it.longHash() } val tagHashes = i.tags.tags.split(",").map { it.longHash() }
tagsBadge = tagsBadge.map { tagsBadge = tagsBadge.map {
if (tagHashes.contains(it.key)) { if (tagHashes.contains(it.key)) {
(it.key to (it.value - 1)) (it.key to (it.value - 1))
@ -292,19 +305,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
val tabNew = val tabNew =
BottomNavigationItem( BottomNavigationItem(
R.drawable.ic_fiber_new_black_24dp, R.drawable.ic_tab_fiber_new_black_24dp,
getString(R.string.tab_new) getString(R.string.tab_new)
).setActiveColor(appColors.colorAccent) ).setActiveColor(appColors.colorAccent)
.setBadgeItem(tabNewBadge) .setBadgeItem(tabNewBadge)
val tabArchive = val tabArchive =
BottomNavigationItem( BottomNavigationItem(
R.drawable.ic_archive_black_24dp, R.drawable.ic_tab_archive_black_24dp,
getString(R.string.tab_read) getString(R.string.tab_read)
).setActiveColor(appColors.colorAccentDark) ).setActiveColor(appColors.colorAccentDark)
.setBadgeItem(tabArchiveBadge) .setBadgeItem(tabArchiveBadge)
val tabStarred = val tabStarred =
BottomNavigationItem( BottomNavigationItem(
R.drawable.ic_favorite_black_24dp, R.drawable.ic_tab_favorite_black_24dp,
getString(R.string.tab_favs) getString(R.string.tab_favs)
).setActiveColorResource(R.color.pink) ).setActiveColorResource(R.color.pink)
.setBadgeItem(tabStarredBadge) .setBadgeItem(tabStarredBadge)
@ -318,6 +331,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
bottomBar.setMode(BottomNavigationBar.MODE_SHIFTING) bottomBar.setMode(BottomNavigationBar.MODE_SHIFTING)
bottomBar.setBackgroundStyle(BottomNavigationBar.BACKGROUND_STYLE_STATIC) bottomBar.setBackgroundStyle(BottomNavigationBar.BACKGROUND_STYLE_STATIC)
if (fromTabShortcut) {
bottomBar.selectTab(elementsShown - 1)
}
} }
override fun onResume() { override fun onResume() {
@ -367,7 +384,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
thread { thread {
if (response.body() != null) { if (response.body() != null) {
val apiItems = (response.body() as ArrayList<Item>).filter { val apiItems = (response.body() as ArrayList<Item>).filter {
maybeTagFilter != null || filter(it.tags) maybeTagFilter != null || filter(it.tags.tags)
} as ArrayList<Item> } as ArrayList<Item>
db.itemsDao().deleteAllItems() db.itemsDao().deleteAllItems()
db.itemsDao() db.itemsDao()
@ -462,7 +479,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
footer { footer {
primaryItem(R.string.drawer_report_bug) { primaryItem(R.string.drawer_report_bug) {
icon = R.drawable.ic_bug_report icon = R.drawable.ic_bug_report_black_24dp
iconTintingEnabled = true iconTintingEnabled = true
onClick { _ -> onClick { _ ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(Config.trackerUrl)) val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(Config.trackerUrl))
@ -472,7 +489,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
primaryItem(R.string.title_activity_settings) { primaryItem(R.string.title_activity_settings) {
icon = R.drawable.ic_settings icon = R.drawable.ic_settings_black_24dp
iconTintingEnabled = true iconTintingEnabled = true
onClick { _ -> onClick { _ ->
startActivityForResult( startActivityForResult(
@ -515,12 +532,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
gd.shape = GradientDrawable.RECTANGLE gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30) gd.setSize(30, 30)
gd.cornerRadius = 30F gd.cornerRadius = 30F
drawer.addItem( var drawerItem =
PrimaryDrawerItem() PrimaryDrawerItem()
.withName(it.tag) .withName(it.tag)
.withIdentifier(it.tag.longHash()) .withIdentifier(it.tag.longHash())
.withIcon(gd) .withIcon(gd)
.withBadge("${it.unread}")
.withBadgeStyle( .withBadgeStyle(
BadgeStyle().withTextColor(Color.WHITE) BadgeStyle().withTextColor(Color.WHITE)
.withColor(appColors.colorAccent) .withColor(appColors.colorAccent)
@ -531,6 +547,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
getElementsAccordingToTab() getElementsAccordingToTab()
false false
} }
if (it.unread > 0) {
drawerItem = drawerItem.withBadge("${it.unread}")
}
drawer.addItem(
drawerItem
) )
(it.tag.longHash() to it.unread) (it.tag.longHash() to it.unread)
@ -562,12 +583,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
gd.shape = GradientDrawable.RECTANGLE gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30) gd.setSize(30, 30)
gd.cornerRadius = 30F gd.cornerRadius = 30F
drawer.addItem( var drawerItem =
PrimaryDrawerItem() PrimaryDrawerItem()
.withName(it.tag) .withName(it.tag)
.withIdentifier(it.tag.longHash()) .withIdentifier(it.tag.longHash())
.withIcon(gd) .withIcon(gd)
.withBadge("${it.unread}")
.withBadgeStyle( .withBadgeStyle(
BadgeStyle().withTextColor(Color.WHITE) BadgeStyle().withTextColor(Color.WHITE)
.withColor(appColors.colorAccent) .withColor(appColors.colorAccent)
@ -578,6 +598,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
getElementsAccordingToTab() getElementsAccordingToTab()
false false
} }
if (it.unread > 0) {
drawerItem = drawerItem.withBadge("${it.unread}")
}
drawer.addItem(
drawerItem
) )
(it.tag.longHash() to it.unread) (it.tag.longHash() to it.unread)
@ -628,14 +653,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
false false
} }
) )
drawer.addItem(DividerDrawerItem())
drawer.addItem(
SecondaryDrawerItem()
.withName(getString(R.string.drawer_item_tags))
.withIdentifier(DRAWER_ID_TAGS)
.withSelectable(false)
)
handleTags(maybeDrawerData.tags)
if (hiddenTags.isNotEmpty()) { if (hiddenTags.isNotEmpty()) {
drawer.addItem(DividerDrawerItem()) drawer.addItem(DividerDrawerItem())
drawer.addItem( drawer.addItem(
@ -647,6 +664,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
handleHiddenTags(maybeDrawerData.tags) handleHiddenTags(maybeDrawerData.tags)
} }
drawer.addItem(DividerDrawerItem()) drawer.addItem(DividerDrawerItem())
drawer.addItem(
SecondaryDrawerItem()
.withName(getString(R.string.drawer_item_tags))
.withIdentifier(DRAWER_ID_TAGS)
.withSelectable(false)
)
handleTags(maybeDrawerData.tags)
drawer.addItem(DividerDrawerItem())
drawer.addItem( drawer.addItem(
SecondaryDrawerItem() SecondaryDrawerItem()
.withName(getString(R.string.drawer_item_sources)) .withName(getString(R.string.drawer_item_sources))
@ -664,7 +689,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
PrimaryDrawerItem() PrimaryDrawerItem()
.withName(R.string.action_about) .withName(R.string.action_about)
.withSelectable(false) .withSelectable(false)
.withIcon(R.drawable.ic_info_outline) .withIcon(R.drawable.ic_info_outline_white_24dp)
.withIconTintingEnabled(true) .withIconTintingEnabled(true)
.withOnDrawerItemClickListener { _, _, _ -> .withOnDrawerItemClickListener { _, _, _ ->
LibsBuilder() LibsBuilder()
@ -721,7 +746,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
var sources: List<Source>? var sources: List<Source>?
fun sourcesApiCall() { fun sourcesApiCall() {
if (this@HomeActivity.isNetworkAccessible(null)) { if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) {
api.sources.enqueue(object : Callback<List<Source>> { api.sources.enqueue(object : Callback<List<Source>> {
override fun onResponse( override fun onResponse(
call: Call<List<Source>>?, call: Call<List<Source>>?,
@ -735,12 +760,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
override fun onFailure(call: Call<List<Source>>?, t: Throwable?) { override fun onFailure(call: Call<List<Source>>?, t: Throwable?) {
val apiDrawerData = DrawerData(tags, null)
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
handleDrawerData(apiDrawerData)
}
} }
}) })
} }
} }
if (this@HomeActivity.isNetworkAccessible(null)) { if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) {
api.tags.enqueue(object : Callback<List<Tag>> { api.tags.enqueue(object : Callback<List<Tag>> {
override fun onResponse( override fun onResponse(
call: Call<List<Tag>>, call: Call<List<Tag>>,
@ -871,7 +900,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
else -> Unit else -> Unit
} }
} else { } else {
if (this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout))) { if (this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout), offlineShortcut)) {
when (position) { when (position) {
0 -> getUnRead() 0 -> getUnRead()
1 -> getRead() 1 -> getRead()
@ -968,7 +997,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
handleListResult() handleListResult()
doGetAccordingToTab() doGetAccordingToTab()
} else { } else {
if (this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout))) { if (this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout), offlineShortcut)) {
doGetAccordingToTab() doGetAccordingToTab()
getAndStoreAllItems() getAndStoreAllItems()
} }
@ -999,7 +1028,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
getAndStoreAllItems() getAndStoreAllItems()
items = response.body() as ArrayList<Item> items = response.body() as ArrayList<Item>
items = items.filter { items = items.filter {
maybeTagFilter != null || filter(it.tags) maybeTagFilter != null || filter(it.tags.tags)
} as ArrayList<Item> } as ArrayList<Item>
if (allItems.isEmpty()) { if (allItems.isEmpty()) {
@ -1027,7 +1056,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = true } swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = true }
} }
if (this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout))) { if (this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout), offlineShortcut)) {
call(maybeTagFilter?.tag, maybeSourceFilter?.id?.toLong(), maybeSearchFilter) call(maybeTagFilter?.tag, maybeSourceFilter?.id?.toLong(), maybeSearchFilter)
.enqueue(object : Callback<List<Item>> { .enqueue(object : Callback<List<Item>> {
override fun onResponse( override fun onResponse(
@ -1116,7 +1145,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
fullHeightCards, fullHeightCards,
appColors, appColors,
debugReadingItems, debugReadingItems,
userIdentifier userIdentifier,
config
) { ) {
updateItems(it) updateItems(it)
} }
@ -1132,7 +1162,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
articleViewer, articleViewer,
debugReadingItems, debugReadingItems,
userIdentifier, userIdentifier,
appColors appColors,
config
) { ) {
updateItems(it) updateItems(it)
} }
@ -1157,7 +1188,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
private fun reloadBadges() { private fun reloadBadges() {
if (this@HomeActivity.isNetworkAccessible(null) && (displayUnreadCount || displayAllCount)) { if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut) && (displayUnreadCount || displayAllCount)) {
api.stats.enqueue(object : Callback<Stats> { api.stats.enqueue(object : Callback<Stats> {
override fun onResponse(call: Call<Stats>, response: Response<Stats>) { override fun onResponse(call: Call<Stats>, response: Response<Stats>) {
if (response.body() != null) { if (response.body() != null) {
@ -1263,7 +1294,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.refresh -> { R.id.refresh -> {
if (this@HomeActivity.isNetworkAccessible(null)) { if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) {
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) { needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
api.update().enqueue(object : Callback<String> { api.update().enqueue(object : Callback<String> {
override fun onResponse( override fun onResponse(
@ -1299,7 +1330,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
val ids = allItems.map { it.id } val ids = allItems.map { it.id }
val itemsByTag: Map<Long, Int> = val itemsByTag: Map<Long, Int> =
allItems.flattenTags() allItems.flattenTags()
.groupBy { it.tags.longHash() } .groupBy { it.tags.tags.longHash() }
.map { it.key to it.value.size } .map { it.key to it.value.size }
.toMap() .toMap()
@ -1307,7 +1338,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
ACRA.getErrorReporter().maybeHandleSilentException(e, this@HomeActivity) ACRA.getErrorReporter().maybeHandleSilentException(e, this@HomeActivity)
} }
if (ids.isNotEmpty() && this@HomeActivity.isNetworkAccessible(null)) { if (ids.isNotEmpty() && this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) {
api.readAll(ids).enqueue(object : Callback<SuccessResponse> { api.readAll(ids).enqueue(object : Callback<SuccessResponse> {
override fun onResponse( override fun onResponse(
call: Call<SuccessResponse>, call: Call<SuccessResponse>,
@ -1321,12 +1352,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
).show() ).show()
tabNewBadge.removeBadge() tabNewBadge.removeBadge()
handleDrawerItems()
tagsBadge = itemsByTag.map {
(it.key to ((tagsBadge[it.key] ?: it.value) - it.value))
}.toMap()
reloadTagsBadges()
getElementsAccordingToTab() getElementsAccordingToTab()
} else { } else {
@ -1451,7 +1477,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
}) })
} }
if (this@HomeActivity.isNetworkAccessible(null)) { if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) {
thread { thread {
val actions = db.actionsDao().actions() val actions = db.actionsDao().actions()

View File

@ -54,7 +54,6 @@ class LoginActivity : AppCompatActivity() {
handleBaseUrlFail() handleBaseUrlFail()
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
userIdentifier = settings.getString("unique_id", "") userIdentifier = settings.getString("unique_id", "")
logErrors = settings.getBoolean("login_debug", false) logErrors = settings.getBoolean("login_debug", false)
@ -145,7 +144,7 @@ class LoginActivity : AppCompatActivity() {
var cancel = false var cancel = false
var focusView: View? = null var focusView: View? = null
if (!url.isBaseUrlValid()) { if (!url.isBaseUrlValid(logErrors, this@LoginActivity)) {
urlView.error = getString(R.string.login_url_problem) urlView.error = getString(R.string.login_url_problem)
focusView = urlView focusView = urlView
cancel = true cancel = true
@ -164,7 +163,7 @@ class LoginActivity : AppCompatActivity() {
} }
} }
if (isWithLogin || isWithHTTPLogin) { if (isWithLogin) {
if (TextUtils.isEmpty(password)) { if (TextUtils.isEmpty(password)) {
passwordView.error = getString(R.string.error_invalid_password) passwordView.error = getString(R.string.error_invalid_password)
focusView = passwordView 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) { if (cancel) {
focusView?.requestFocus() focusView?.requestFocus()
} else { } else {
@ -195,6 +208,7 @@ class LoginActivity : AppCompatActivity() {
this, this,
this@LoginActivity, this@LoginActivity,
isWithSelfSignedCert, isWithSelfSignedCert,
-1L,
isWithSelfSignedCert isWithSelfSignedCert
) )

View File

@ -10,6 +10,7 @@ import android.preference.PreferenceManager
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import android.widget.ImageView import android.widget.ImageView
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
@ -28,10 +29,8 @@ import java.io.IOException
import java.util.UUID.randomUUID import java.util.UUID.randomUUID
@AcraHttpSender(uri = "http://amine-bou.fr:5984/acra-selfoss/_design/acra-storage/_update/report", @AcraHttpSender(uri = "http://37.187.110.167/amine/acra/simplest-acra.php",
basicAuthLogin = "selfoss", httpMethod = HttpSender.Method.POST)
basicAuthPassword = "selfoss",
httpMethod = HttpSender.Method.PUT)
@AcraDialog(resText = R.string.crash_dialog_text, @AcraDialog(resText = R.string.crash_dialog_text,
resCommentPrompt = R.string.crash_dialog_comment, resCommentPrompt = R.string.crash_dialog_comment,
resTheme = android.R.style.Theme_DeviceDefault_Dialog) 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], ReportField.USER_APP_START_DATE, ReportField.USER_COMMENT, ReportField.USER_CRASH_DATE, ReportField.USER_EMAIL, ReportField.CUSTOM_DATA],
buildConfigClass = BuildConfig::class) buildConfigClass = BuildConfig::class)
class MyApp : MultiDexApplication() { class MyApp : MultiDexApplication() {
private lateinit var config: Config
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
config = Config(baseContext)
initAmplify() initAmplify()
val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
@ -68,11 +68,18 @@ class MyApp : MultiDexApplication() {
private fun handleNotificationChannels() { private fun handleNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val name = getString(R.string.notification_channel_sync) val name = getString(R.string.notification_channel_sync)
val importance = NotificationManager.IMPORTANCE_LOW val importance = NotificationManager.IMPORTANCE_LOW
val mChannel = NotificationChannel(Config.syncChannelId, name, importance) val mChannel = NotificationChannel(Config.syncChannelId, name, importance)
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val newItemsChannelname = getString(R.string.new_items_channel_sync)
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
val newItemsChannelmChannel = NotificationChannel(Config.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
notificationManager.createNotificationChannel(mChannel) notificationManager.createNotificationChannel(mChannel)
notificationManager.createNotificationChannel(newItemsChannelmChannel)
} }
} }
@ -100,7 +107,7 @@ class MyApp : MultiDexApplication() {
tag: String? tag: String?
) { ) {
Glide.with(imageView?.context) Glide.with(imageView?.context)
.load(uri) .loadMaybeBasicAuth(config, uri.toString())
.apply(RequestOptions.fitCenterTransform().placeholder(placeholder)) .apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
.into(imageView) .into(imageView)
} }

View File

@ -1,5 +1,6 @@
package apps.amine.bou.readerforselfoss package apps.amine.bou.readerforselfoss
import android.content.SharedPreferences
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -13,6 +14,7 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.room.Room import androidx.room.Room
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
@ -51,6 +53,11 @@ class ReaderActivity : AppCompatActivity() {
private lateinit var toolbarMenu: Menu private lateinit var toolbarMenu: Menu
private lateinit var db: AppDatabase 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) { private fun showMenuItem(willAddToFavorite: Boolean) {
toolbarMenu.findItem(R.id.save).isVisible = willAddToFavorite toolbarMenu.findItem(R.id.save).isVisible = willAddToFavorite
@ -65,6 +72,8 @@ class ReaderActivity : AppCompatActivity() {
showMenuItem(false) showMenuItem(false)
} }
private lateinit var editor: SharedPreferences.Editor
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -85,16 +94,19 @@ class ReaderActivity : AppCompatActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
val prefs = PreferenceManager.getDefaultSharedPreferences(this) prefs = PreferenceManager.getDefaultSharedPreferences(this)
editor = prefs.edit()
debugReadingItems = prefs.getBoolean("read_debug", false) debugReadingItems = prefs.getBoolean("read_debug", false)
userIdentifier = prefs.getString("unique_id", "") userIdentifier = prefs.getString("unique_id", "")
markOnScroll = prefs.getBoolean("mark_on_scroll", false) markOnScroll = prefs.getBoolean("mark_on_scroll", false)
activeAlignment = prefs.getInt("text_align", JUSTIFY)
api = SelfossApi( api = SelfossApi(
this, this,
this@ReaderActivity, this@ReaderActivity,
prefs.getBoolean("isSelfSignedCert", false), prefs.getBoolean("isSelfSignedCert", false),
prefs.getString("api_timeout", "-1").toLong(),
prefs.getBoolean("should_log_everything", false) prefs.getBoolean("should_log_everything", false)
) )
@ -222,6 +234,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 { override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater val inflater = menuInflater
inflater.inflate(R.menu.reader_menu, menu) inflater.inflate(R.menu.reader_menu, menu)
@ -232,6 +249,11 @@ class ReaderActivity : AppCompatActivity() {
} else { } else {
canFavorite() canFavorite()
} }
if (activeAlignment == JUSTIFY) {
alignmentMenu(false)
} else {
alignmentMenu(true)
}
return true return true
} }
@ -313,10 +335,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) return super.onOptionsItemSelected(item)
} }
private fun refreshFragment() {
finish()
overridePendingTransition(0, 0)
startActivity(intent)
overridePendingTransition(0, 0)
}
companion object { companion object {
var allItems: ArrayList<Item> = ArrayList() var allItems: ArrayList<Item> = ArrayList()
} }

View File

@ -60,6 +60,7 @@ class SourcesActivity : AppCompatActivity() {
this, this,
this@SourcesActivity, this@SourcesActivity,
prefs.getBoolean("isSelfSignedCert", false), prefs.getBoolean("isSelfSignedCert", false),
prefs.getString("api_timeout", "-1").toLong(),
prefs.getBoolean("should_log_everything", false) prefs.getBoolean("should_log_everything", false)
) )
var items: ArrayList<Source> = ArrayList() var items: ArrayList<Source> = ArrayList()

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.database.AppDatabase
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
import apps.amine.bou.readerforselfoss.themes.AppColors 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.LinkOnTouchListener
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
@ -51,6 +52,7 @@ class ItemCardAdapter(
override val appColors: AppColors, override val appColors: AppColors,
override val debugReadingItems: Boolean, override val debugReadingItems: Boolean,
override val userIdentifier: String, override val userIdentifier: String,
override val config: Config,
override val updateItems: (ArrayList<Item>) -> Unit override val updateItems: (ArrayList<Item>) -> Unit
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() { ) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.baseContext private val c: Context = app.baseContext
@ -86,7 +88,7 @@ class ItemCardAdapter(
holder.mView.itemImage.setImageDrawable(null) holder.mView.itemImage.setImageDrawable(null)
} else { } else {
holder.mView.itemImage.visibility = View.VISIBLE 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()) { if (itm.getIcon(c).isEmpty()) {
@ -99,7 +101,7 @@ class ItemCardAdapter(
.build(itm.sourcetitle.toTextDrawableString(c), color) .build(itm.sourcetitle.toTextDrawableString(c), color)
holder.mView.sourceImage.setImageDrawable(drawable) holder.mView.sourceImage.setImageDrawable(drawable)
} else { } else {
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.sourceImage) c.circularBitmapDrawable(config, itm.getIcon(c), holder.mView.sourceImage)
} }
holder.mView.favButton.isLiked = itm.starred holder.mView.favButton.isLiked = itm.starred
@ -179,7 +181,8 @@ class ItemCardAdapter(
}) })
mView.shareBtn.setOnClickListener { mView.shareBtn.setOnClickListener {
c.shareLink(items[adapterPosition].getLinkDecoded()) val item = items[adapterPosition]
c.shareLink(item.getLinkDecoded(), item.title)
} }
mView.browserBtn.setOnClickListener { 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.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
import apps.amine.bou.readerforselfoss.themes.AppColors 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.LinkOnTouchListener
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
@ -52,6 +53,7 @@ class ItemListAdapter(
override val debugReadingItems: Boolean, override val debugReadingItems: Boolean,
override val userIdentifier: String, override val userIdentifier: String,
override val appColors: AppColors, override val appColors: AppColors,
override val config: Config,
override val updateItems: (ArrayList<Item>) -> Unit override val updateItems: (ArrayList<Item>) -> Unit
) : ItemsAdapter<ItemListAdapter.ViewHolder>() { ) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL
@ -108,10 +110,10 @@ class ItemListAdapter(
holder.mView.itemImage.setImageDrawable(drawable) holder.mView.itemImage.setImageDrawable(drawable)
} else { } else {
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage) c.circularBitmapDrawable(config, itm.getIcon(c), holder.mView.itemImage)
} }
} else { } 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.database.AppDatabase
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
import apps.amine.bou.readerforselfoss.themes.AppColors 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.maybeHandleSilentException
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
import apps.amine.bou.readerforselfoss.utils.persistence.toEntity 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 userIdentifier: String
abstract val app: Activity abstract val app: Activity
abstract val appColors: AppColors abstract val appColors: AppColors
abstract val config: Config
abstract val updateItems: (ArrayList<Item>) -> Unit abstract val updateItems: (ArrayList<Item>) -> Unit
fun updateAllItems(newItems: ArrayList<Item>) { fun updateAllItems(newItems: ArrayList<Item>) {
@ -39,7 +41,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
updateItems(items) updateItems(items)
} }
private fun doUnmark(i: Item, position: Int) { private fun unmarkSnackbar(i: Item, position: Int) {
val s = Snackbar val s = Snackbar
.make( .make(
app.findViewById(R.id.coordLayout), app.findViewById(R.id.coordLayout),
@ -69,12 +71,11 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
} }
notifyItemRemoved(position) notifyItemRemoved(position)
updateItems(items) updateItems(items)
doUnmark(i, position)
} }
}) })
} else { } else {
thread { 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() 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] val i = items[position]
items.remove(i) items.remove(i)
notifyItemRemoved(position) notifyItemRemoved(position)
@ -103,7 +161,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
) { ) {
if (!response.succeeded() && debugReadingItems) { if (!response.succeeded() && debugReadingItems) {
val message = val message =
"message: ${response.message()} " + "MARK message: ${response.message()} " +
"response isSuccess: ${response.isSuccessful} " + "response isSuccess: ${response.isSuccessful} " +
"response code: ${response.code()} " + "response code: ${response.code()} " +
"response message: ${response.message()} " + "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() Toast.makeText(app.baseContext, message, Toast.LENGTH_LONG).show()
} }
doUnmark(i, position) unmarkSnackbar(i, position)
} }
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { 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), app.getString(R.string.cant_mark_read),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
items.add(i) items.add(position, i)
notifyItemInserted(position) notifyItemInserted(position)
updateItems(items) updateItems(items)
@ -139,7 +197,64 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
} else { } else {
thread { thread {
db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false)) 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.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Source import apps.amine.bou.readerforselfoss.api.selfoss.Source
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse 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.glide.circularBitmapDrawable
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
@ -29,6 +30,7 @@ class SourcesListAdapter(
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() { ) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL
private lateinit var config: Config
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate( val v = LayoutInflater.from(c).inflate(
@ -41,6 +43,7 @@ class SourcesListAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itm = items[position] val itm = items[position]
config = Config(c)
if (itm.getIcon(c).isEmpty()) { if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.title) val color = generator.getColor(itm.title)
@ -52,7 +55,7 @@ class SourcesListAdapter(
.build(itm.title.toTextDrawableString(c), color) .build(itm.title.toTextDrawableString(c), color)
holder.mView.itemImage.setImageDrawable(drawable) holder.mView.itemImage.setImageDrawable(drawable)
} else { } else {
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage) c.circularBitmapDrawable(config, itm.getIcon(c), holder.mView.itemImage)
} }
holder.mView.sourceTitle.text = itm.title holder.mView.sourceTitle.text = itm.title

View File

@ -18,11 +18,13 @@ import retrofit2.Call
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
class SelfossApi( class SelfossApi(
c: Context, c: Context,
callingActivity: Activity?, callingActivity: Activity?,
isWithSelfSignedCert: Boolean, isWithSelfSignedCert: Boolean,
timeout: Long,
shouldLog: Boolean shouldLog: Boolean
) { ) {
@ -38,16 +40,25 @@ class SelfossApi(
this this
} }
fun OkHttpClient.Builder.maybeWithSettingsTimeout(timeout: Long): OkHttpClient.Builder =
if (timeout != -1L) {
this.readTimeout(timeout, TimeUnit.SECONDS)
.connectTimeout(timeout, TimeUnit.SECONDS)
} else {
this
}
fun Credentials.createAuthenticator(): DispatchingAuthenticator = fun Credentials.createAuthenticator(): DispatchingAuthenticator =
DispatchingAuthenticator.Builder() DispatchingAuthenticator.Builder()
.with("digest", DigestAuthenticator(this)) .with("digest", DigestAuthenticator(this))
.with("basic", BasicAuthenticator(this)) .with("basic", BasicAuthenticator(this))
.build() .build()
fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient.Builder { fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean, timeout: Long): OkHttpClient.Builder {
val authCache = ConcurrentHashMap<String, CachingAuthenticator>() val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
return OkHttpClient return OkHttpClient
.Builder() .Builder()
.maybeWithSettingsTimeout(timeout)
.maybeWithSelfSigned(isWithSelfSignedCert) .maybeWithSelfSigned(isWithSelfSignedCert)
.authenticator(CachingAuthenticatorDecorator(this, authCache)) .authenticator(CachingAuthenticatorDecorator(this, authCache))
.addInterceptor(AuthenticationCacheInterceptor(authCache)) .addInterceptor(AuthenticationCacheInterceptor(authCache))
@ -66,6 +77,7 @@ class SelfossApi(
val gson = val gson =
GsonBuilder() GsonBuilder()
.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter()) .registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
.registerTypeAdapter(SelfossTagType::class.java, SelfossTagTypeTypeAdapter())
.setLenient() .setLenient()
.create() .create()
@ -77,7 +89,7 @@ class SelfossApi(
HttpLoggingInterceptor.Level.NONE HttpLoggingInterceptor.Level.NONE
} }
val httpClient = authenticator.getHttpClien(isWithSelfSignedCert) val httpClient = authenticator.getHttpClien(isWithSelfSignedCert, timeout)
httpClient.addInterceptor(logging) httpClient.addInterceptor(logging)

View File

@ -45,7 +45,7 @@ data class Spout(
data class Source( data class Source(
@SerializedName("id") val id: String, @SerializedName("id") val id: String,
@SerializedName("title") val title: String, @SerializedName("title") val title: String,
@SerializedName("tags") val tags: String, @SerializedName("tags") val tags: SelfossTagType,
@SerializedName("spout") val spout: String, @SerializedName("spout") val spout: String,
@SerializedName("error") val error: String, @SerializedName("error") val error: String,
@SerializedName("icon") val icon: String @SerializedName("icon") val icon: String
@ -71,7 +71,7 @@ data class Item(
@SerializedName("icon") val icon: String, @SerializedName("icon") val icon: String,
@SerializedName("link") val link: String, @SerializedName("link") val link: String,
@SerializedName("sourcetitle") val sourcetitle: String, @SerializedName("sourcetitle") val sourcetitle: String,
@SerializedName("tags") val tags: String @SerializedName("tags") val tags: SelfossTagType
) : Parcelable { ) : Parcelable {
var config: Config? = null var config: Config? = null
@ -94,7 +94,7 @@ data class Item(
icon = source.readString(), icon = source.readString(),
link = source.readString(), link = source.readString(),
sourcetitle = source.readString(), sourcetitle = source.readString(),
tags = source.readString() tags = source.readParcelable(ClassLoader.getSystemClassLoader())
) )
override fun describeContents() = 0 override fun describeContents() = 0
@ -110,7 +110,7 @@ data class Item(
dest.writeString(icon) dest.writeString(icon)
dest.writeString(link) dest.writeString(link)
dest.writeString(sourcetitle) dest.writeString(sourcetitle)
dest.writeString(tags) dest.writeParcelable(tags, flags)
} }
fun getIcon(app: Context): String { fun getIcon(app: Context): String {
@ -153,4 +153,27 @@ data class Item(
return stringUrl return stringUrl
} }
}
data class SelfossTagType(val tags: String) : Parcelable {
companion object {
@JvmField val CREATOR: Parcelable.Creator<SelfossTagType> =
object : Parcelable.Creator<SelfossTagType> {
override fun createFromParcel(source: Parcel): SelfossTagType =
SelfossTagType(source)
override fun newArray(size: Int): Array<SelfossTagType?> = arrayOfNulls(size)
}
}
constructor(source: Parcel) : this(
tags = source.readString()
)
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(tags)
}
} }

View File

@ -0,0 +1,22 @@
package apps.amine.bou.readerforselfoss.api.selfoss
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import java.lang.reflect.Type
internal class SelfossTagTypeTypeAdapter : JsonDeserializer<SelfossTagType> {
@Throws(JsonParseException::class)
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): SelfossTagType? =
if (json.isJsonArray) {
SelfossTagType(json.asJsonArray.joinToString(",") { it.toString() })
} else {
SelfossTagType(json.toString())
}
}

View File

@ -1,14 +1,17 @@
package apps.amine.bou.readerforselfoss.background package apps.amine.bou.readerforselfoss.background
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.os.Handler import android.content.Intent
import android.preference.PreferenceManager import android.preference.PreferenceManager
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
import androidx.core.app.NotificationCompat.PRIORITY_LOW import androidx.core.app.NotificationCompat.PRIORITY_LOW
import androidx.room.Room import androidx.room.Room
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import apps.amine.bou.readerforselfoss.MainActivity
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
@ -43,14 +46,14 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
.setOngoing(true) .setOngoing(true)
.setPriority(PRIORITY_LOW) .setPriority(PRIORITY_LOW)
.setChannelId(Config.syncChannelId) .setChannelId(Config.syncChannelId)
.setSmallIcon(R.drawable.ic_cloud_download) .setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
notificationManager.notify(1, notification.build()) notificationManager.notify(1, notification.build())
val settings = val settings =
this.context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) this.context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context) val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context)
val shouldLogEverything = sharedPref.getBoolean("should_log_everything", false) val notifyNewItems = sharedPref.getBoolean("notify_new_items", false)
db = Room.databaseBuilder( db = Room.databaseBuilder(
applicationContext, applicationContext,
@ -61,7 +64,8 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
this.context, this.context,
null, null,
settings.getBoolean("isSelfSignedCert", false), settings.getBoolean("isSelfSignedCert", false),
shouldLogEverything sharedPref.getString("api_timeout", "-1").toLong(),
sharedPref.getBoolean("should_log_everything", false)
) )
api.allItems().enqueue(object : Callback<List<Item>> { api.allItems().enqueue(object : Callback<List<Item>> {
@ -81,6 +85,28 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
db.itemsDao().deleteAllItems() db.itemsDao().deleteAllItems()
db.itemsDao() db.itemsDao()
.insertAllItems(*(apiItems.map { it.toEntity() }).toTypedArray()) .insertAllItems(*(apiItems.map { it.toEntity() }).toTypedArray())
val newSize = apiItems.filter { it.unread }.size
if (notifyNewItems && newSize > 0) {
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val newItemsNotification = NotificationCompat.Builder(applicationContext, Config.newItemsChannelId)
.setContentTitle(context.getString(R.string.new_items_notification_title))
.setContentText(context.getString(R.string.new_items_notification_text, newSize))
.setPriority(PRIORITY_DEFAULT)
.setChannelId(Config.newItemsChannelId)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
Timer("", false).schedule(4000) {
notificationManager.notify(2, newItemsNotification.build())
}
}
} }
Timer("", false).schedule(4000) { Timer("", false).schedule(4000) {
notificationManager.cancel(1) notificationManager.cancel(1)

View File

@ -3,10 +3,13 @@ package apps.amine.bou.readerforselfoss.fragments
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.TypedArray
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.view.InflateException
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -17,6 +20,8 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.webkit.WebSettings import android.webkit.WebSettings
import androidx.appcompat.app.AlertDialog
import androidx.core.content.res.ResourcesCompat
import androidx.room.Room import androidx.room.Room
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
@ -32,6 +37,7 @@ import apps.amine.bou.readerforselfoss.themes.AppColors
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible 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.shareLink
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
import apps.amine.bou.readerforselfoss.utils.succeeded import apps.amine.bou.readerforselfoss.utils.succeeded
import apps.amine.bou.readerforselfoss.utils.toPx
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.github.rubensousa.floatingtoolbar.FloatingToolbar import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import kotlinx.android.synthetic.main.fragment_article.*
import kotlinx.android.synthetic.main.fragment_article.view.* import kotlinx.android.synthetic.main.fragment_article.view.*
import org.acra.ACRA import org.acra.ACRA
import retrofit2.Call import retrofit2.Call
@ -54,9 +60,9 @@ import kotlin.concurrent.thread
class ArticleFragment : Fragment() { class ArticleFragment : Fragment() {
private lateinit var pageNumber: Number private lateinit var pageNumber: Number
private var fontSize: Int = 14 private var fontSize: Int = 16
private lateinit var allItems: ArrayList<Item> private lateinit var allItems: ArrayList<Item>
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper private var mCustomTabActivityHelper: CustomTabActivityHelper? = null;
private lateinit var url: String private lateinit var url: String
private lateinit var contentText: String private lateinit var contentText: String
private lateinit var contentSource: String private lateinit var contentSource: String
@ -66,14 +72,27 @@ class ArticleFragment : Fragment() {
private lateinit var fab: FloatingActionButton private lateinit var fab: FloatingActionButton
private lateinit var appColors: AppColors private lateinit var appColors: AppColors
private lateinit var db: AppDatabase 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() { override fun onStop() {
super.onStop() super.onStop()
mCustomTabActivityHelper.unbindCustomTabsService(activity) if (mCustomTabActivityHelper != null) {
mCustomTabActivityHelper!!.unbindCustomTabsService(activity)
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(activity!!) appColors = AppColors(activity!!)
config = Config(activity!!)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -86,153 +105,190 @@ class ArticleFragment : Fragment() {
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build() ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build()
} }
private lateinit var rootView: ViewGroup
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
rootView = inflater try {
.inflate(R.layout.fragment_article, container, false) as ViewGroup rootView = inflater
.inflate(R.layout.fragment_article, container, false) as ViewGroup
url = allItems[pageNumber.toInt()].getLinkDecoded() url = allItems[pageNumber.toInt()].getLinkDecoded()
contentText = allItems[pageNumber.toInt()].content contentText = allItems[pageNumber.toInt()].content
contentTitle = allItems[pageNumber.toInt()].title contentTitle = allItems[pageNumber.toInt()].title
contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!) contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!)
contentSource = allItems[pageNumber.toInt()].sourceAndDateText() contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
val prefs = PreferenceManager.getDefaultSharedPreferences(activity) prefs = PreferenceManager.getDefaultSharedPreferences(activity)
editor = prefs.edit() editor = prefs.edit()
fontSize = prefs.getString("reader_font_size", "14").toInt() fontSize = prefs.getString("reader_font_size", "16").toInt()
val settings = activity!!.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) font = prefs.getString("reader_font", "")
val debugReadingItems = prefs.getBoolean("read_debug", false) if (font.isNotEmpty()) {
resId = context!!.resources.getIdentifier(font, "font", context!!.packageName)
typeface = ResourcesCompat.getFont(context!!, resId)!!
}
val api = SelfossApi( refreshAlignment()
context!!,
activity!!,
settings.getBoolean("isSelfSignedCert", false),
prefs.getBoolean("should_log_everything", false)
)
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 fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
floatingToolbar.attachFab(fab)
floatingToolbar.background = ColorDrawable(appColors.colorAccent) fab.rippleColor = appColors.colorAccentDark
val customTabsIntent = activity!!.buildCustomTabsIntent() val floatingToolbar: FloatingToolbar = rootView!!.floatingToolbar
mCustomTabActivityHelper = CustomTabActivityHelper() floatingToolbar.attachFab(fab)
mCustomTabActivityHelper.bindCustomTabsService(activity)
floatingToolbar.background = ColorDrawable(appColors.colorAccent)
val customTabsIntent = activity!!.buildCustomTabsIntent()
mCustomTabActivityHelper = CustomTabActivityHelper()
mCustomTabActivityHelper!!.bindCustomTabsService(activity)
floatingToolbar.setClickListener( floatingToolbar.setClickListener(
object : FloatingToolbar.ItemClickListener { object : FloatingToolbar.ItemClickListener {
override fun onItemClick(item: MenuItem) { override fun onItemClick(item: MenuItem) {
when (item.itemId) { when (item.itemId) {
R.id.more_action -> getContentFromMercury(customTabsIntent, prefs) R.id.more_action -> getContentFromMercury(customTabsIntent, prefs)
R.id.share_action -> activity!!.shareLink(url) R.id.share_action -> activity!!.shareLink(url, contentTitle)
R.id.open_action -> activity!!.openItemUrl( R.id.open_action -> activity!!.openItemUrl(
allItems, allItems,
pageNumber.toInt(), pageNumber.toInt(),
url, url,
customTabsIntent, customTabsIntent,
false, false,
false, false,
activity!! 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!!)
}
}
}
) )
} else { R.id.unread_action -> if ((context != null && context!!.isNetworkAccessible(null)) || context == null) {
thread { api.unmarkItem(allItems[pageNumber.toInt()].id).enqueue(
db.actionsDao().insertAllActions(ActionEntity(allItems[pageNumber.toInt()].id, false, true, false, false)) 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()) { } catch (e: InflateException) {
getContentFromMercury(customTabsIntent, prefs) AlertDialog.Builder(context!!)
} else { .setMessage(context!!.getString(R.string.webview_dialog_issue_message))
rootView.titleView.text = contentTitle .setTitle(context!!.getString(R.string.webview_dialog_issue_title))
.setPositiveButton(android.R.string.ok
htmlToWebview(contentText, prefs) ) { dialog, which ->
val sharedPref = PreferenceManager.getDefaultSharedPreferences(context!!)
if (!contentImage.isEmptyOrNullOrNullString() && context != null) { val editor = sharedPref.edit()
rootView.imageView.visibility = View.VISIBLE editor.putBoolean("prefer_article_viewer", false)
Glide editor.commit()
.with(context!!) activity!!.finish()
.asBitmap() }
.load(contentImage) .create()
.apply(RequestOptions.fitCenterTransform()) .show()
.into(rootView.imageView)
} else {
rootView.imageView.visibility = View.GONE
}
} }
rootView.nestedScrollView.setOnScrollChangeListener(
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) {
fab.hide()
} else {
if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
}
}
)
return rootView return rootView
} }
private fun refreshAlignment() {
textAlignment = when (prefs.getInt("text_align", 1)) {
1 -> "justify"
2 -> "left"
else -> "justify"
}
}
private fun getContentFromMercury( private fun getContentFromMercury(
customTabsIntent: CustomTabsIntent, customTabsIntent: CustomTabsIntent,
prefs: SharedPreferences prefs: SharedPreferences
) { ) {
if ((context != null && context!!.isNetworkAccessible(null)) || context == null) { if ((context != null && context!!.isNetworkAccessible(null)) || context == null) {
rootView.progressBar.visibility = View.VISIBLE rootView!!.progressBar.visibility = View.VISIBLE
val parser = MercuryApi( val parser = MercuryApi(
prefs.getBoolean("should_log_everything", false) prefs.getBoolean("should_log_everything", false)
) )
@ -247,13 +303,16 @@ class ArticleFragment : Fragment() {
try { try {
if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) { if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) {
try { try {
rootView.titleView.text = response.body()!!.title rootView!!.titleView.text = response.body()!!.title
if (typeface != null) {
rootView!!.titleView.typeface = typeface
}
try { try {
// Note: Mercury may return relative urls... If it does the url val will not be changed. // Note: Mercury may return relative urls... If it does the url val will not be changed.
URL(response.body()!!.url) URL(response.body()!!.url)
url = response.body()!!.url url = response.body()!!.url
} catch (e: MalformedURLException) { } catch (e: MalformedURLException) {
ACRA.getErrorReporter().maybeHandleSilentException(e, activity!!) // Mercury returned a relative url. We do nothing.
} }
} catch (e: Exception) { } catch (e: Exception) {
if (context != null) { if (context != null) {
@ -262,7 +321,8 @@ class ArticleFragment : Fragment() {
} }
try { try {
htmlToWebview(response.body()!!.content.orEmpty(), prefs) contentText = response.body()!!.content.orEmpty()
htmlToWebview()
} catch (e: Exception) { } catch (e: Exception) {
if (context != null) { if (context != null) {
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!) ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
@ -271,19 +331,19 @@ class ArticleFragment : Fragment() {
try { try {
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) { 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 { try {
Glide Glide
.with(context!!) .with(context!!)
.asBitmap() .asBitmap()
.load(response.body()!!.lead_image_url) .loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty())
.apply(RequestOptions.fitCenterTransform()) .apply(RequestOptions.fitCenterTransform())
.into(rootView.imageView) .into(rootView!!.imageView)
} catch (e: Exception) { } catch (e: Exception) {
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!) ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
} }
} else { } else {
rootView.imageView.visibility = View.GONE rootView!!.imageView.visibility = View.GONE
} }
} catch (e: Exception) { } catch (e: Exception) {
if (context != null) { if (context != null) {
@ -292,9 +352,9 @@ class ArticleFragment : Fragment() {
} }
try { try {
rootView.nestedScrollView.scrollTo(0, 0) rootView!!.nestedScrollView.scrollTo(0, 0)
rootView.progressBar.visibility = View.GONE rootView!!.progressBar.visibility = View.GONE
} catch (e: Exception) { } catch (e: Exception) {
if (context != null) { if (context != null) {
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!) ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
@ -325,13 +385,18 @@ class ArticleFragment : Fragment() {
} }
} }
private fun htmlToWebview(c: String, prefs: SharedPreferences) { private fun htmlToWebview() {
val stringColor = String.format("#%06X", 0xFFFFFF and appColors.colorAccent) 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) { val (textColor, backgroundColor) = if (appColors.isDarkTheme) {
if (context != null) { if (context != null) {
rootView.webcontent.setBackgroundColor( rootView!!.webcontent.setBackgroundColor(
ContextCompat.getColor( ContextCompat.getColor(
context!!, context!!,
R.color.dark_webview R.color.dark_webview
@ -343,7 +408,7 @@ class ArticleFragment : Fragment() {
} }
} else { } else {
if (context != null) { if (context != null) {
rootView.webcontent.setBackgroundColor( rootView!!.webcontent.setBackgroundColor(
ContextCompat.getColor( ContextCompat.getColor(
context!!, context!!,
R.color.light_webview R.color.light_webview
@ -367,15 +432,15 @@ class ArticleFragment : Fragment() {
"#FFFFFF" "#FFFFFF"
} }
rootView.webcontent.settings.useWideViewPort = true rootView!!.webcontent.settings.useWideViewPort = true
rootView.webcontent.settings.loadWithOverviewMode = true rootView!!.webcontent.settings.loadWithOverviewMode = true
rootView.webcontent.settings.javaScriptEnabled = false rootView!!.webcontent.settings.javaScriptEnabled = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
rootView.webcontent.settings.layoutAlgorithm = rootView!!.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
} else { } else {
rootView.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN rootView!!.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN
} }
var baseUrl: String? = null var baseUrl: String? = null
@ -387,10 +452,29 @@ class ArticleFragment : Fragment() {
ACRA.getErrorReporter().maybeHandleSilentException(e, activity!!) 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, baseUrl,
"""<html> """<html>
|<head> |<head>
| <meta name="viewport" content="width=device-width, initial-scale=1">
| <style> | <style>
| img { | img {
| display: inline-block; | display: inline-block;
@ -405,13 +489,14 @@ class ArticleFragment : Fragment() {
| color: $stringTextColor; | color: $stringTextColor;
| } | }
| * { | * {
| font-size: ${fontSize.toPx}px; | font-size: ${fontSize}px;
| text-align: justify; | text-align: $textAlignment;
| word-break: break-word; | word-break: break-word;
| overflow:hidden; | overflow:hidden;
| line-height: 1.5em;
| } | }
| a, pre, code { | a, pre, code {
| text-align: left; | text-align: $textAlignment;
| } | }
| pre, code { | pre, code {
| white-space: pre-wrap; | white-space: pre-wrap;
@ -419,9 +504,10 @@ class ArticleFragment : Fragment() {
| background-color: $stringBackgroundColor; | background-color: $stringBackgroundColor;
| } | }
| </style> | </style>
| $fontLinkAndStyle
|</head> |</head>
|<body> |<body>
| $c | $contentText
|</body>""".trimMargin(), |</body>""".trimMargin(),
"text/html", "text/html",
"utf-8", "utf-8",
@ -430,7 +516,7 @@ class ArticleFragment : Fragment() {
} }
private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) { private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) {
rootView.progressBar.visibility = View.GONE rootView!!.progressBar.visibility = View.GONE
activity!!.openItemUrl( activity!!.openItemUrl(
allItems, allItems,
pageNumber.toInt(), pageNumber.toInt(),

View File

@ -8,6 +8,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -24,6 +25,7 @@ import android.text.Editable;
import android.text.InputFilter; import android.text.InputFilter;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
@ -124,6 +126,27 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
@TargetApi(Build.VERSION_CODES.HONEYCOMB) @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) { public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_headers, 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);
}
} }
/** /**
@ -136,6 +159,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|| GeneralPreferenceFragment.class.getName().equals(fragmentName) || GeneralPreferenceFragment.class.getName().equals(fragmentName)
|| ArticleViewerPreferenceFragment.class.getName().equals(fragmentName) || ArticleViewerPreferenceFragment.class.getName().equals(fragmentName)
|| OfflinePreferenceFragment.class.getName().equals(fragmentName) || OfflinePreferenceFragment.class.getName().equals(fragmentName)
|| ExperimentalPreferenceFragment.class.getName().equals(fragmentName)
|| DebugPreferenceFragment.class.getName().equals(fragmentName) || DebugPreferenceFragment.class.getName().equals(fragmentName)
|| LinksPreferenceFragment.class.getName().equals(fragmentName) || LinksPreferenceFragment.class.getName().equals(fragmentName)
|| ThemePreferenceFragment.class.getName().equals(fragmentName); || ThemePreferenceFragment.class.getName().equals(fragmentName);
@ -384,6 +408,26 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
} }
} }
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class ExperimentalPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_experimental);
setHasOptionsMenu(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
getActivity().finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {

View File

@ -2,11 +2,21 @@ package apps.amine.bou.readerforselfoss.utils
import android.content.Context import android.content.Context
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.provider.Settings
import org.acra.ErrorReporter import org.acra.ErrorReporter
fun ErrorReporter.maybeHandleSilentException(throwable: Throwable, ctx: Context) { fun ErrorReporter.maybeHandleSilentException(throwable: Throwable, ctx: Context) {
val sharedPref = PreferenceManager.getDefaultSharedPreferences(ctx) 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) this.handleSilentException(throwable)
} }
} }

View File

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

View File

@ -38,6 +38,8 @@ class Config(c: Context) {
const val syncChannelId = "sync-channel-id" const val syncChannelId = "sync-channel-id"
const val newItemsChannelId = "new-items-channel-id"
fun logoutAndRedirect( fun logoutAndRedirect(
c: Context, c: Context,
callingActivity: Activity, callingActivity: Activity,

View File

@ -3,6 +3,7 @@ package apps.amine.bou.readerforselfoss.utils
import android.content.Context import android.content.Context
import android.text.format.DateUtils import android.text.format.DateUtils
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossTagType
import org.acra.ACRA import org.acra.ACRA
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -44,8 +45,8 @@ fun Item.toggleStar(): Item {
fun List<Item>.flattenTags(): List<Item> = fun List<Item>.flattenTags(): List<Item> =
this.flatMap { this.flatMap {
val item = it val item = it
val tags: List<String> = it.tags.split(",") val tags: List<String> = it.tags.tags.split(",")
tags.map { tags.map { t ->
item.copy(tags = it.trim()) item.copy(tags = SelfossTagType(t.trim()))
} }
} }

View File

@ -2,6 +2,7 @@ package apps.amine.bou.readerforselfoss.utils
import android.app.Activity import android.app.Activity
import android.app.PendingIntent import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory 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.api.selfoss.Item
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import okhttp3.HttpUrl import okhttp3.HttpUrl
import org.acra.ACRA
fun Context.buildCustomTabsIntent(): CustomTabsIntent { fun Context.buildCustomTabsIntent(): CustomTabsIntent {
@ -128,13 +130,17 @@ fun Context.openItemUrl(
private fun openInBrowser(linkDecoded: String, app: Activity) { private fun openInBrowser(linkDecoded: String, app: Activity) {
val intent = Intent(Intent.ACTION_VIEW) val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(linkDecoded) 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 = fun String.isUrlValid(): Boolean =
HttpUrl.parse(this) != null && Patterns.WEB_URL.matcher(this).matches() 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) val baseUrl = HttpUrl.parse(this)
var existsAndEndsWithSlash = false var existsAndEndsWithSlash = false
if (baseUrl != null) { if (baseUrl != null) {

View File

@ -2,30 +2,30 @@ package apps.amine.bou.readerforselfoss.utils.glide
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.util.Base64
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import android.widget.ImageView import android.widget.ImageView
import apps.amine.bou.readerforselfoss.utils.Config
import com.bumptech.glide.Glide 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.RequestOptions
import com.bumptech.glide.request.target.BitmapImageViewTarget 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) Glide.with(this)
.asBitmap() .asBitmap()
.load(url) .loadMaybeBasicAuth(config, url)
.apply(RequestOptions.centerCropTransform()) .apply(RequestOptions.centerCropTransform())
.into(iv) .into(iv)
fun Context.bitmapFitCenter(url: String, iv: ImageView) = fun Context.circularBitmapDrawable(config: Config, url: String, iv: ImageView) =
Glide.with(this) Glide.with(this)
.asBitmap() .asBitmap()
.load(url) .loadMaybeBasicAuth(config, url)
.apply(RequestOptions.fitCenterTransform())
.into(iv)
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
Glide.with(this)
.asBitmap()
.load(url)
.apply(RequestOptions.centerCropTransform()) .apply(RequestOptions.centerCropTransform())
.into(object : BitmapImageViewTarget(iv) { .into(object : BitmapImageViewTarget(iv) {
override fun setResource(resource: Bitmap?) { override fun setResource(resource: Bitmap?) {
@ -36,4 +36,24 @@ fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
circularBitmapDrawable.isCircular = true circularBitmapDrawable.isCircular = true
iv.setImageDrawable(circularBitmapDrawable) 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

@ -11,15 +11,16 @@ import com.google.android.material.snackbar.Snackbar
var snackBarShown = false var snackBarShown = false
var view: View? = null var view: View? = null
lateinit var s: Snackbar
fun Context.isNetworkAccessible(v: View?): Boolean { fun Context.isNetworkAccessible(v: View?, overrideOffline: Boolean = false): Boolean {
val cm = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val cm = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork: NetworkInfo? = cm.activeNetworkInfo val activeNetwork: NetworkInfo? = cm.activeNetworkInfo
val networkIsAccessible = activeNetwork != null && activeNetwork.isConnectedOrConnecting val networkIsAccessible = activeNetwork != null && activeNetwork.isConnectedOrConnecting
if (v != null && !networkIsAccessible && (!snackBarShown || v != view)) { if (v != null && (!networkIsAccessible || overrideOffline) && (!snackBarShown || v != view)) {
view = v view = v
val s = Snackbar s = Snackbar
.make( .make(
v, v,
R.string.no_network_connectivity, R.string.no_network_connectivity,
@ -37,5 +38,8 @@ fun Context.isNetworkAccessible(v: View?): Boolean {
s.show() s.show()
snackBarShown = true snackBarShown = true
} }
return networkIsAccessible if (snackBarShown && networkIsAccessible && !overrideOffline) {
s.dismiss()
}
return if(overrideOffline) overrideOffline else networkIsAccessible
} }

View File

@ -1,6 +1,7 @@
package apps.amine.bou.readerforselfoss.utils.persistence package apps.amine.bou.readerforselfoss.utils.persistence
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossTagType
import apps.amine.bou.readerforselfoss.api.selfoss.Source import apps.amine.bou.readerforselfoss.api.selfoss.Source
import apps.amine.bou.readerforselfoss.api.selfoss.Tag import apps.amine.bou.readerforselfoss.api.selfoss.Tag
import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity
@ -18,7 +19,7 @@ fun SourceEntity.toView(): Source =
Source( Source(
this.id, this.id,
this.title, this.title,
this.tags, SelfossTagType(this.tags),
this.spout, this.spout,
this.error, this.error,
this.icon this.icon
@ -28,7 +29,7 @@ fun Source.toEntity(): SourceEntity =
SourceEntity( SourceEntity(
this.id, this.id,
this.title, this.title,
this.tags, this.tags.tags,
this.spout, this.spout,
this.error, this.error,
this.icon.orEmpty() this.icon.orEmpty()
@ -53,7 +54,7 @@ fun ItemEntity.toView(): Item =
this.icon, this.icon,
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags SelfossTagType(this.tags)
) )
fun Item.toEntity(): ItemEntity = fun Item.toEntity(): ItemEntity =
@ -68,5 +69,5 @@ fun Item.toEntity(): ItemEntity =
this.icon, this.icon,
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags this.tags.tags
) )

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

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