Compare commits
	
		
			202 Commits
		
	
	
		
			v161808215
			...
			v171901010
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					65821492ad | ||
| 
						 | 
					6ede718a9f | ||
| 
						 | 
					f1757937a4 | ||
| 2bd2e0a953 | |||
| 
						 | 
					b5aef28af0 | ||
| 
						 | 
					45747a1506 | ||
| 
						 | 
					c6e2e08bcb | ||
| 
						 | 
					25bf18661e | ||
| 
						 | 
					6b088dcd24 | ||
| 
						 | 
					d2b18e1880 | ||
| 
						 | 
					eec7c94e98 | ||
| 
						 | 
					d1f8fcacc0 | ||
| 
						 | 
					07e4a33cbd | ||
| 
						 | 
					f6317f566e | ||
| 
						 | 
					9f51e4e6a5 | ||
| 
						 | 
					750604a31f | ||
| 
						 | 
					392eee0ad4 | ||
| 
						 | 
					37e7b987ee | ||
| 
						 | 
					9eac51e729 | ||
| 
						 | 
					fa9cce6783 | ||
| 
						 | 
					f0d4b63a97 | ||
| 
						 | 
					83eeb11388 | ||
| 
						 | 
					01f746f33d | ||
| 
						 | 
					200851894b | ||
| 
						 | 
					862e5cf4ab | ||
| 
						 | 
					0b07f2a407 | ||
| 
						 | 
					9ba6feef0b | ||
| 
						 | 
					63a0638522 | ||
| 
						 | 
					f9a4e6e363 | ||
| 
						 | 
					6b40fd4bdc | ||
| 
						 | 
					04c7776466 | ||
| 
						 | 
					92c335b4e1 | ||
| 
						 | 
					17251e576b | ||
| 
						 | 
					62ea782429 | ||
| 
						 | 
					f99474e3c1 | ||
| 
						 | 
					57ac8f428f | ||
| 
						 | 
					9cc1adbf15 | ||
| 
						 | 
					1d9a440ae7 | ||
| 
						 | 
					511553806c | ||
| 
						 | 
					87e7d7c4fe | ||
| 
						 | 
					ec87089310 | ||
| 
						 | 
					d8478ebb01 | ||
| 
						 | 
					600adc81b5 | ||
| 
						 | 
					ddac2870af | ||
| 8d9c8c1394 | |||
| 
						 | 
					b59c3bcb23 | ||
| 7f554adba5 | |||
| 
						 | 
					21ce061282 | ||
| 
						 | 
					bdb71e9b14 | ||
| 
						 | 
					df22e7de15 | ||
| 
						 | 
					6b3550396b | ||
| 
						 | 
					c70f1e31a6 | ||
| 
						 | 
					695670e944 | ||
| 
						 | 
					1028826788 | ||
| 
						 | 
					82a8977c96 | ||
| 
						 | 
					07d9ce1054 | ||
| 
						 | 
					7da7d49277 | ||
| 
						 | 
					9b45365441 | ||
| 
						 | 
					91a7464bce | ||
| 
						 | 
					51add226eb | ||
| 
						 | 
					332e9f5108 | ||
| 
						 | 
					0b91087c07 | ||
| 
						 | 
					ebbb1ba0f8 | ||
| 
						 | 
					e9143ae852 | ||
| 
						 | 
					42e8ecee78 | ||
| 
						 | 
					4efd76fcbc | ||
| 
						 | 
					fb1614070e | ||
| 
						 | 
					c473dd7227 | ||
| 
						 | 
					76bddb195d | ||
| 
						 | 
					1e02ad2041 | ||
| 
						 | 
					f6ab909f8b | ||
| 
						 | 
					7e520e9bed | ||
| 
						 | 
					32e2d05014 | ||
| 
						 | 
					40d9c97f73 | ||
| 
						 | 
					1aa68d3449 | ||
| 
						 | 
					aeeac8cccd | ||
| 
						 | 
					7292edf997 | ||
| 
						 | 
					f49256c72f | ||
| 
						 | 
					d02b28b81f | ||
| 
						 | 
					08117043dd | ||
| 
						 | 
					63496c993e | ||
| 
						 | 
					00ef542e49 | ||
| 
						 | 
					a78c6e6b33 | ||
| 
						 | 
					363eaf9bf9 | ||
| 
						 | 
					fec6683701 | ||
| 
						 | 
					1549edb647 | ||
| 
						 | 
					3de48ba162 | ||
| 
						 | 
					a2a3d6f1a7 | ||
| 
						 | 
					ccab2c7648 | ||
| 
						 | 
					880dd1db5c | ||
| 
						 | 
					ed18fea356 | ||
| 
						 | 
					9816b20bf6 | ||
| 
						 | 
					0bb2195bff | ||
| 
						 | 
					ab2d0c4036 | ||
| 
						 | 
					99fc417109 | ||
| 
						 | 
					dc304ef8c1 | ||
| 
						 | 
					c5511880bc | ||
| 
						 | 
					5fe76d735e | ||
| 
						 | 
					3064b3b835 | ||
| 
						 | 
					70dc8af3ce | ||
| 
						 | 
					53c8c241da | ||
| 
						 | 
					bdc4f5680b | ||
| 
						 | 
					ed290573b2 | ||
| 
						 | 
					1616a97a8a | ||
| 
						 | 
					d090183007 | ||
| 
						 | 
					de337fd260 | ||
| 
						 | 
					12dc206323 | ||
| 
						 | 
					d47c508dee | ||
| 
						 | 
					ed75f55437 | ||
| 
						 | 
					5ad3ad4a57 | ||
| 
						 | 
					aeac1bd1d4 | ||
| 
						 | 
					4d18085072 | ||
| 
						 | 
					0c9f8214ca | ||
| 
						 | 
					a7ce7ce02e | ||
| 
						 | 
					820986c7f0 | ||
| 
						 | 
					8079cae745 | ||
| 
						 | 
					6f067bd258 | ||
| 
						 | 
					b6ade0f212 | ||
| 
						 | 
					27dadc1be3 | ||
| 
						 | 
					95e4162b4c | ||
| 
						 | 
					f75557585e | ||
| 
						 | 
					1b4c26919b | ||
| 
						 | 
					ad085bf129 | ||
| 
						 | 
					8fcd551105 | ||
| 
						 | 
					a0954700e2 | ||
| 
						 | 
					9705560442 | ||
| 
						 | 
					1f47a13ce5 | ||
| 
						 | 
					6f0ff2c975 | ||
| 
						 | 
					76e5477986 | ||
| 
						 | 
					7f308d5be3 | ||
| 
						 | 
					54a43c83e8 | ||
| 
						 | 
					8fe7266c84 | ||
| 
						 | 
					d7a46b27b7 | ||
| 
						 | 
					2257d09fdd | ||
| 
						 | 
					047c5481c4 | ||
| 
						 | 
					8a6719f934 | ||
| 
						 | 
					51a692f3be | ||
| 
						 | 
					b333f93171 | ||
| 
						 | 
					89d34a1a71 | ||
| 
						 | 
					8788e920ce | ||
| 
						 | 
					d306fb53d3 | ||
| 
						 | 
					374537b5c7 | ||
| 
						 | 
					598149d4cd | ||
| 
						 | 
					50bcf18096 | ||
| 
						 | 
					a089ced03f | ||
| 
						 | 
					1f18dddf8b | ||
| 
						 | 
					f5934e240e | ||
| 
						 | 
					6b8da2eacf | ||
| 
						 | 
					f4757a67b7 | ||
| 
						 | 
					6edeb9d840 | ||
| 
						 | 
					43ce0fd7bc | ||
| 
						 | 
					5599f5a8fc | ||
| 
						 | 
					6fd45ceb4f | ||
| 
						 | 
					05ad8aac29 | ||
| 
						 | 
					fa4f2476b7 | ||
| 
						 | 
					00818a94e9 | ||
| 
						 | 
					5d5250e44a | ||
| 
						 | 
					3052b33132 | ||
| 
						 | 
					50de6f8b5b | ||
| 
						 | 
					f88a2f415f | ||
| 
						 | 
					96f9813e01 | ||
| 
						 | 
					fee739cb17 | ||
| 
						 | 
					b1814c63b9 | ||
| 
						 | 
					c1d45678f8 | ||
| 
						 | 
					3d34e59a94 | ||
| 
						 | 
					f1133bea8b | ||
| 
						 | 
					ec64c88ff1 | ||
| 
						 | 
					be66dbba6c | ||
| 
						 | 
					8926cdbbf5 | ||
| 
						 | 
					a956870dec | ||
| 
						 | 
					8ed7951c9b | ||
| 
						 | 
					5569a47674 | ||
| 
						 | 
					0dc6981913 | ||
| 
						 | 
					4984f2f7ad | ||
| 
						 | 
					3b6891c84a | ||
| 
						 | 
					4901e7174c | ||
| 
						 | 
					8d70e68fe2 | ||
| 
						 | 
					d3e1527b70 | ||
| 
						 | 
					0c201301f2 | ||
| 
						 | 
					6090590f24 | ||
| 
						 | 
					06b88c783d | ||
| 
						 | 
					bb75ebf635 | ||
| 
						 | 
					7d7d0014be | ||
| 
						 | 
					b3f8d44794 | ||
| 
						 | 
					29d1e38340 | ||
| 
						 | 
					2be872e61e | ||
| 
						 | 
					377c5518f7 | ||
| 
						 | 
					21be7357b5 | ||
| 
						 | 
					d47ba2c820 | ||
| 
						 | 
					a64b14614a | ||
| 
						 | 
					6a88192e77 | ||
| 
						 | 
					aa7c630818 | ||
| 
						 | 
					7fb54f14c7 | ||
| 
						 | 
					3d709c02b7 | ||
| 
						 | 
					339d384561 | ||
| 
						 | 
					50338d51af | ||
| 
						 | 
					92dbabf899 | ||
| 
						 | 
					0043021390 | ||
| 
						 | 
					70ba9b20da | ||
| 
						 | 
					7fda0a04a1 | ||
| 
						 | 
					3db3157dc9 | ||
| 
						 | 
					2089fe60ca | 
							
								
								
									
										6
									
								
								.github/CONTRIBUTING.md
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -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.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -217,5 +217,3 @@ gradle-app.setting
 | 
				
			|||||||
release/
 | 
					release/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
crowdin.properties
 | 
					crowdin.properties
 | 
				
			||||||
 | 
					 | 
				
			||||||
publish-version.sh
 | 
					 | 
				
			||||||
							
								
								
									
										30
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						@@ -1,3 +1,31 @@
 | 
				
			|||||||
 | 
					**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 #33. Background sync with settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #1. Initial article caching.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #228 by removing the list action bar. Action buttons are exclusively on the card view from now on.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #38. Only doing api calls on network available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**1.6.x**
 | 
					**1.6.x**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Handling hidden tags.
 | 
					- Handling hidden tags.
 | 
				
			||||||
@@ -16,6 +44,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
- Versions updates.
 | 
					- Versions updates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixes #215, #208.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**1.5.7.x**
 | 
					**1.5.7.x**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added confirmation to the mark as read and update menues.
 | 
					- Added confirmation to the mark as read and update menues.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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() {
 | 
				
			||||||
@@ -24,6 +24,8 @@ def versionNameFromGit() {
 | 
				
			|||||||
    return gitVersion()
 | 
					    return gitVersion()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					apply plugin: 'kotlin-kapt'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply plugin: 'com.android.application'
 | 
					apply plugin: 'com.android.application'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply plugin: 'kotlin-android'
 | 
					apply plugin: 'kotlin-android'
 | 
				
			||||||
@@ -36,11 +38,11 @@ android {
 | 
				
			|||||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
					        targetCompatibility JavaVersion.VERSION_1_8
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    compileSdkVersion 28
 | 
					    compileSdkVersion 28
 | 
				
			||||||
    buildToolsVersion '28.0.1'
 | 
					    buildToolsVersion '28.0.3'
 | 
				
			||||||
    defaultConfig {
 | 
					    defaultConfig {
 | 
				
			||||||
        applicationId "apps.amine.bou.readerforselfoss"
 | 
					        applicationId "apps.amine.bou.readerforselfoss"
 | 
				
			||||||
        minSdkVersion 16
 | 
					        minSdkVersion 16
 | 
				
			||||||
        targetSdkVersion 27
 | 
					        targetSdkVersion 28
 | 
				
			||||||
        versionCode versionCodeFromGit()
 | 
					        versionCode versionCodeFromGit()
 | 
				
			||||||
        versionName versionNameFromGit()
 | 
					        versionName versionNameFromGit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,12 +55,19 @@ android {
 | 
				
			|||||||
        vectorDrawables.useSupportLibrary = true
 | 
					        vectorDrawables.useSupportLibrary = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // tests
 | 
					        // tests
 | 
				
			||||||
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
 | 
					        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        javaCompileOptions {
 | 
				
			||||||
 | 
					            annotationProcessorOptions {
 | 
				
			||||||
 | 
					                arguments = ["room.schemaLocation":
 | 
				
			||||||
 | 
					                                     "$projectDir/schemas".toString()]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    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'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -84,32 +93,29 @@ android {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					dependencies {
 | 
				
			||||||
    // Testing
 | 
					    // Testing
 | 
				
			||||||
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
 | 
					    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-beta02'
 | 
				
			||||||
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
 | 
					    androidTestImplementation 'androidx.test:runner:1.1.0-beta02'
 | 
				
			||||||
    // Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
 | 
					    // Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
 | 
				
			||||||
    androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.1'
 | 
					    androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0-beta02'
 | 
				
			||||||
    // Espresso-intents for validation and stubbing of Intents
 | 
					    // Espresso-intents for validation and stubbing of Intents
 | 
				
			||||||
    androidTestImplementation 'com.android.support.test.espresso:espresso-intents:3.0.1'
 | 
					    androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0-beta02'
 | 
				
			||||||
    implementation fileTree(include: ['*.jar'], dir: 'libs')
 | 
					    implementation fileTree(include: ['*.jar'], dir: 'libs')
 | 
				
			||||||
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
 | 
					    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
 | 
				
			||||||
    // Android Support
 | 
					    // Android Support
 | 
				
			||||||
    implementation 'com.android.support:appcompat-v7:27.1.1'
 | 
					    implementation "androidx.appcompat:appcompat:$android_version"
 | 
				
			||||||
    implementation 'com.android.support:design:27.1.1'
 | 
					    implementation "com.google.android.material:material:$android_version"
 | 
				
			||||||
    implementation 'com.android.support:recyclerview-v7:27.1.1'
 | 
					    implementation "androidx.recyclerview:recyclerview:$android_version"
 | 
				
			||||||
    implementation 'com.android.support:support-v4:27.1.1'
 | 
					    implementation "androidx.legacy:legacy-support-v4:$android_version"
 | 
				
			||||||
    implementation 'com.android.support:support-vector-drawable:27.1.1'
 | 
					    implementation "androidx.vectordrawable:vectordrawable:$android_version"
 | 
				
			||||||
    implementation 'com.android.support:customtabs:27.1.1'
 | 
					    implementation "androidx.browser:browser:$android_version"
 | 
				
			||||||
    implementation 'com.android.support:cardview-v7:27.1.1'
 | 
					    implementation "androidx.cardview:cardview:$android_version"
 | 
				
			||||||
    implementation 'com.android.support.constraint:constraint-layout:1.1.0'
 | 
					    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //multidex
 | 
					    //multidex
 | 
				
			||||||
    implementation 'com.android.support:multidex:1.0.3'
 | 
					    implementation 'androidx.multidex:multidex:2.0.0'
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Intro
 | 
					 | 
				
			||||||
    implementation 'agency.tango.android:material-intro-screen:0.0.5'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // About
 | 
					    // About
 | 
				
			||||||
    implementation('com.mikepenz:aboutlibraries:6.0.0@aar') {
 | 
					    implementation('com.mikepenz:aboutlibraries:6.2.0@aar') {
 | 
				
			||||||
        transitive = true
 | 
					        transitive = true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -120,8 +126,8 @@ dependencies {
 | 
				
			|||||||
    implementation 'com.burgstaller:okhttp-digest:1.12'
 | 
					    implementation 'com.burgstaller:okhttp-digest:1.12'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Material-ish things
 | 
					    // Material-ish things
 | 
				
			||||||
    implementation 'com.ashokvarma.android:bottom-navigation-bar:2.0.3'
 | 
					    implementation 'com.ashokvarma.android:bottom-navigation-bar:2.0.5'
 | 
				
			||||||
    implementation 'com.github.jd-alexander:LikeButton:0.2.1'
 | 
					    implementation 'com.github.jd-alexander:LikeButton:0.2.3'
 | 
				
			||||||
    implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
 | 
					    implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // glide
 | 
					    // glide
 | 
				
			||||||
@@ -129,25 +135,32 @@ dependencies {
 | 
				
			|||||||
    implementation 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
 | 
					    implementation 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Asking politely users to rate the app
 | 
					    // Asking politely users to rate the app
 | 
				
			||||||
    implementation 'com.github.stkent:amplify:2.1.0'
 | 
					    implementation 'com.github.stkent:amplify:2.2.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Drawer
 | 
					    // Drawer
 | 
				
			||||||
    implementation 'co.zsmb:materialdrawer-kt:1.3.5'
 | 
					    implementation 'co.zsmb:materialdrawer-kt:2.0.1'
 | 
				
			||||||
    implementation 'com.anupcowkur:reservoir:3.1.0'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Themes
 | 
					    // Themes
 | 
				
			||||||
    implementation 'com.52inc:scoops:1.0.0'
 | 
					    implementation 'com.52inc:scoops:1.0.0'
 | 
				
			||||||
    implementation 'com.jrummyapps:colorpicker:2.1.7'
 | 
					    implementation 'com.jaredrummler:colorpicker:1.0.2'
 | 
				
			||||||
    implementation 'com.github.rubensousa:floatingtoolbar:1.5.1'
 | 
					    implementation 'com.github.rubensousa:floatingtoolbar:1.5.1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Pager
 | 
					    // Pager
 | 
				
			||||||
    implementation 'me.relex:circleindicator:1.2.2@aar'
 | 
					    implementation 'me.relex:circleindicator:2.0.0@aar'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    implementation 'androidx.core:core-ktx:0.3'
 | 
					    implementation 'androidx.core:core-ktx:1.0.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Crash
 | 
					    // Crash
 | 
				
			||||||
    implementation 'ch.acra:acra-http:5.1.3'
 | 
					    implementation 'ch.acra:acra-http:5.2.1'
 | 
				
			||||||
    implementation 'ch.acra:acra-dialog:5.1.3'
 | 
					    implementation 'ch.acra:acra-dialog:5.2.1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
 | 
				
			||||||
 | 
					    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    implementation "androidx.room:room-runtime:$room_version"
 | 
				
			||||||
 | 
					    kapt "androidx.room:room-compiler:$room_version"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    implementation "android.arch.work:work-runtime-ktx:$work_version"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -30,22 +30,6 @@
 | 
				
			|||||||
    <fields>;
 | 
					    <fields>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
##Retrofit
 | 
					 | 
				
			||||||
#-keep class com.google.gson.** { *; }
 | 
					 | 
				
			||||||
#-keep class com.google.inject.** { *; }
 | 
					 | 
				
			||||||
#-keep class org.apache.http.** { *; }
 | 
					 | 
				
			||||||
#-keep class org.apache.james.mime4j.** { *; }
 | 
					 | 
				
			||||||
#-keep class javax.inject.** { *; }
 | 
					 | 
				
			||||||
#-keep class retrofit.** { *; }
 | 
					 | 
				
			||||||
#-keepclassmembernames interface * {
 | 
					 | 
				
			||||||
#    @retrofit.http.* <methods>;
 | 
					 | 
				
			||||||
#}
 | 
					 | 
				
			||||||
#-keep class retrofit.** { *; }
 | 
					 | 
				
			||||||
#-keep class apps.amine.bou.readerforselfoss.api.selfoss.model.** { *; }
 | 
					 | 
				
			||||||
#-keepclassmembernames interface * {
 | 
					 | 
				
			||||||
#    @retrofit.http.* <methods>;
 | 
					 | 
				
			||||||
#}
 | 
					 | 
				
			||||||
-dontwarn okio.**
 | 
					-dontwarn okio.**
 | 
				
			||||||
-dontwarn retrofit2.Platform$Java8
 | 
					-dontwarn retrofit2.Platform$Java8
 | 
				
			||||||
-keep class retrofit.** { *; }
 | 
					-keep class retrofit.** { *; }
 | 
				
			||||||
@@ -76,3 +60,6 @@
 | 
				
			|||||||
-dontwarn javax.annotation.**
 | 
					-dontwarn javax.annotation.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-keep class android.support.v7.widget.SearchView { *; }
 | 
					-keep class android.support.v7.widget.SearchView { *; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# maybe remove later ?
 | 
				
			||||||
 | 
					-keep class * extends androidx.fragment.app.Fragment
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "formatVersion": 1,
 | 
				
			||||||
 | 
					  "database": {
 | 
				
			||||||
 | 
					    "version": 1,
 | 
				
			||||||
 | 
					    "identityHash": "08ca537d7ac9d4dd216e8e395d70801a",
 | 
				
			||||||
 | 
					    "entities": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "tags",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tag",
 | 
				
			||||||
 | 
					            "columnName": "tag",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "color",
 | 
				
			||||||
 | 
					            "columnName": "color",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "tag"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "sources",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "title",
 | 
				
			||||||
 | 
					            "columnName": "title",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tags",
 | 
				
			||||||
 | 
					            "columnName": "tags",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "spout",
 | 
				
			||||||
 | 
					            "columnName": "spout",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "error",
 | 
				
			||||||
 | 
					            "columnName": "error",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "icon",
 | 
				
			||||||
 | 
					            "columnName": "icon",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "views": [],
 | 
				
			||||||
 | 
					    "setupQueries": [
 | 
				
			||||||
 | 
					      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
 | 
				
			||||||
 | 
					      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"08ca537d7ac9d4dd216e8e395d70801a\")"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,176 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "formatVersion": 1,
 | 
				
			||||||
 | 
					  "database": {
 | 
				
			||||||
 | 
					    "version": 2,
 | 
				
			||||||
 | 
					    "identityHash": "6fa6944b04100d68eab61039876a8804",
 | 
				
			||||||
 | 
					    "entities": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "tags",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tag",
 | 
				
			||||||
 | 
					            "columnName": "tag",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "color",
 | 
				
			||||||
 | 
					            "columnName": "color",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "tag"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "sources",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "title",
 | 
				
			||||||
 | 
					            "columnName": "title",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tags",
 | 
				
			||||||
 | 
					            "columnName": "tags",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "spout",
 | 
				
			||||||
 | 
					            "columnName": "spout",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "error",
 | 
				
			||||||
 | 
					            "columnName": "error",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "icon",
 | 
				
			||||||
 | 
					            "columnName": "icon",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "items",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "datetime",
 | 
				
			||||||
 | 
					            "columnName": "datetime",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "title",
 | 
				
			||||||
 | 
					            "columnName": "title",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "content",
 | 
				
			||||||
 | 
					            "columnName": "content",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "starred",
 | 
				
			||||||
 | 
					            "columnName": "starred",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "thumbnail",
 | 
				
			||||||
 | 
					            "columnName": "thumbnail",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "icon",
 | 
				
			||||||
 | 
					            "columnName": "icon",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "link",
 | 
				
			||||||
 | 
					            "columnName": "link",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "sourcetitle",
 | 
				
			||||||
 | 
					            "columnName": "sourcetitle",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tags",
 | 
				
			||||||
 | 
					            "columnName": "tags",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "views": [],
 | 
				
			||||||
 | 
					    "setupQueries": [
 | 
				
			||||||
 | 
					      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
 | 
				
			||||||
 | 
					      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6fa6944b04100d68eab61039876a8804\")"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,226 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "formatVersion": 1,
 | 
				
			||||||
 | 
					  "database": {
 | 
				
			||||||
 | 
					    "version": 3,
 | 
				
			||||||
 | 
					    "identityHash": "7ad9c4961992c13b670128485ebb3efc",
 | 
				
			||||||
 | 
					    "entities": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "tags",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tag",
 | 
				
			||||||
 | 
					            "columnName": "tag",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "color",
 | 
				
			||||||
 | 
					            "columnName": "color",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "tag"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "sources",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "title",
 | 
				
			||||||
 | 
					            "columnName": "title",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tags",
 | 
				
			||||||
 | 
					            "columnName": "tags",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "spout",
 | 
				
			||||||
 | 
					            "columnName": "spout",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "error",
 | 
				
			||||||
 | 
					            "columnName": "error",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "icon",
 | 
				
			||||||
 | 
					            "columnName": "icon",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "items",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "datetime",
 | 
				
			||||||
 | 
					            "columnName": "datetime",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "title",
 | 
				
			||||||
 | 
					            "columnName": "title",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "content",
 | 
				
			||||||
 | 
					            "columnName": "content",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "starred",
 | 
				
			||||||
 | 
					            "columnName": "starred",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "thumbnail",
 | 
				
			||||||
 | 
					            "columnName": "thumbnail",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "icon",
 | 
				
			||||||
 | 
					            "columnName": "icon",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "link",
 | 
				
			||||||
 | 
					            "columnName": "link",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "sourcetitle",
 | 
				
			||||||
 | 
					            "columnName": "sourcetitle",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tags",
 | 
				
			||||||
 | 
					            "columnName": "tags",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "actions",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL)",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "articleId",
 | 
				
			||||||
 | 
					            "columnName": "articleid",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "read",
 | 
				
			||||||
 | 
					            "columnName": "read",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "starred",
 | 
				
			||||||
 | 
					            "columnName": "starred",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unstarred",
 | 
				
			||||||
 | 
					            "columnName": "unstarred",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "views": [],
 | 
				
			||||||
 | 
					    "setupQueries": [
 | 
				
			||||||
 | 
					      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
 | 
				
			||||||
 | 
					      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"7ad9c4961992c13b670128485ebb3efc\")"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,27 +2,27 @@ package apps.amine.bou.readerforselfoss
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.support.test.InstrumentationRegistry
 | 
					import androidx.test.InstrumentationRegistry
 | 
				
			||||||
import android.support.test.espresso.Espresso.onView
 | 
					import androidx.test.espresso.Espresso.onView
 | 
				
			||||||
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
 | 
					import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
 | 
				
			||||||
import android.support.test.espresso.action.ViewActions.click
 | 
					import androidx.test.espresso.action.ViewActions.click
 | 
				
			||||||
import android.support.test.espresso.action.ViewActions.closeSoftKeyboard
 | 
					import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
 | 
				
			||||||
import android.support.test.espresso.action.ViewActions.pressBack
 | 
					import androidx.test.espresso.action.ViewActions.pressBack
 | 
				
			||||||
import android.support.test.espresso.action.ViewActions.pressKey
 | 
					import androidx.test.espresso.action.ViewActions.pressKey
 | 
				
			||||||
import android.support.test.espresso.action.ViewActions.typeText
 | 
					import androidx.test.espresso.action.ViewActions.typeText
 | 
				
			||||||
import android.support.test.espresso.assertion.ViewAssertions.matches
 | 
					import androidx.test.espresso.assertion.ViewAssertions.matches
 | 
				
			||||||
import android.support.test.espresso.contrib.DrawerActions
 | 
					import androidx.test.espresso.contrib.DrawerActions
 | 
				
			||||||
import android.support.test.espresso.intent.Intents
 | 
					import androidx.test.espresso.intent.Intents
 | 
				
			||||||
import android.support.test.espresso.intent.Intents.intended
 | 
					import androidx.test.espresso.intent.Intents.intended
 | 
				
			||||||
import android.support.test.espresso.intent.Intents.times
 | 
					import androidx.test.espresso.intent.Intents.times
 | 
				
			||||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
 | 
					import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
 | 
					import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.isRoot
 | 
					import androidx.test.espresso.matcher.ViewMatchers.isRoot
 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.withContentDescription
 | 
					import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.withId
 | 
					import androidx.test.espresso.matcher.ViewMatchers.withId
 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.withText
 | 
					import androidx.test.espresso.matcher.ViewMatchers.withText
 | 
				
			||||||
import android.support.test.rule.ActivityTestRule
 | 
					import androidx.test.rule.ActivityTestRule
 | 
				
			||||||
import android.support.test.runner.AndroidJUnit4
 | 
					import androidx.test.runner.AndroidJUnit4
 | 
				
			||||||
import android.view.KeyEvent
 | 
					import android.view.KeyEvent
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.Config
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
import org.junit.After
 | 
					import org.junit.After
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,91 +0,0 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.content.Context
 | 
					 | 
				
			||||||
import android.content.Intent
 | 
					 | 
				
			||||||
import android.support.test.InstrumentationRegistry.getInstrumentation
 | 
					 | 
				
			||||||
import android.support.test.espresso.Espresso.onView
 | 
					 | 
				
			||||||
import android.support.test.espresso.action.ViewActions.click
 | 
					 | 
				
			||||||
import android.support.test.espresso.assertion.ViewAssertions.matches
 | 
					 | 
				
			||||||
import android.support.test.espresso.intent.Intents
 | 
					 | 
				
			||||||
import android.support.test.espresso.intent.Intents.intended
 | 
					 | 
				
			||||||
import android.support.test.espresso.intent.Intents.times
 | 
					 | 
				
			||||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
 | 
					 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
 | 
					 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.withId
 | 
					 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.withText
 | 
					 | 
				
			||||||
import android.support.test.rule.ActivityTestRule
 | 
					 | 
				
			||||||
import android.support.test.runner.AndroidJUnit4
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.Config
 | 
					 | 
				
			||||||
import org.junit.After
 | 
					 | 
				
			||||||
import org.junit.Before
 | 
					 | 
				
			||||||
import org.junit.Rule
 | 
					 | 
				
			||||||
import org.junit.Test
 | 
					 | 
				
			||||||
import org.junit.runner.RunWith
 | 
					 | 
				
			||||||
import java.util.*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@RunWith(AndroidJUnit4::class)
 | 
					 | 
				
			||||||
class IntroActivityEspressoTest {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Rule @JvmField
 | 
					 | 
				
			||||||
    val rule = ActivityTestRule(IntroActivity::class.java, true, false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Before
 | 
					 | 
				
			||||||
    fun clearData() {
 | 
					 | 
				
			||||||
        val editor =
 | 
					 | 
				
			||||||
                getInstrumentation().targetContext
 | 
					 | 
				
			||||||
                        .getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
					 | 
				
			||||||
                        .edit()
 | 
					 | 
				
			||||||
        editor.clear()
 | 
					 | 
				
			||||||
        editor.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Intents.init()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Test
 | 
					 | 
				
			||||||
    fun nextEachTimes() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        rule.launchActivity(Intent())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
 | 
					 | 
				
			||||||
        onView(withId(R.id.button_next)).perform(click())
 | 
					 | 
				
			||||||
        onView(withText(R.string.intro_needs_selfoss_message)).check(matches(isDisplayed()))
 | 
					 | 
				
			||||||
        onView(withId(R.id.button_next)).perform(click())
 | 
					 | 
				
			||||||
        onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
 | 
					 | 
				
			||||||
        onView(withId(R.id.button_next)).perform(click())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        intended(hasComponent(IntroActivity::class.java.name), times(1))
 | 
					 | 
				
			||||||
        intended(hasComponent(LoginActivity::class.java.name), times(1))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Test
 | 
					 | 
				
			||||||
    fun nextBackRandomTimes() {
 | 
					 | 
				
			||||||
        val max = 5
 | 
					 | 
				
			||||||
        val min = 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        val random = (Random().nextInt(max + 1 - min)) + min
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        rule.launchActivity(Intent())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
 | 
					 | 
				
			||||||
        onView(withId(R.id.button_next)).perform(click())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        repeat(random) { _ ->
 | 
					 | 
				
			||||||
            onView(withText(R.string.intro_needs_selfoss_message)).check(matches(isDisplayed()))
 | 
					 | 
				
			||||||
            onView(withId(R.id.button_next)).perform(click())
 | 
					 | 
				
			||||||
            onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
 | 
					 | 
				
			||||||
            onView(withId(R.id.button_back)).perform(click())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        onView(withId(R.id.button_next)).perform(click())
 | 
					 | 
				
			||||||
        onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
 | 
					 | 
				
			||||||
        onView(withId(R.id.button_next)).perform(click())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        intended(hasComponent(IntroActivity::class.java.name), times(1))
 | 
					 | 
				
			||||||
        intended(hasComponent(LoginActivity::class.java.name), times(1))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @After
 | 
					 | 
				
			||||||
    fun releaseIntents() {
 | 
					 | 
				
			||||||
        Intents.release()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -2,25 +2,25 @@ package apps.amine.bou.readerforselfoss
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.support.test.InstrumentationRegistry
 | 
					import androidx.test.InstrumentationRegistry
 | 
				
			||||||
import android.support.test.espresso.Espresso.onView
 | 
					import androidx.test.espresso.Espresso.onView
 | 
				
			||||||
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
 | 
					import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
 | 
				
			||||||
import android.support.test.espresso.action.ViewActions.click
 | 
					import androidx.test.espresso.action.ViewActions.click
 | 
				
			||||||
import android.support.test.espresso.action.ViewActions.closeSoftKeyboard
 | 
					import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
 | 
				
			||||||
import android.support.test.espresso.action.ViewActions.pressBack
 | 
					import androidx.test.espresso.action.ViewActions.pressBack
 | 
				
			||||||
import android.support.test.espresso.action.ViewActions.typeText
 | 
					import androidx.test.espresso.action.ViewActions.typeText
 | 
				
			||||||
import android.support.test.espresso.assertion.ViewAssertions.matches
 | 
					import androidx.test.espresso.assertion.ViewAssertions.matches
 | 
				
			||||||
import android.support.test.espresso.intent.Intents
 | 
					import androidx.test.espresso.intent.Intents
 | 
				
			||||||
import android.support.test.espresso.intent.Intents.intended
 | 
					import androidx.test.espresso.intent.Intents.intended
 | 
				
			||||||
import android.support.test.espresso.intent.Intents.times
 | 
					import androidx.test.espresso.intent.Intents.times
 | 
				
			||||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
 | 
					import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers
 | 
					import androidx.test.espresso.matcher.ViewMatchers
 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.isRoot
 | 
					import androidx.test.espresso.matcher.ViewMatchers.isRoot
 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
 | 
					import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.withId
 | 
					import androidx.test.espresso.matcher.ViewMatchers.withId
 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers.withText
 | 
					import androidx.test.espresso.matcher.ViewMatchers.withText
 | 
				
			||||||
import android.support.test.rule.ActivityTestRule
 | 
					import androidx.test.rule.ActivityTestRule
 | 
				
			||||||
import android.support.test.runner.AndroidJUnit4
 | 
					import androidx.test.runner.AndroidJUnit4
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.Config
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
import com.mikepenz.aboutlibraries.ui.LibsActivity
 | 
					import com.mikepenz.aboutlibraries.ui.LibsActivity
 | 
				
			||||||
import org.junit.After
 | 
					import org.junit.After
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,13 +3,13 @@ package apps.amine.bou.readerforselfoss
 | 
				
			|||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.content.SharedPreferences
 | 
					import android.content.SharedPreferences
 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					import android.preference.PreferenceManager
 | 
				
			||||||
import android.support.test.InstrumentationRegistry.getInstrumentation
 | 
					import androidx.test.InstrumentationRegistry.getInstrumentation
 | 
				
			||||||
import android.support.test.espresso.intent.Intents
 | 
					import androidx.test.espresso.intent.Intents
 | 
				
			||||||
import android.support.test.espresso.intent.Intents.intended
 | 
					import androidx.test.espresso.intent.Intents.intended
 | 
				
			||||||
import android.support.test.espresso.intent.Intents.times
 | 
					import androidx.test.espresso.intent.Intents.times
 | 
				
			||||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
 | 
					import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
 | 
				
			||||||
import android.support.test.rule.ActivityTestRule
 | 
					import androidx.test.rule.ActivityTestRule
 | 
				
			||||||
import android.support.test.runner.AndroidJUnit4
 | 
					import androidx.test.runner.AndroidJUnit4
 | 
				
			||||||
import org.junit.After
 | 
					import org.junit.After
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.junit.Before
 | 
					import org.junit.Before
 | 
				
			||||||
@@ -45,7 +45,6 @@ class MainActivityEspressoTest {
 | 
				
			|||||||
        rule.launchActivity(intent)
 | 
					        rule.launchActivity(intent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        intended(hasComponent(MainActivity::class.java.name))
 | 
					        intended(hasComponent(MainActivity::class.java.name))
 | 
				
			||||||
        intended(hasComponent(IntroActivity::class.java.name))
 | 
					 | 
				
			||||||
        intended(hasComponent(LoginActivity::class.java.name), times(0))
 | 
					        intended(hasComponent(LoginActivity::class.java.name), times(0))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,7 +57,6 @@ class MainActivityEspressoTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        intended(hasComponent(MainActivity::class.java.name))
 | 
					        intended(hasComponent(MainActivity::class.java.name))
 | 
				
			||||||
        intended(hasComponent(LoginActivity::class.java.name))
 | 
					        intended(hasComponent(LoginActivity::class.java.name))
 | 
				
			||||||
        intended(hasComponent(IntroActivity::class.java.name), times(0))
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @After
 | 
					    @After
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss
 | 
					package apps.amine.bou.readerforselfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.support.design.widget.TextInputLayout
 | 
					import com.google.android.material.textfield.TextInputLayout
 | 
				
			||||||
import android.support.test.espresso.matcher.ViewMatchers
 | 
					import androidx.test.espresso.matcher.ViewMatchers
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
import org.hamcrest.Description
 | 
					import org.hamcrest.Description
 | 
				
			||||||
import org.hamcrest.Matcher
 | 
					import org.hamcrest.Matcher
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
<?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.INTERNET" />
 | 
					    <uses-permission android:name="android.permission.INTERNET" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <application
 | 
					    <application
 | 
				
			||||||
@@ -11,19 +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>
 | 
				
			||||||
        </activity>
 | 
					
 | 
				
			||||||
        <activity
 | 
					            <meta-data
 | 
				
			||||||
            android:name=".IntroActivity"
 | 
					                android:name="android.app.shortcuts"
 | 
				
			||||||
            android:theme="@style/Theme.Intro">
 | 
					                android:resource="@xml/shortcuts" />
 | 
				
			||||||
        </activity>
 | 
					        </activity>
 | 
				
			||||||
        <activity
 | 
					        <activity
 | 
				
			||||||
            android:name=".LoginActivity"
 | 
					            android:name=".LoginActivity"
 | 
				
			||||||
@@ -37,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"
 | 
				
			||||||
@@ -55,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>
 | 
				
			||||||
@@ -76,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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,8 @@ import android.content.Intent
 | 
				
			|||||||
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.support.constraint.ConstraintLayout
 | 
					import androidx.constraintlayout.widget.ConstraintLayout
 | 
				
			||||||
import android.support.v7.app.AppCompatActivity
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
import android.widget.AdapterView
 | 
					import android.widget.AdapterView
 | 
				
			||||||
import android.widget.ArrayAdapter
 | 
					import android.widget.ArrayAdapter
 | 
				
			||||||
@@ -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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,29 +9,38 @@ import android.net.Uri
 | 
				
			|||||||
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.support.v4.view.MenuItemCompat
 | 
					import androidx.core.view.MenuItemCompat
 | 
				
			||||||
import android.support.v7.app.AlertDialog
 | 
					import androidx.appcompat.app.AlertDialog
 | 
				
			||||||
import android.support.v7.app.AppCompatActivity
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
import android.support.v7.widget.DividerItemDecoration
 | 
					import androidx.recyclerview.widget.DividerItemDecoration
 | 
				
			||||||
import android.support.v7.widget.GridLayoutManager
 | 
					import androidx.recyclerview.widget.GridLayoutManager
 | 
				
			||||||
import android.support.v7.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import android.support.v7.widget.SearchView
 | 
					import androidx.appcompat.widget.SearchView
 | 
				
			||||||
import android.support.v7.widget.StaggeredGridLayoutManager
 | 
					import androidx.recyclerview.widget.StaggeredGridLayoutManager
 | 
				
			||||||
import android.support.v7.widget.helper.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.work.Constraints
 | 
				
			||||||
 | 
					import androidx.work.ExistingPeriodicWorkPolicy
 | 
				
			||||||
 | 
					import androidx.work.PeriodicWorkRequestBuilder
 | 
				
			||||||
 | 
					import androidx.work.WorkManager
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.adapters.ItemCardAdapter
 | 
					import apps.amine.bou.readerforselfoss.adapters.ItemCardAdapter
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.adapters.ItemListAdapter
 | 
					import apps.amine.bou.readerforselfoss.adapters.ItemListAdapter
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.adapters.ItemsAdapter
 | 
					import apps.amine.bou.readerforselfoss.adapters.ItemsAdapter
 | 
				
			||||||
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
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Source
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Stats
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Stats
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Tag
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Tag
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.background.LoadingWorker
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.settings.SettingsActivity
 | 
					import apps.amine.bou.readerforselfoss.settings.SettingsActivity
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
					import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.Toppings
 | 
					import apps.amine.bou.readerforselfoss.themes.Toppings
 | 
				
			||||||
@@ -43,20 +52,19 @@ import apps.amine.bou.readerforselfoss.utils.drawer.CustomUrlPrimaryDrawerItem
 | 
				
			|||||||
import apps.amine.bou.readerforselfoss.utils.flattenTags
 | 
					import apps.amine.bou.readerforselfoss.utils.flattenTags
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.longHash
 | 
					import apps.amine.bou.readerforselfoss.utils.longHash
 | 
				
			||||||
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.persistence.toEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.persistence.toView
 | 
				
			||||||
import co.zsmb.materialdrawerkt.builders.accountHeader
 | 
					import co.zsmb.materialdrawerkt.builders.accountHeader
 | 
				
			||||||
import co.zsmb.materialdrawerkt.builders.drawer
 | 
					import co.zsmb.materialdrawerkt.builders.drawer
 | 
				
			||||||
import co.zsmb.materialdrawerkt.builders.footer
 | 
					import co.zsmb.materialdrawerkt.builders.footer
 | 
				
			||||||
import co.zsmb.materialdrawerkt.draweritems.badgeable.primaryItem
 | 
					import co.zsmb.materialdrawerkt.draweritems.badgeable.primaryItem
 | 
				
			||||||
import co.zsmb.materialdrawerkt.draweritems.profile.profile
 | 
					import co.zsmb.materialdrawerkt.draweritems.profile.profile
 | 
				
			||||||
import com.anupcowkur.reservoir.Reservoir
 | 
					 | 
				
			||||||
import com.anupcowkur.reservoir.ReservoirGetCallback
 | 
					 | 
				
			||||||
import com.anupcowkur.reservoir.ReservoirPutCallback
 | 
					 | 
				
			||||||
import com.ashokvarma.bottomnavigation.BottomNavigationBar
 | 
					import com.ashokvarma.bottomnavigation.BottomNavigationBar
 | 
				
			||||||
import com.ashokvarma.bottomnavigation.BottomNavigationItem
 | 
					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
 | 
				
			||||||
@@ -70,6 +78,9 @@ import org.acra.ACRA
 | 
				
			|||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
import retrofit2.Callback
 | 
					import retrofit2.Callback
 | 
				
			||||||
import retrofit2.Response
 | 
					import retrofit2.Response
 | 
				
			||||||
 | 
					import java.util.concurrent.TimeUnit
 | 
				
			||||||
 | 
					import kotlin.concurrent.thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
					class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val MENU_PREFERENCES = 12302
 | 
					    private val MENU_PREFERENCES = 12302
 | 
				
			||||||
@@ -84,7 +95,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
    private var items: ArrayList<Item> = ArrayList()
 | 
					    private var items: ArrayList<Item> = ArrayList()
 | 
				
			||||||
    private var allItems: ArrayList<Item> = ArrayList()
 | 
					    private var allItems: ArrayList<Item> = ArrayList()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var clickBehavior = false
 | 
					 | 
				
			||||||
    private var debugReadingItems = false
 | 
					    private var debugReadingItems = false
 | 
				
			||||||
    private var shouldLogEverything = false
 | 
					    private var shouldLogEverything = false
 | 
				
			||||||
    private var internalBrowser = false
 | 
					    private var internalBrowser = false
 | 
				
			||||||
@@ -96,14 +106,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
    private var itemsNumber: Int = 200
 | 
					    private var itemsNumber: Int = 200
 | 
				
			||||||
    private var elementsShown: Int = 0
 | 
					    private var elementsShown: Int = 0
 | 
				
			||||||
    private var maybeTagFilter: Tag? = null
 | 
					    private var maybeTagFilter: Tag? = null
 | 
				
			||||||
    private var maybeSourceFilter: Sources? = null
 | 
					    private var maybeSourceFilter: Source? = null
 | 
				
			||||||
    private var maybeSearchFilter: String? = null
 | 
					    private var maybeSearchFilter: String? = null
 | 
				
			||||||
    private var userIdentifier: String = ""
 | 
					    private var userIdentifier: String = ""
 | 
				
			||||||
    private var displayAccountHeader: Boolean = false
 | 
					    private var displayAccountHeader: Boolean = false
 | 
				
			||||||
    private var infiniteScroll: Boolean = false
 | 
					    private var infiniteScroll: Boolean = false
 | 
				
			||||||
    private var lastFetchDone: Boolean = false
 | 
					    private var lastFetchDone: Boolean = false
 | 
				
			||||||
 | 
					    private var itemsCaching: Boolean = false
 | 
				
			||||||
    private var hiddenTags: List<String> = emptyList()
 | 
					    private var hiddenTags: List<String> = emptyList()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var periodicRefresh = false
 | 
				
			||||||
 | 
					    private var refreshMinutes: Long = 360L
 | 
				
			||||||
 | 
					    private var refreshWhenChargingOnly = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private lateinit var tabNewBadge: TextBadgeItem
 | 
					    private lateinit var tabNewBadge: TextBadgeItem
 | 
				
			||||||
    private lateinit var tabArchiveBadge: TextBadgeItem
 | 
					    private lateinit var tabArchiveBadge: TextBadgeItem
 | 
				
			||||||
@@ -116,7 +130,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
    private lateinit var appColors: AppColors
 | 
					    private lateinit var appColors: AppColors
 | 
				
			||||||
    private var offset: Int = 0
 | 
					    private var offset: Int = 0
 | 
				
			||||||
    private var firstVisible: Int = 0
 | 
					    private var firstVisible: Int = 0
 | 
				
			||||||
    private var recyclerViewScrollListener: RecyclerView.OnScrollListener? = null
 | 
					    private lateinit var recyclerViewScrollListener: RecyclerView.OnScrollListener
 | 
				
			||||||
    private lateinit var settings: SharedPreferences
 | 
					    private lateinit var settings: SharedPreferences
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var recyclerAdapter: RecyclerView.Adapter<*>? = null
 | 
					    private var recyclerAdapter: RecyclerView.Adapter<*>? = null
 | 
				
			||||||
@@ -125,10 +139,16 @@ 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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data class DrawerData(val tags: List<Tag>?, val sources: List<Sources>?)
 | 
					    private lateinit var db: AppDatabase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lateinit var config: Config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data class DrawerData(val tags: List<Tag>?, val sources: List<Source>?)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onStart() {
 | 
					    override fun onStart() {
 | 
				
			||||||
        super.onStart()
 | 
					        super.onStart()
 | 
				
			||||||
@@ -137,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()
 | 
				
			||||||
@@ -149,6 +177,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
            Amplify.getSharedInstance().promptIfReady(promptView)
 | 
					            Amplify.getSharedInstance().promptIfReady(promptView)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        db = Room.databaseBuilder(
 | 
				
			||||||
 | 
					            applicationContext,
 | 
				
			||||||
 | 
					            AppDatabase::class.java, "selfoss-database"
 | 
				
			||||||
 | 
					        ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        customTabActivityHelper = CustomTabActivityHelper()
 | 
					        customTabActivityHelper = CustomTabActivityHelper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
 | 
					        sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
 | 
				
			||||||
@@ -158,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()
 | 
				
			||||||
@@ -169,30 +204,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
        handleSwipeRefreshLayout()
 | 
					        handleSwipeRefreshLayout()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun handleGDPRDialog(GDPRShown: Boolean) {
 | 
					 | 
				
			||||||
        val sharedEditor = sharedPref.edit()
 | 
					 | 
				
			||||||
        if (!GDPRShown) {
 | 
					 | 
				
			||||||
            val alertDialog = AlertDialog.Builder(this).create()
 | 
					 | 
				
			||||||
            alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
 | 
					 | 
				
			||||||
            alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
 | 
					 | 
				
			||||||
            alertDialog.setButton(
 | 
					 | 
				
			||||||
                AlertDialog.BUTTON_NEUTRAL,
 | 
					 | 
				
			||||||
                "OK",
 | 
					 | 
				
			||||||
                { dialog, _ ->
 | 
					 | 
				
			||||||
                    sharedEditor.putBoolean("GDPR_shown", true)
 | 
					 | 
				
			||||||
                    sharedEditor.commit()
 | 
					 | 
				
			||||||
                    dialog.dismiss()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            alertDialog.show()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (sharedPref.getString("acra.user.email", "").isNotEmpty()) {
 | 
					 | 
				
			||||||
            sharedEditor.remove("acra.user.email")
 | 
					 | 
				
			||||||
            sharedEditor.commit()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private fun handleSwipeRefreshLayout() {
 | 
					    private fun handleSwipeRefreshLayout() {
 | 
				
			||||||
        swipeRefreshLayout.setColorSchemeResources(
 | 
					        swipeRefreshLayout.setColorSchemeResources(
 | 
				
			||||||
            R.color.refresh_progress_1,
 | 
					            R.color.refresh_progress_1,
 | 
				
			||||||
@@ -200,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()
 | 
				
			||||||
@@ -212,10 +224,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
 | 
					                ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                override fun getSwipeDirs(
 | 
					                override fun getSwipeDirs(
 | 
				
			||||||
                    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,15 +250,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                        val adapter = recyclerView.adapter
 | 
					                        val adapter = recyclerView.adapter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        when (adapter) {
 | 
					                        when (adapter) {
 | 
				
			||||||
                            is ItemCardAdapter -> adapter.removeItemAtIndex(position)
 | 
					                            is ItemCardAdapter -> adapter.handleItemAtIndex(position)
 | 
				
			||||||
                            is ItemListAdapter -> adapter.removeItemAtIndex(position)
 | 
					                            is ItemListAdapter -> adapter.handleItemAtIndex(position)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if (items.size > 0) {
 | 
					 | 
				
			||||||
                        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))
 | 
				
			||||||
@@ -255,31 +266,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }.toMap()
 | 
					                        }.toMap()
 | 
				
			||||||
                        reloadTagsBadges()
 | 
					                        reloadTagsBadges()
 | 
				
			||||||
                        } else {
 | 
					 | 
				
			||||||
                            tabNewBadge.hide()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        val manager = recyclerView.layoutManager
 | 
					                        // Just load everythin
 | 
				
			||||||
                        val lastVisibleItem: Int = when (manager) {
 | 
					                        if (items.size <= 0) {
 | 
				
			||||||
                            is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
 | 
					                            getElementsAccordingToTab()
 | 
				
			||||||
                                null
 | 
					 | 
				
			||||||
                            ).last()
 | 
					 | 
				
			||||||
                            is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
 | 
					 | 
				
			||||||
                            else -> 0
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        Log.e("TAGTAG", "$lastVisibleItem === ${items.size} && ${items.size} <= ${maxItemNumber()} && ${!lastFetchDone}x")
 | 
					 | 
				
			||||||
                        if (lastVisibleItem === items.size &&
 | 
					 | 
				
			||||||
                            items.size <= maxItemNumber() &&
 | 
					 | 
				
			||||||
                            !lastFetchDone
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                            if (maxItemNumber() <= itemsNumber) {
 | 
					 | 
				
			||||||
                                lastFetchDone = true
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            getElementsAccordingToTab(
 | 
					 | 
				
			||||||
                                appendResults = true,
 | 
					 | 
				
			||||||
                                offsetOverride = lastVisibleItem
 | 
					 | 
				
			||||||
                            )
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        Toast.makeText(
 | 
					                        Toast.makeText(
 | 
				
			||||||
@@ -311,19 +301,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)
 | 
				
			||||||
@@ -337,6 +327,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() {
 | 
				
			||||||
@@ -368,6 +362,33 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
        getElementsAccordingToTab()
 | 
					        getElementsAccordingToTab()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        handleGDPRDialog(sharedPref.getBoolean("GDPR_shown", false))
 | 
					        handleGDPRDialog(sharedPref.getBoolean("GDPR_shown", false))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        handleRecurringTask()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        handleOfflineActions()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getAndStoreAllItems() {
 | 
				
			||||||
 | 
					        api.allItems().enqueue(object : Callback<List<Item>> {
 | 
				
			||||||
 | 
					            override fun onFailure(call: Call<List<Item>>, t: Throwable) {
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            override fun onResponse(
 | 
				
			||||||
 | 
					                call: Call<List<Item>>,
 | 
				
			||||||
 | 
					                response: Response<List<Item>>
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                thread {
 | 
				
			||||||
 | 
					                    if (response.body() != null) {
 | 
				
			||||||
 | 
					                        val apiItems = (response.body() as ArrayList<Item>).filter {
 | 
				
			||||||
 | 
					                            maybeTagFilter != null || filter(it.tags.tags)
 | 
				
			||||||
 | 
					                        } as ArrayList<Item>
 | 
				
			||||||
 | 
					                        db.itemsDao().deleteAllItems()
 | 
				
			||||||
 | 
					                        db.itemsDao()
 | 
				
			||||||
 | 
					                            .insertAllItems(*(apiItems.map { it.toEntity() }).toTypedArray())
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onStop() {
 | 
					    override fun onStop() {
 | 
				
			||||||
@@ -378,7 +399,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
    private fun handleSharedPrefs() {
 | 
					    private fun handleSharedPrefs() {
 | 
				
			||||||
        debugReadingItems = sharedPref.getBoolean("read_debug", false)
 | 
					        debugReadingItems = sharedPref.getBoolean("read_debug", false)
 | 
				
			||||||
        shouldLogEverything = sharedPref.getBoolean("should_log_everything", false)
 | 
					        shouldLogEverything = sharedPref.getBoolean("should_log_everything", false)
 | 
				
			||||||
        clickBehavior = sharedPref.getBoolean("tab_on_tap", false)
 | 
					 | 
				
			||||||
        internalBrowser = sharedPref.getBoolean("prefer_internal_browser", true)
 | 
					        internalBrowser = sharedPref.getBoolean("prefer_internal_browser", true)
 | 
				
			||||||
        articleViewer = sharedPref.getBoolean("prefer_article_viewer", true)
 | 
					        articleViewer = sharedPref.getBoolean("prefer_article_viewer", true)
 | 
				
			||||||
        shouldBeCardView = sharedPref.getBoolean("card_view_active", false)
 | 
					        shouldBeCardView = sharedPref.getBoolean("card_view_active", false)
 | 
				
			||||||
@@ -389,11 +409,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
        userIdentifier = sharedPref.getString("unique_id", "")
 | 
					        userIdentifier = sharedPref.getString("unique_id", "")
 | 
				
			||||||
        displayAccountHeader = sharedPref.getBoolean("account_header_displaying", false)
 | 
					        displayAccountHeader = sharedPref.getBoolean("account_header_displaying", false)
 | 
				
			||||||
        infiniteScroll = sharedPref.getBoolean("infinite_loading", false)
 | 
					        infiniteScroll = sharedPref.getBoolean("infinite_loading", false)
 | 
				
			||||||
 | 
					        itemsCaching = sharedPref.getBoolean("items_caching", false)
 | 
				
			||||||
        hiddenTags = if (sharedPref.getString("hidden_tags", "").isNotEmpty()) {
 | 
					        hiddenTags = if (sharedPref.getString("hidden_tags", "").isNotEmpty()) {
 | 
				
			||||||
            sharedPref.getString("hidden_tags", "").replace("\\s".toRegex(), "").split(",")
 | 
					            sharedPref.getString("hidden_tags", "").replace("\\s".toRegex(), "").split(",")
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            emptyList()
 | 
					            emptyList()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        periodicRefresh = sharedPref.getBoolean("periodic_refresh", false)
 | 
				
			||||||
 | 
					        refreshWhenChargingOnly = sharedPref.getBoolean("refresh_when_charging", false)
 | 
				
			||||||
 | 
					        refreshMinutes = sharedPref.getString("periodic_refresh_minutes", "360").toLong()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (refreshMinutes <= 15) {
 | 
				
			||||||
 | 
					            refreshMinutes = 15
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun handleThemeBinding() {
 | 
					    private fun handleThemeBinding() {
 | 
				
			||||||
@@ -447,7 +475,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))
 | 
				
			||||||
@@ -457,7 +485,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(
 | 
				
			||||||
@@ -500,12 +528,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)
 | 
				
			||||||
@@ -516,6 +543,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)
 | 
				
			||||||
@@ -533,7 +565,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                        )
 | 
					                        )
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    val filteredHiddenTags: List<Tag> = maybeTags.filter { hiddenTags.contains(it.tag) }
 | 
					                    val filteredHiddenTags: List<Tag> =
 | 
				
			||||||
 | 
					                        maybeTags.filter { hiddenTags.contains(it.tag) }
 | 
				
			||||||
                    tagsBadge = filteredHiddenTags.map {
 | 
					                    tagsBadge = filteredHiddenTags.map {
 | 
				
			||||||
                        val gd = GradientDrawable()
 | 
					                        val gd = GradientDrawable()
 | 
				
			||||||
                        val color = try {
 | 
					                        val color = try {
 | 
				
			||||||
@@ -546,12 +579,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)
 | 
				
			||||||
@@ -562,6 +594,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)
 | 
				
			||||||
@@ -569,7 +606,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            fun handleSources(maybeSources: List<Sources>?) {
 | 
					            fun handleSources(maybeSources: List<Source>?) {
 | 
				
			||||||
                if (maybeSources == null) {
 | 
					                if (maybeSources == null) {
 | 
				
			||||||
                    if (loadedFromCache) {
 | 
					                    if (loadedFromCache) {
 | 
				
			||||||
                        drawer.addItem(
 | 
					                        drawer.addItem(
 | 
				
			||||||
@@ -612,14 +649,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(
 | 
				
			||||||
@@ -631,6 +660,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))
 | 
				
			||||||
@@ -648,7 +685,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()
 | 
				
			||||||
@@ -668,14 +705,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (!loadedFromCache) {
 | 
					                if (!loadedFromCache) {
 | 
				
			||||||
                    Reservoir.putAsync(
 | 
					                    if (maybeDrawerData.tags != null) {
 | 
				
			||||||
                        "drawerData", maybeDrawerData, object : ReservoirPutCallback {
 | 
					                        thread {
 | 
				
			||||||
                            override fun onSuccess() {
 | 
					                            val tagEntities = maybeDrawerData.tags.map { it.toEntity() }
 | 
				
			||||||
 | 
					                            db.drawerDataDao().insertAllTags(*tagEntities.toTypedArray())
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (maybeDrawerData.sources != null) {
 | 
				
			||||||
 | 
					                        thread {
 | 
				
			||||||
 | 
					                            val sourceEntities =
 | 
				
			||||||
 | 
					                                maybeDrawerData.sources.map { it.toEntity() }
 | 
				
			||||||
 | 
					                            db.drawerDataDao().insertAllSources(*sourceEntities.toTypedArray())
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                            override fun onFailure(p0: Exception?) {
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                        })
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                if (!loadedFromCache) {
 | 
					                if (!loadedFromCache) {
 | 
				
			||||||
@@ -697,13 +739,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        fun drawerApiCalls(maybeDrawerData: DrawerData?) {
 | 
					        fun drawerApiCalls(maybeDrawerData: DrawerData?) {
 | 
				
			||||||
            var tags: List<Tag>? = null
 | 
					            var tags: List<Tag>? = null
 | 
				
			||||||
            var sources: List<Sources>?
 | 
					            var sources: List<Source>?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            fun sourcesApiCall() {
 | 
					            fun sourcesApiCall() {
 | 
				
			||||||
                api.sources.enqueue(object : Callback<List<Sources>> {
 | 
					                if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) {
 | 
				
			||||||
 | 
					                    api.sources.enqueue(object : Callback<List<Source>> {
 | 
				
			||||||
                        override fun onResponse(
 | 
					                        override fun onResponse(
 | 
				
			||||||
                        call: Call<List<Sources>>?,
 | 
					                            call: Call<List<Source>>?,
 | 
				
			||||||
                        response: Response<List<Sources>>
 | 
					                            response: Response<List<Source>>
 | 
				
			||||||
                        ) {
 | 
					                        ) {
 | 
				
			||||||
                            sources = response.body()
 | 
					                            sources = response.body()
 | 
				
			||||||
                            val apiDrawerData = DrawerData(tags, sources)
 | 
					                            val apiDrawerData = DrawerData(tags, sources)
 | 
				
			||||||
@@ -712,11 +755,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    override fun onFailure(call: Call<List<Sources>>?, 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, 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>>,
 | 
				
			||||||
@@ -731,6 +780,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        drawer.addItem(
 | 
					        drawer.addItem(
 | 
				
			||||||
            PrimaryDrawerItem().withName(getString(R.string.drawer_loading)).withSelectable(
 | 
					            PrimaryDrawerItem().withName(getString(R.string.drawer_loading)).withSelectable(
 | 
				
			||||||
@@ -738,18 +788,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val resultType = object : TypeToken<DrawerData>() {}.type
 | 
					        thread {
 | 
				
			||||||
        Reservoir.getAsync(
 | 
					            var drawerData = DrawerData(db.drawerDataDao().tags().map { it.toView() },
 | 
				
			||||||
            "drawerData", resultType, object : ReservoirGetCallback<DrawerData> {
 | 
					                                        db.drawerDataDao().sources().map { it.toView() })
 | 
				
			||||||
                override fun onSuccess(maybeDrawerData: DrawerData?) {
 | 
					            runOnUiThread {
 | 
				
			||||||
                    handleDrawerData(maybeDrawerData, loadedFromCache = true)
 | 
					                handleDrawerData(drawerData, loadedFromCache = true)
 | 
				
			||||||
                    drawerApiCalls(maybeDrawerData)
 | 
					                drawerApiCalls(drawerData)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                override fun onFailure(p0: Exception?) {
 | 
					 | 
				
			||||||
                    drawerApiCalls(null)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun reloadLayoutManager() {
 | 
					    private fun reloadLayoutManager() {
 | 
				
			||||||
@@ -760,7 +806,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
        when (currentManager) {
 | 
					        when (currentManager) {
 | 
				
			||||||
            is StaggeredGridLayoutManager ->
 | 
					            is StaggeredGridLayoutManager ->
 | 
				
			||||||
                if (!shouldBeCardView) {
 | 
					                if (!shouldBeCardView) {
 | 
				
			||||||
                    layoutManager = GridLayoutManager(this, calculateNoOfColumns())
 | 
					                    layoutManager = GridLayoutManager(
 | 
				
			||||||
 | 
					                        this,
 | 
				
			||||||
 | 
					                        calculateNoOfColumns()
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    recyclerView.layoutManager = layoutManager
 | 
					                    recyclerView.layoutManager = layoutManager
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            is GridLayoutManager ->
 | 
					            is GridLayoutManager ->
 | 
				
			||||||
@@ -776,7 +825,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
            else ->
 | 
					            else ->
 | 
				
			||||||
                if (currentManager == null) {
 | 
					                if (currentManager == null) {
 | 
				
			||||||
                    if (!shouldBeCardView) {
 | 
					                    if (!shouldBeCardView) {
 | 
				
			||||||
                        layoutManager = GridLayoutManager(this, calculateNoOfColumns())
 | 
					                        layoutManager = GridLayoutManager(
 | 
				
			||||||
 | 
					                            this,
 | 
				
			||||||
 | 
					                            calculateNoOfColumns()
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
                        recyclerView.layoutManager = layoutManager
 | 
					                        recyclerView.layoutManager = layoutManager
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        layoutManager = StaggeredGridLayoutManager(
 | 
					                        layoutManager = StaggeredGridLayoutManager(
 | 
				
			||||||
@@ -819,6 +871,45 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
            override fun onTabSelected(position: Int) {
 | 
					            override fun onTabSelected(position: Int) {
 | 
				
			||||||
                offset = 0
 | 
					                offset = 0
 | 
				
			||||||
                lastFetchDone = false
 | 
					                lastFetchDone = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (itemsCaching) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (!swipeRefreshLayout.isRefreshing) {
 | 
				
			||||||
 | 
					                        swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = true }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    thread {
 | 
				
			||||||
 | 
					                        val dbItems = db.itemsDao().items().map { it.toView() }
 | 
				
			||||||
 | 
					                        runOnUiThread {
 | 
				
			||||||
 | 
					                            if (dbItems.isNotEmpty()) {
 | 
				
			||||||
 | 
					                                items = when (position) {
 | 
				
			||||||
 | 
					                                    0 -> ArrayList(dbItems.filter { it.unread })
 | 
				
			||||||
 | 
					                                    1 -> ArrayList(dbItems.filter { !it.unread })
 | 
				
			||||||
 | 
					                                    2 -> ArrayList(dbItems.filter { it.starred })
 | 
				
			||||||
 | 
					                                    else -> ArrayList(dbItems.filter { it.unread })
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                                handleListResult()
 | 
				
			||||||
 | 
					                                when (position) {
 | 
				
			||||||
 | 
					                                    0 -> getUnRead()
 | 
				
			||||||
 | 
					                                    1 -> getRead()
 | 
				
			||||||
 | 
					                                    2 -> getStarred()
 | 
				
			||||||
 | 
					                                    else -> Unit
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                if (this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout), offlineShortcut)) {
 | 
				
			||||||
 | 
					                                    when (position) {
 | 
				
			||||||
 | 
					                                        0 -> getUnRead()
 | 
				
			||||||
 | 
					                                        1 -> getRead()
 | 
				
			||||||
 | 
					                                        2 -> getStarred()
 | 
				
			||||||
 | 
					                                        else -> Unit
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                    getAndStoreAllItems()
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
                    when (position) {
 | 
					                    when (position) {
 | 
				
			||||||
                        0 -> getUnRead()
 | 
					                        0 -> getUnRead()
 | 
				
			||||||
                        1 -> getRead()
 | 
					                        1 -> getRead()
 | 
				
			||||||
@@ -826,14 +917,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                        else -> Unit
 | 
					                        else -> Unit
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun handleInfiniteScroll() {
 | 
					    private fun handleInfiniteScroll() {
 | 
				
			||||||
        if (recyclerViewScrollListener == null) {
 | 
					 | 
				
			||||||
        recyclerViewScrollListener = object : RecyclerView.OnScrollListener() {
 | 
					        recyclerViewScrollListener = object : RecyclerView.OnScrollListener() {
 | 
				
			||||||
                override fun onScrolled(localRecycler: RecyclerView?, dx: Int, dy: Int) {
 | 
					            override fun onScrolled(localRecycler: RecyclerView, dx: Int, dy: Int) {
 | 
				
			||||||
                    if (localRecycler != null && dy > 0) {
 | 
					                if (dy > 0) {
 | 
				
			||||||
                    val manager = recyclerView.layoutManager
 | 
					                    val manager = recyclerView.layoutManager
 | 
				
			||||||
                    val lastVisibleItem: Int = when (manager) {
 | 
					                    val lastVisibleItem: Int = when (manager) {
 | 
				
			||||||
                        is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
 | 
					                        is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
 | 
				
			||||||
@@ -849,7 +940,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        recyclerView.clearOnScrollListeners()
 | 
					        recyclerView.clearOnScrollListeners()
 | 
				
			||||||
        recyclerView.addOnScrollListener(recyclerViewScrollListener)
 | 
					        recyclerView.addOnScrollListener(recyclerViewScrollListener)
 | 
				
			||||||
@@ -868,6 +958,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
        appendResults: Boolean = false,
 | 
					        appendResults: Boolean = false,
 | 
				
			||||||
        offsetOverride: Int? = null
 | 
					        offsetOverride: Int? = null
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
 | 
					        fun doGetAccordingToTab() {
 | 
				
			||||||
 | 
					            when (elementsShown) {
 | 
				
			||||||
 | 
					                UNREAD_SHOWN -> getUnRead(appendResults)
 | 
				
			||||||
 | 
					                READ_SHOWN -> getRead(appendResults)
 | 
				
			||||||
 | 
					                FAV_SHOWN -> getStarred(appendResults)
 | 
				
			||||||
 | 
					                else -> getUnRead(appendResults)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        offset = if (appendResults && offsetOverride === null) {
 | 
					        offset = if (appendResults && offsetOverride === null) {
 | 
				
			||||||
            (offset + itemsNumber)
 | 
					            (offset + itemsNumber)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
@@ -875,12 +974,37 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        firstVisible = if (appendResults) firstVisible else 0
 | 
					        firstVisible = if (appendResults) firstVisible else 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        when (elementsShown) {
 | 
					        if (itemsCaching) {
 | 
				
			||||||
            UNREAD_SHOWN -> getUnRead(appendResults)
 | 
					
 | 
				
			||||||
            READ_SHOWN -> getRead(appendResults)
 | 
					            if (!swipeRefreshLayout.isRefreshing) {
 | 
				
			||||||
            FAV_SHOWN -> getStarred(appendResults)
 | 
					                swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = true }
 | 
				
			||||||
            else -> getUnRead(appendResults)
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            thread {
 | 
				
			||||||
 | 
					                val dbItems = db.itemsDao().items().map { it.toView() }
 | 
				
			||||||
 | 
					                runOnUiThread {
 | 
				
			||||||
 | 
					                    if (dbItems.isNotEmpty()) {
 | 
				
			||||||
 | 
					                        items = when (elementsShown) {
 | 
				
			||||||
 | 
					                            UNREAD_SHOWN -> ArrayList(dbItems.filter { it.unread })
 | 
				
			||||||
 | 
					                            READ_SHOWN -> ArrayList(dbItems.filter { !it.unread })
 | 
				
			||||||
 | 
					                            FAV_SHOWN -> ArrayList(dbItems.filter { it.starred })
 | 
				
			||||||
 | 
					                            else -> ArrayList(dbItems.filter { it.unread })
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        handleListResult()
 | 
				
			||||||
 | 
					                        doGetAccordingToTab()
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        if (this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout), offlineShortcut)) {
 | 
				
			||||||
 | 
					                            doGetAccordingToTab()
 | 
				
			||||||
 | 
					                            getAndStoreAllItems()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            doGetAccordingToTab()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun filter(tags: String): Boolean {
 | 
					    private fun filter(tags: String): Boolean {
 | 
				
			||||||
@@ -894,12 +1018,13 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
        call: (String?, Long?, String?) -> Call<List<Item>>
 | 
					        call: (String?, Long?, String?) -> Call<List<Item>>
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        fun handleItemsResponse(response: Response<List<Item>>) {
 | 
					        fun handleItemsResponse(response: Response<List<Item>>) {
 | 
				
			||||||
            val shouldUpdate = (response.body() != items)
 | 
					            val shouldUpdate = (response.body()?.toSet() != items.toSet())
 | 
				
			||||||
            if (response.body() != null) {
 | 
					            if (response.body() != null) {
 | 
				
			||||||
                if (shouldUpdate) {
 | 
					                if (shouldUpdate) {
 | 
				
			||||||
 | 
					                    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()) {
 | 
				
			||||||
@@ -916,9 +1041,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                    allItems = ArrayList()
 | 
					                    allItems = ArrayList()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (shouldUpdate) {
 | 
					
 | 
				
			||||||
            handleListResult(appendResults)
 | 
					            handleListResult(appendResults)
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!appendResults) mayBeEmpty()
 | 
					            if (!appendResults) mayBeEmpty()
 | 
				
			||||||
            swipeRefreshLayout.isRefreshing = false
 | 
					            swipeRefreshLayout.isRefreshing = false
 | 
				
			||||||
@@ -928,6 +1052,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), 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(
 | 
				
			||||||
@@ -946,6 +1071,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                        ).show()
 | 
					                        ).show()
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = false }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun getUnRead(appendResults: Boolean = false) {
 | 
					    private fun getUnRead(appendResults: Boolean = false) {
 | 
				
			||||||
@@ -1006,6 +1134,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                            this,
 | 
					                            this,
 | 
				
			||||||
                            items,
 | 
					                            items,
 | 
				
			||||||
                            api,
 | 
					                            api,
 | 
				
			||||||
 | 
					                            db,
 | 
				
			||||||
                            customTabActivityHelper,
 | 
					                            customTabActivityHelper,
 | 
				
			||||||
                            internalBrowser,
 | 
					                            internalBrowser,
 | 
				
			||||||
                            articleViewer,
 | 
					                            articleViewer,
 | 
				
			||||||
@@ -1013,27 +1142,27 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                            appColors,
 | 
					                            appColors,
 | 
				
			||||||
                            debugReadingItems,
 | 
					                            debugReadingItems,
 | 
				
			||||||
                            userIdentifier,
 | 
					                            userIdentifier,
 | 
				
			||||||
                            {
 | 
					                            config
 | 
				
			||||||
 | 
					                        ) {
 | 
				
			||||||
                            updateItems(it)
 | 
					                            updateItems(it)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                recyclerAdapter =
 | 
					                recyclerAdapter =
 | 
				
			||||||
                        ItemListAdapter(
 | 
					                        ItemListAdapter(
 | 
				
			||||||
                            this,
 | 
					                            this,
 | 
				
			||||||
                            items,
 | 
					                            items,
 | 
				
			||||||
                            api,
 | 
					                            api,
 | 
				
			||||||
 | 
					                            db,
 | 
				
			||||||
                            customTabActivityHelper,
 | 
					                            customTabActivityHelper,
 | 
				
			||||||
                            clickBehavior,
 | 
					 | 
				
			||||||
                            internalBrowser,
 | 
					                            internalBrowser,
 | 
				
			||||||
                            articleViewer,
 | 
					                            articleViewer,
 | 
				
			||||||
                            debugReadingItems,
 | 
					                            debugReadingItems,
 | 
				
			||||||
                            userIdentifier,
 | 
					                            userIdentifier,
 | 
				
			||||||
                            appColors,
 | 
					                            appColors,
 | 
				
			||||||
                            {
 | 
					                            config
 | 
				
			||||||
 | 
					                        ) {
 | 
				
			||||||
                            updateItems(it)
 | 
					                            updateItems(it)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                recyclerView.addItemDecoration(
 | 
					                recyclerView.addItemDecoration(
 | 
				
			||||||
                    DividerItemDecoration(
 | 
					                    DividerItemDecoration(
 | 
				
			||||||
@@ -1055,7 +1184,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun reloadBadges() {
 | 
					    private fun reloadBadges() {
 | 
				
			||||||
        if (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) {
 | 
				
			||||||
@@ -1161,7 +1290,8 @@ 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 -> {
 | 
				
			||||||
                needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message, {
 | 
					                if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) {
 | 
				
			||||||
 | 
					                    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(
 | 
				
			||||||
                                call: Call<String>,
 | 
					                                call: Call<String>,
 | 
				
			||||||
@@ -1183,25 +1313,28 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
                        Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
 | 
					                        Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
 | 
				
			||||||
                })
 | 
					                    }
 | 
				
			||||||
                    return true
 | 
					                    return true
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    return false
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            R.id.readAll -> {
 | 
					            R.id.readAll -> {
 | 
				
			||||||
                if (elementsShown == UNREAD_SHOWN) {
 | 
					                if (elementsShown == UNREAD_SHOWN) {
 | 
				
			||||||
                    needsConfirmation(R.string.readAll, R.string.markall_dialog_message, {
 | 
					                    needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
 | 
				
			||||||
                        swipeRefreshLayout.isRefreshing = false
 | 
					                        swipeRefreshLayout.isRefreshing = false
 | 
				
			||||||
                        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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        fun readAllDebug(e: Throwable) {
 | 
					                        fun readAllDebug(e: Throwable) {
 | 
				
			||||||
                            // TODO: debug
 | 
					                            ACRA.getErrorReporter().maybeHandleSilentException(e, this@HomeActivity)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if (ids.isNotEmpty()) {
 | 
					                        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>,
 | 
				
			||||||
@@ -1238,7 +1371,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                                                )
 | 
					                                                )
 | 
				
			||||||
                                            )
 | 
					                                            )
 | 
				
			||||||
                                        }
 | 
					                                        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                                    }
 | 
					                                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    swipeRefreshLayout.isRefreshing = false
 | 
					                                    swipeRefreshLayout.isRefreshing = false
 | 
				
			||||||
@@ -1268,7 +1400,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
                            ).show()
 | 
					                            ).show()
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        handleListResult()
 | 
					                        handleListResult()
 | 
				
			||||||
                    })
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return true
 | 
					                return true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -1287,8 +1419,79 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
 | 
				
			|||||||
            else -> badgeNew // if !elementsShown then unread are fetched.
 | 
					            else -> badgeNew // if !elementsShown then unread are fetched.
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun updateItems(adapterItems: ArrayList<Item>) {
 | 
					    private fun updateItems(adapterItems: ArrayList<Item>) {
 | 
				
			||||||
        items = adapterItems
 | 
					        items = adapterItems
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun handleGDPRDialog(GDPRShown: Boolean) {
 | 
				
			||||||
 | 
					        val sharedEditor = sharedPref.edit()
 | 
				
			||||||
 | 
					        if (!GDPRShown) {
 | 
				
			||||||
 | 
					            val alertDialog = AlertDialog.Builder(this).create()
 | 
				
			||||||
 | 
					            alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
 | 
				
			||||||
 | 
					            alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
 | 
				
			||||||
 | 
					            alertDialog.setButton(
 | 
				
			||||||
 | 
					                AlertDialog.BUTTON_NEUTRAL,
 | 
				
			||||||
 | 
					                "OK"
 | 
				
			||||||
 | 
					            ) { dialog, _ ->
 | 
				
			||||||
 | 
					                sharedEditor.putBoolean("GDPR_shown", true)
 | 
				
			||||||
 | 
					                sharedEditor.commit()
 | 
				
			||||||
 | 
					                dialog.dismiss()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            alertDialog.show()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun handleRecurringTask() {
 | 
				
			||||||
 | 
					        if (periodicRefresh) {
 | 
				
			||||||
 | 
					            val myConstraints = Constraints.Builder()
 | 
				
			||||||
 | 
					                .setRequiresBatteryNotLow(true)
 | 
				
			||||||
 | 
					                .setRequiresCharging(refreshWhenChargingOnly)
 | 
				
			||||||
 | 
					                .setRequiresStorageNotLow(true)
 | 
				
			||||||
 | 
					                .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val backgroundWork =
 | 
				
			||||||
 | 
					                PeriodicWorkRequestBuilder<LoadingWorker>(refreshMinutes, TimeUnit.MINUTES)
 | 
				
			||||||
 | 
					                    .setConstraints(myConstraints)
 | 
				
			||||||
 | 
					                    .addTag("selfoss-loading")
 | 
				
			||||||
 | 
					                    .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            WorkManager.getInstance().enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun handleOfflineActions() {
 | 
				
			||||||
 | 
					        fun <T>doAndReportOnFail(call: Call<T>, action: ActionEntity) {
 | 
				
			||||||
 | 
					           call.enqueue(object: Callback<T> {
 | 
				
			||||||
 | 
					               override fun onResponse(
 | 
				
			||||||
 | 
					                   call: Call<T>,
 | 
				
			||||||
 | 
					                   response: Response<T>
 | 
				
			||||||
 | 
					               ) {
 | 
				
			||||||
 | 
					                   thread {
 | 
				
			||||||
 | 
					                       db.actionsDao().delete(action)
 | 
				
			||||||
 | 
					                   }
 | 
				
			||||||
 | 
					               }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               override fun onFailure(call: Call<T>, t: Throwable) {
 | 
				
			||||||
 | 
					                   ACRA.getErrorReporter().maybeHandleSilentException(t, this@HomeActivity)
 | 
				
			||||||
 | 
					               }
 | 
				
			||||||
 | 
					           })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) {
 | 
				
			||||||
 | 
					            thread {
 | 
				
			||||||
 | 
					                val actions = db.actionsDao().actions()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                actions.forEach { action ->
 | 
				
			||||||
 | 
					                    when {
 | 
				
			||||||
 | 
					                        action.read -> doAndReportOnFail(api.markItem(action.articleId), action)
 | 
				
			||||||
 | 
					                        action.unread -> doAndReportOnFail(api.unmarkItem(action.articleId), action)
 | 
				
			||||||
 | 
					                        action.starred -> doAndReportOnFail(api.starrItem(action.articleId), action)
 | 
				
			||||||
 | 
					                        action.unstarred -> doAndReportOnFail(api.unstarrItem(action.articleId), action)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,70 +0,0 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import agency.tango.materialintroscreen.MaterialIntroActivity
 | 
					 | 
				
			||||||
import agency.tango.materialintroscreen.MessageButtonBehaviour
 | 
					 | 
				
			||||||
import agency.tango.materialintroscreen.SlideFragmentBuilder
 | 
					 | 
				
			||||||
import android.content.Intent
 | 
					 | 
				
			||||||
import android.net.Uri
 | 
					 | 
				
			||||||
import android.os.Bundle
 | 
					 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					 | 
				
			||||||
import android.support.v7.app.AppCompatDelegate
 | 
					 | 
				
			||||||
import android.view.View
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class IntroActivity : MaterialIntroActivity() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
					 | 
				
			||||||
        super.onCreate(savedInstanceState)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        addSlide(
 | 
					 | 
				
			||||||
            SlideFragmentBuilder()
 | 
					 | 
				
			||||||
                .backgroundColor(R.color.colorPrimary)
 | 
					 | 
				
			||||||
                .buttonsColor(R.color.colorAccent)
 | 
					 | 
				
			||||||
                .image(R.drawable.web_hi_res_512)
 | 
					 | 
				
			||||||
                .title(getString(R.string.intro_hello_title))
 | 
					 | 
				
			||||||
                .description(getString(R.string.intro_hello_message))
 | 
					 | 
				
			||||||
                .build()
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        addSlide(
 | 
					 | 
				
			||||||
            SlideFragmentBuilder()
 | 
					 | 
				
			||||||
                .backgroundColor(R.color.colorAccent)
 | 
					 | 
				
			||||||
                .buttonsColor(R.color.colorPrimary)
 | 
					 | 
				
			||||||
                .image(R.drawable.ic_info_outline_white_48px)
 | 
					 | 
				
			||||||
                .title(getString(R.string.intro_needs_selfoss_title))
 | 
					 | 
				
			||||||
                .description(getString(R.string.intro_needs_selfoss_message))
 | 
					 | 
				
			||||||
                .build(),
 | 
					 | 
				
			||||||
            MessageButtonBehaviour(
 | 
					 | 
				
			||||||
                View.OnClickListener {
 | 
					 | 
				
			||||||
                    val browserIntent = Intent(
 | 
					 | 
				
			||||||
                        Intent.ACTION_VIEW,
 | 
					 | 
				
			||||||
                        Uri.parse("https://selfoss.aditu.de")
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    startActivity(browserIntent)
 | 
					 | 
				
			||||||
                }, getString(R.string.intro_needs_selfoss_link)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        addSlide(
 | 
					 | 
				
			||||||
            SlideFragmentBuilder()
 | 
					 | 
				
			||||||
                .backgroundColor(R.color.colorPrimaryDark)
 | 
					 | 
				
			||||||
                .buttonsColor(R.color.colorAccentDark)
 | 
					 | 
				
			||||||
                .image(R.drawable.ic_thumb_up_white_48px)
 | 
					 | 
				
			||||||
                .title(getString(R.string.intro_all_set_title))
 | 
					 | 
				
			||||||
                .description(getString(R.string.intro_all_set_message))
 | 
					 | 
				
			||||||
                .build()
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun onFinish() {
 | 
					 | 
				
			||||||
        super.onFinish()
 | 
					 | 
				
			||||||
        val getPrefs = PreferenceManager.getDefaultSharedPreferences(baseContext)
 | 
					 | 
				
			||||||
        val e = getPrefs.edit()
 | 
					 | 
				
			||||||
        e.putBoolean("firstStart", false)
 | 
					 | 
				
			||||||
        e.apply()
 | 
					 | 
				
			||||||
        val intent = Intent(this, LoginActivity::class.java)
 | 
					 | 
				
			||||||
        startActivity(intent)
 | 
					 | 
				
			||||||
        finish()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -6,8 +6,8 @@ import android.content.Context
 | 
				
			|||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.content.SharedPreferences
 | 
					import android.content.SharedPreferences
 | 
				
			||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
import android.support.v7.app.AlertDialog
 | 
					import androidx.appcompat.app.AlertDialog
 | 
				
			||||||
import android.support.v7.app.AppCompatActivity
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
import android.text.TextUtils
 | 
					import android.text.TextUtils
 | 
				
			||||||
import android.view.Menu
 | 
					import android.view.Menu
 | 
				
			||||||
import android.view.MenuItem
 | 
					import android.view.MenuItem
 | 
				
			||||||
@@ -21,6 +21,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.isBaseUrlValid
 | 
					import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
 | 
					import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
 | 
				
			||||||
import com.mikepenz.aboutlibraries.Libs
 | 
					import com.mikepenz.aboutlibraries.Libs
 | 
				
			||||||
import com.mikepenz.aboutlibraries.LibsBuilder
 | 
					import com.mikepenz.aboutlibraries.LibsBuilder
 | 
				
			||||||
import kotlinx.android.synthetic.main.activity_login.*
 | 
					import kotlinx.android.synthetic.main.activity_login.*
 | 
				
			||||||
@@ -53,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)
 | 
				
			||||||
@@ -144,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
 | 
				
			||||||
@@ -163,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
 | 
				
			||||||
@@ -177,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 {
 | 
				
			||||||
@@ -194,8 +208,11 @@ class LoginActivity : AppCompatActivity() {
 | 
				
			|||||||
                this,
 | 
					                this,
 | 
				
			||||||
                this@LoginActivity,
 | 
					                this@LoginActivity,
 | 
				
			||||||
                isWithSelfSignedCert,
 | 
					                isWithSelfSignedCert,
 | 
				
			||||||
 | 
					                -1L,
 | 
				
			||||||
                isWithSelfSignedCert
 | 
					                isWithSelfSignedCert
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this@LoginActivity.isNetworkAccessible(this@LoginActivity.findViewById(R.id.loginForm))) {
 | 
				
			||||||
                api.login().enqueue(object : Callback<SuccessResponse> {
 | 
					                api.login().enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
                    private fun preferenceError(t: Throwable) {
 | 
					                    private fun preferenceError(t: Throwable) {
 | 
				
			||||||
                        editor.remove("url")
 | 
					                        editor.remove("url")
 | 
				
			||||||
@@ -235,6 +252,9 @@ class LoginActivity : AppCompatActivity() {
 | 
				
			|||||||
                        preferenceError(t)
 | 
					                        preferenceError(t)
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                showProgress(false)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ package apps.amine.bou.readerforselfoss
 | 
				
			|||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					import android.preference.PreferenceManager
 | 
				
			||||||
import android.support.v7.app.AppCompatActivity
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MainActivity : AppCompatActivity() {
 | 
					class MainActivity : AppCompatActivity() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,17 +11,9 @@ class MainActivity : AppCompatActivity() {
 | 
				
			|||||||
        super.onCreate(savedInstanceState)
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
        setContentView(R.layout.activity_main)
 | 
					        setContentView(R.layout.activity_main)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (PreferenceManager.getDefaultSharedPreferences(baseContext).getBoolean(
 | 
					 | 
				
			||||||
                "firstStart",
 | 
					 | 
				
			||||||
                true
 | 
					 | 
				
			||||||
            )) {
 | 
					 | 
				
			||||||
            val i = Intent(this@MainActivity, IntroActivity::class.java)
 | 
					 | 
				
			||||||
            startActivity(i)
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
        val intent = Intent(this, LoginActivity::class.java)
 | 
					        val intent = Intent(this, LoginActivity::class.java)
 | 
				
			||||||
            startActivity(intent)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        startActivity(intent)
 | 
				
			||||||
        finish()
 | 
					        finish()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,16 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss
 | 
					package apps.amine.bou.readerforselfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.app.NotificationChannel
 | 
				
			||||||
 | 
					import android.app.NotificationManager
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.content.SharedPreferences
 | 
					 | 
				
			||||||
import android.graphics.drawable.Drawable
 | 
					import android.graphics.drawable.Drawable
 | 
				
			||||||
import android.net.Uri
 | 
					import android.net.Uri
 | 
				
			||||||
 | 
					import android.os.Build
 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					import android.preference.PreferenceManager
 | 
				
			||||||
import android.support.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 com.anupcowkur.reservoir.Reservoir
 | 
					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
 | 
				
			||||||
@@ -27,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)
 | 
				
			||||||
@@ -43,14 +43,13 @@ 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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        initCache()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
					        val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
				
			||||||
        if (prefs.getString("unique_id", "").isEmpty()) {
 | 
					        if (prefs.getString("unique_id", "").isEmpty()) {
 | 
				
			||||||
            val editor = prefs.edit()
 | 
					            val editor = prefs.edit()
 | 
				
			||||||
@@ -63,6 +62,25 @@ class MyApp : MultiDexApplication() {
 | 
				
			|||||||
        initTheme()
 | 
					        initTheme()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tryToHandleBug()
 | 
					        tryToHandleBug()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        handleNotificationChannels()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun handleNotificationChannels() {
 | 
				
			||||||
 | 
					        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 importance = NotificationManager.IMPORTANCE_LOW
 | 
				
			||||||
 | 
					            val mChannel = NotificationChannel(Config.syncChannelId, name, importance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            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(newItemsChannelmChannel)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun attachBaseContext(base: Context?) {
 | 
					    override fun attachBaseContext(base: Context?) {
 | 
				
			||||||
@@ -80,14 +98,6 @@ class MyApp : MultiDexApplication() {
 | 
				
			|||||||
            .applyAllDefaultRules()
 | 
					            .applyAllDefaultRules()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun initCache() {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            Reservoir.init(this, 8192) //in bytes
 | 
					 | 
				
			||||||
        } catch (e: IOException) {
 | 
					 | 
				
			||||||
            //failure
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private fun initDrawerImageLoader() {
 | 
					    private fun initDrawerImageLoader() {
 | 
				
			||||||
        DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
 | 
					        DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
 | 
				
			||||||
            override fun set(
 | 
					            override fun set(
 | 
				
			||||||
@@ -97,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)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,29 +1,35 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss
 | 
					package apps.amine.bou.readerforselfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.SharedPreferences
 | 
				
			||||||
import android.content.res.Resources
 | 
					 | 
				
			||||||
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.support.v4.app.FragmentManager
 | 
					import androidx.fragment.app.FragmentManager
 | 
				
			||||||
import android.support.v4.app.FragmentStatePagerAdapter
 | 
					import androidx.fragment.app.FragmentStatePagerAdapter
 | 
				
			||||||
import android.support.v4.content.ContextCompat
 | 
					import androidx.core.content.ContextCompat
 | 
				
			||||||
import android.support.v4.view.ViewPager
 | 
					import androidx.viewpager.widget.ViewPager
 | 
				
			||||||
import android.support.v7.app.AppCompatActivity
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
import android.view.Menu
 | 
					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 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
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.fragments.ArticleFragment
 | 
					import apps.amine.bou.readerforselfoss.fragments.ArticleFragment
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
					import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.Toppings
 | 
					import apps.amine.bou.readerforselfoss.themes.Toppings
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.transformers.DepthPageTransformer
 | 
					import apps.amine.bou.readerforselfoss.transformers.DepthPageTransformer
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.Config
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
 | 
					import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.persistence.toEntity
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.succeeded
 | 
					import apps.amine.bou.readerforselfoss.utils.succeeded
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.toggleStar
 | 
					import apps.amine.bou.readerforselfoss.utils.toggleStar
 | 
				
			||||||
import com.ftinc.scoop.Scoop
 | 
					import com.ftinc.scoop.Scoop
 | 
				
			||||||
@@ -33,6 +39,7 @@ import org.acra.ACRA
 | 
				
			|||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
import retrofit2.Callback
 | 
					import retrofit2.Callback
 | 
				
			||||||
import retrofit2.Response
 | 
					import retrofit2.Response
 | 
				
			||||||
 | 
					import kotlin.concurrent.thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ReaderActivity : AppCompatActivity() {
 | 
					class ReaderActivity : AppCompatActivity() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,6 +52,13 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private lateinit var toolbarMenu: Menu
 | 
					    private lateinit var toolbarMenu: Menu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lateinit var db: AppDatabase
 | 
				
			||||||
 | 
					    private lateinit var prefs: SharedPreferences
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var activeAlignment: Int = 1
 | 
				
			||||||
 | 
					    val JUSTIFY = 1
 | 
				
			||||||
 | 
					    val ALIGN_LEFT = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun showMenuItem(willAddToFavorite: Boolean) {
 | 
					    private fun showMenuItem(willAddToFavorite: Boolean) {
 | 
				
			||||||
        toolbarMenu.findItem(R.id.save).isVisible = willAddToFavorite
 | 
					        toolbarMenu.findItem(R.id.save).isVisible = willAddToFavorite
 | 
				
			||||||
        toolbarMenu.findItem(R.id.unsave).isVisible = !willAddToFavorite
 | 
					        toolbarMenu.findItem(R.id.unsave).isVisible = !willAddToFavorite
 | 
				
			||||||
@@ -58,11 +72,18 @@ 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setContentView(R.layout.activity_reader)
 | 
					        setContentView(R.layout.activity_reader)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        db = Room.databaseBuilder(
 | 
				
			||||||
 | 
					            applicationContext,
 | 
				
			||||||
 | 
					            AppDatabase::class.java, "selfoss-database"
 | 
				
			||||||
 | 
					        ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val scoop = Scoop.getInstance()
 | 
					        val scoop = Scoop.getInstance()
 | 
				
			||||||
        scoop.bind(this, Toppings.PRIMARY.value, toolBar)
 | 
					        scoop.bind(this, Toppings.PRIMARY.value, toolBar)
 | 
				
			||||||
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 | 
					        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 | 
				
			||||||
@@ -73,55 +94,66 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
					        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
				
			||||||
        supportActionBar?.setDisplayShowHomeEnabled(true)
 | 
					        supportActionBar?.setDisplayShowHomeEnabled(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
					        prefs = PreferenceManager.getDefaultSharedPreferences(this)
 | 
				
			||||||
        val sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
 | 
					        editor = prefs.edit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        debugReadingItems = sharedPref.getBoolean("read_debug", false)
 | 
					        debugReadingItems = prefs.getBoolean("read_debug", false)
 | 
				
			||||||
        userIdentifier = sharedPref.getString("unique_id", "")
 | 
					        userIdentifier = prefs.getString("unique_id", "")
 | 
				
			||||||
        markOnScroll = sharedPref.getBoolean("mark_on_scroll", false)
 | 
					        markOnScroll = prefs.getBoolean("mark_on_scroll", false)
 | 
				
			||||||
 | 
					        activeAlignment = prefs.getInt("text_align", JUSTIFY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        api = SelfossApi(
 | 
				
			||||||
 | 
					            this,
 | 
				
			||||||
 | 
					            this@ReaderActivity,
 | 
				
			||||||
 | 
					            prefs.getBoolean("isSelfSignedCert", false),
 | 
				
			||||||
 | 
					            prefs.getString("api_timeout", "-1").toLong(),
 | 
				
			||||||
 | 
					            prefs.getBoolean("should_log_everything", false)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (allItems.isEmpty()) {
 | 
					        if (allItems.isEmpty()) {
 | 
				
			||||||
            finish()
 | 
					            finish()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        api = SelfossApi(
 | 
					 | 
				
			||||||
            this,
 | 
					 | 
				
			||||||
            this@ReaderActivity,
 | 
					 | 
				
			||||||
            settings.getBoolean("isSelfSignedCert", false),
 | 
					 | 
				
			||||||
            sharedPref.getBoolean("should_log_everything", false)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        currentItem = intent.getIntExtra("currentItem", 0)
 | 
					        currentItem = intent.getIntExtra("currentItem", 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pager.adapter = ScreenSlidePagerAdapter(supportFragmentManager, AppColors(this@ReaderActivity))
 | 
					        readItem(allItems[currentItem])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pager.adapter =
 | 
				
			||||||
 | 
					                ScreenSlidePagerAdapter(supportFragmentManager, AppColors(this@ReaderActivity))
 | 
				
			||||||
        pager.currentItem = currentItem
 | 
					        pager.currentItem = currentItem
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onResume() {
 | 
					    override fun onResume() {
 | 
				
			||||||
        super.onResume()
 | 
					        super.onResume()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (pager.adapter as ScreenSlidePagerAdapter).notifyDataSetChanged()
 | 
					        notifyAdapter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pager.setPageTransformer(true, DepthPageTransformer())
 | 
					        pager.setPageTransformer(true, DepthPageTransformer())
 | 
				
			||||||
        (indicator as CircleIndicator).setViewPager(pager)
 | 
					        (indicator as CircleIndicator).setViewPager(pager)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pager.addOnPageChangeListener(
 | 
					        pager.addOnPageChangeListener(
 | 
				
			||||||
            object : ViewPager.SimpleOnPageChangeListener() {
 | 
					            object : ViewPager.SimpleOnPageChangeListener() {
 | 
				
			||||||
                var isLastItem = false
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                override fun onPageSelected(position: Int) {
 | 
					                override fun onPageSelected(position: Int) {
 | 
				
			||||||
                    isLastItem = (position === (allItems.size - 1))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (allItems[position].starred) {
 | 
					                    if (allItems[position].starred) {
 | 
				
			||||||
                        canRemoveFromFavorite()
 | 
					                        canRemoveFromFavorite()
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        canFavorite()
 | 
					                        canFavorite()
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    readItem(allItems[pager.currentItem])
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                override fun onPageScrollStateChanged(state: Int) {
 | 
					    fun readItem(item: Item) {
 | 
				
			||||||
                    if (markOnScroll && (state === ViewPager.SCROLL_STATE_DRAGGING || (state === ViewPager.SCROLL_STATE_IDLE && isLastItem))) {
 | 
					        if (markOnScroll) {
 | 
				
			||||||
                        api.markItem(allItems[pager.currentItem].id).enqueue(
 | 
					            thread {
 | 
				
			||||||
 | 
					                db.itemsDao().delete(item.toEntity())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (this@ReaderActivity.isNetworkAccessible(this@ReaderActivity.findViewById(R.id.reader_activity_view))) {
 | 
				
			||||||
 | 
					                api.markItem(item.id).enqueue(
 | 
				
			||||||
                    object : Callback<SuccessResponse> {
 | 
					                    object : Callback<SuccessResponse> {
 | 
				
			||||||
                        override fun onResponse(
 | 
					                        override fun onResponse(
 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					                            call: Call<SuccessResponse>,
 | 
				
			||||||
@@ -136,7 +168,8 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
                                            "response errorBody: ${response.errorBody()?.string()} " +
 | 
					                                            "response errorBody: ${response.errorBody()?.string()} " +
 | 
				
			||||||
                                            "body success: ${response.body()?.success} " +
 | 
					                                            "body success: ${response.body()?.success} " +
 | 
				
			||||||
                                            "body isSuccess: ${response.body()?.isSuccess}"
 | 
					                                            "body isSuccess: ${response.body()?.isSuccess}"
 | 
				
			||||||
                                        ACRA.getErrorReporter().maybeHandleSilentException(Exception(message), this@ReaderActivity)
 | 
					                                ACRA.getErrorReporter()
 | 
				
			||||||
 | 
					                                    .maybeHandleSilentException(Exception(message), this@ReaderActivity)
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -144,16 +177,26 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
                            call: Call<SuccessResponse>,
 | 
					                            call: Call<SuccessResponse>,
 | 
				
			||||||
                            t: Throwable
 | 
					                            t: Throwable
 | 
				
			||||||
                        ) {
 | 
					                        ) {
 | 
				
			||||||
 | 
					                            thread {
 | 
				
			||||||
 | 
					                                db.itemsDao().insertAllItems(item.toEntity())
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
                            if (debugReadingItems) {
 | 
					                            if (debugReadingItems) {
 | 
				
			||||||
                                        ACRA.getErrorReporter().maybeHandleSilentException(t, this@ReaderActivity)
 | 
					                                ACRA.getErrorReporter()
 | 
				
			||||||
 | 
					                                    .maybeHandleSilentException(t, this@ReaderActivity)
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                thread {
 | 
				
			||||||
 | 
					                    db.actionsDao().insertAllActions(ActionEntity(item.id, true, false, false, false))
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        )
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun notifyAdapter() {
 | 
				
			||||||
 | 
					        (pager.adapter as ScreenSlidePagerAdapter).notifyDataSetChanged()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onPause() {
 | 
					    override fun onPause() {
 | 
				
			||||||
@@ -171,7 +214,6 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
    private inner class ScreenSlidePagerAdapter(fm: FragmentManager, val appColors: AppColors) :
 | 
					    private inner class ScreenSlidePagerAdapter(fm: FragmentManager, val appColors: AppColors) :
 | 
				
			||||||
        FragmentStatePagerAdapter(fm) {
 | 
					        FragmentStatePagerAdapter(fm) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        override fun getCount(): Int {
 | 
					        override fun getCount(): Int {
 | 
				
			||||||
            return allItems.size
 | 
					            return allItems.size
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -183,10 +225,20 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
        override fun startUpdate(container: ViewGroup) {
 | 
					        override fun startUpdate(container: ViewGroup) {
 | 
				
			||||||
            super.startUpdate(container)
 | 
					            super.startUpdate(container)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            container.background = ColorDrawable(ContextCompat.getColor(this@ReaderActivity, appColors.colorBackground))
 | 
					            container.background = ColorDrawable(
 | 
				
			||||||
 | 
					                ContextCompat.getColor(
 | 
				
			||||||
 | 
					                    this@ReaderActivity,
 | 
				
			||||||
 | 
					                    appColors.colorBackground
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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)
 | 
				
			||||||
@@ -197,25 +249,43 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            canFavorite()
 | 
					            canFavorite()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (activeAlignment == JUSTIFY) {
 | 
				
			||||||
 | 
					            alignmentMenu(false)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            alignmentMenu(true)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return true
 | 
					        return true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
					    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
				
			||||||
 | 
					        fun afterSave() {
 | 
				
			||||||
 | 
					            allItems[pager.currentItem] =
 | 
				
			||||||
 | 
					                    allItems[pager.currentItem].toggleStar()
 | 
				
			||||||
 | 
					            notifyAdapter()
 | 
				
			||||||
 | 
					            canRemoveFromFavorite()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fun afterUnsave() {
 | 
				
			||||||
 | 
					            allItems[pager.currentItem] = allItems[pager.currentItem].toggleStar()
 | 
				
			||||||
 | 
					            notifyAdapter()
 | 
				
			||||||
 | 
					            canFavorite()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        when (item.itemId) {
 | 
					        when (item.itemId) {
 | 
				
			||||||
            android.R.id.home -> {
 | 
					            android.R.id.home -> {
 | 
				
			||||||
                onBackPressed()
 | 
					                onBackPressed()
 | 
				
			||||||
                return true
 | 
					                return true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            R.id.save -> {
 | 
					            R.id.save -> {
 | 
				
			||||||
 | 
					                if (this@ReaderActivity.isNetworkAccessible(null)) {
 | 
				
			||||||
                    api.starrItem(allItems[pager.currentItem].id)
 | 
					                    api.starrItem(allItems[pager.currentItem].id)
 | 
				
			||||||
                        .enqueue(object : Callback<SuccessResponse> {
 | 
					                        .enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
                            override fun onResponse(
 | 
					                            override fun onResponse(
 | 
				
			||||||
                                call: Call<SuccessResponse>,
 | 
					                                call: Call<SuccessResponse>,
 | 
				
			||||||
                                response: Response<SuccessResponse>
 | 
					                                response: Response<SuccessResponse>
 | 
				
			||||||
                            ) {
 | 
					                            ) {
 | 
				
			||||||
                            allItems[pager.currentItem] = allItems[pager.currentItem].toggleStar()
 | 
					                                afterSave()
 | 
				
			||||||
                            canRemoveFromFavorite()
 | 
					 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            override fun onFailure(
 | 
					                            override fun onFailure(
 | 
				
			||||||
@@ -229,16 +299,22 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
                                ).show()
 | 
					                                ).show()
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    thread {
 | 
				
			||||||
 | 
					                        db.actionsDao().insertAllActions(ActionEntity(allItems[pager.currentItem].id, false, false, true, false))
 | 
				
			||||||
 | 
					                        afterSave()
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            R.id.unsave -> {
 | 
					            R.id.unsave -> {
 | 
				
			||||||
 | 
					                if (this@ReaderActivity.isNetworkAccessible(null)) {
 | 
				
			||||||
                    api.unstarrItem(allItems[pager.currentItem].id)
 | 
					                    api.unstarrItem(allItems[pager.currentItem].id)
 | 
				
			||||||
                        .enqueue(object : Callback<SuccessResponse> {
 | 
					                        .enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
                            override fun onResponse(
 | 
					                            override fun onResponse(
 | 
				
			||||||
                                call: Call<SuccessResponse>,
 | 
					                                call: Call<SuccessResponse>,
 | 
				
			||||||
                                response: Response<SuccessResponse>
 | 
					                                response: Response<SuccessResponse>
 | 
				
			||||||
                            ) {
 | 
					                            ) {
 | 
				
			||||||
                            allItems[pager.currentItem] = allItems[pager.currentItem].toggleStar()
 | 
					                                afterUnsave()
 | 
				
			||||||
                            canFavorite()
 | 
					 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            override fun onFailure(
 | 
					                            override fun onFailure(
 | 
				
			||||||
@@ -252,11 +328,36 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
                                ).show()
 | 
					                                ).show()
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    thread {
 | 
				
			||||||
 | 
					                        db.actionsDao().insertAllActions(ActionEntity(allItems[pager.currentItem].id, false, false, false, true))
 | 
				
			||||||
 | 
					                        afterUnsave()
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            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()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,14 +5,15 @@ import android.content.res.ColorStateList
 | 
				
			|||||||
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.support.v7.app.AppCompatActivity
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
import android.support.v7.widget.LinearLayoutManager
 | 
					import androidx.recyclerview.widget.LinearLayoutManager
 | 
				
			||||||
import android.widget.Toast
 | 
					import android.widget.Toast
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
 | 
					import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Source
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
					import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.Toppings
 | 
					import apps.amine.bou.readerforselfoss.themes.Toppings
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
 | 
				
			||||||
import com.ftinc.scoop.Scoop
 | 
					import com.ftinc.scoop.Scoop
 | 
				
			||||||
import kotlinx.android.synthetic.main.activity_sources.*
 | 
					import kotlinx.android.synthetic.main.activity_sources.*
 | 
				
			||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
@@ -59,20 +60,22 @@ 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<Sources> = ArrayList()
 | 
					        var items: ArrayList<Source> = ArrayList()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        recyclerView.setHasFixedSize(true)
 | 
					        recyclerView.setHasFixedSize(true)
 | 
				
			||||||
        recyclerView.layoutManager = mLayoutManager
 | 
					        recyclerView.layoutManager = mLayoutManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        api.sources.enqueue(object : Callback<List<Sources>> {
 | 
					        if (this@SourcesActivity.isNetworkAccessible(this@SourcesActivity.findViewById(R.id.recyclerView))) {
 | 
				
			||||||
 | 
					            api.sources.enqueue(object : Callback<List<Source>> {
 | 
				
			||||||
                override fun onResponse(
 | 
					                override fun onResponse(
 | 
				
			||||||
                call: Call<List<Sources>>,
 | 
					                    call: Call<List<Source>>,
 | 
				
			||||||
                response: Response<List<Sources>>
 | 
					                    response: Response<List<Source>>
 | 
				
			||||||
                ) {
 | 
					                ) {
 | 
				
			||||||
                    if (response.body() != null && response.body()!!.isNotEmpty()) {
 | 
					                    if (response.body() != null && response.body()!!.isNotEmpty()) {
 | 
				
			||||||
                    items = response.body() as ArrayList<Sources>
 | 
					                        items = response.body() as ArrayList<Source>
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
 | 
					                    val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
 | 
				
			||||||
                    recyclerView.adapter = mAdapter
 | 
					                    recyclerView.adapter = mAdapter
 | 
				
			||||||
@@ -86,7 +89,7 @@ class SourcesActivity : AppCompatActivity() {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            override fun onFailure(call: Call<List<Sources>>, t: Throwable) {
 | 
					                override fun onFailure(call: Call<List<Source>>, t: Throwable) {
 | 
				
			||||||
                    Toast.makeText(
 | 
					                    Toast.makeText(
 | 
				
			||||||
                        this@SourcesActivity,
 | 
					                        this@SourcesActivity,
 | 
				
			||||||
                        R.string.cant_get_sources,
 | 
					                        R.string.cant_get_sources,
 | 
				
			||||||
@@ -94,6 +97,7 @@ class SourcesActivity : AppCompatActivity() {
 | 
				
			|||||||
                    ).show()
 | 
					                    ).show()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fab.setOnClickListener {
 | 
					        fab.setOnClickListener {
 | 
				
			||||||
            startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
 | 
					            startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,8 @@ package apps.amine.bou.readerforselfoss.adapters
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.support.v7.widget.CardView
 | 
					import androidx.cardview.widget.CardView
 | 
				
			||||||
import android.support.v7.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import android.text.Html
 | 
					import android.text.Html
 | 
				
			||||||
import android.view.LayoutInflater
 | 
					import android.view.LayoutInflater
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
@@ -14,11 +14,16 @@ 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
 | 
				
			||||||
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.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.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.bitmapCenterCrop
 | 
					import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
 | 
				
			||||||
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.openInBrowserAsNewTask
 | 
					import apps.amine.bou.readerforselfoss.utils.openInBrowserAsNewTask
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
 | 
					import apps.amine.bou.readerforselfoss.utils.openItemUrl
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.shareLink
 | 
					import apps.amine.bou.readerforselfoss.utils.shareLink
 | 
				
			||||||
@@ -33,11 +38,13 @@ import kotlinx.android.synthetic.main.card_item.view.*
 | 
				
			|||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
import retrofit2.Callback
 | 
					import retrofit2.Callback
 | 
				
			||||||
import retrofit2.Response
 | 
					import retrofit2.Response
 | 
				
			||||||
 | 
					import kotlin.concurrent.thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ItemCardAdapter(
 | 
					class ItemCardAdapter(
 | 
				
			||||||
    override val app: Activity,
 | 
					    override val app: Activity,
 | 
				
			||||||
    override var items: ArrayList<Item>,
 | 
					    override var items: ArrayList<Item>,
 | 
				
			||||||
    override val api: SelfossApi,
 | 
					    override val api: SelfossApi,
 | 
				
			||||||
 | 
					    override val db: AppDatabase,
 | 
				
			||||||
    private val helper: CustomTabActivityHelper,
 | 
					    private val helper: CustomTabActivityHelper,
 | 
				
			||||||
    private val internalBrowser: Boolean,
 | 
					    private val internalBrowser: Boolean,
 | 
				
			||||||
    private val articleViewer: Boolean,
 | 
					    private val articleViewer: Boolean,
 | 
				
			||||||
@@ -45,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
 | 
				
			||||||
@@ -63,6 +71,7 @@ class ItemCardAdapter(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        holder.mView.favButton.isLiked = itm.starred
 | 
					        holder.mView.favButton.isLiked = itm.starred
 | 
				
			||||||
        holder.mView.title.text = Html.fromHtml(itm.title)
 | 
					        holder.mView.title.text = Html.fromHtml(itm.title)
 | 
				
			||||||
 | 
					        holder.mView.title.setOnTouchListener(LinkOnTouchListener())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        holder.mView.title.setLinkTextColor(appColors.colorAccent)
 | 
					        holder.mView.title.setLinkTextColor(appColors.colorAccent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -79,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()) {
 | 
				
			||||||
@@ -92,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
 | 
				
			||||||
@@ -114,6 +123,7 @@ class ItemCardAdapter(
 | 
				
			|||||||
            mView.favButton.setOnLikeListener(object : OnLikeListener {
 | 
					            mView.favButton.setOnLikeListener(object : OnLikeListener {
 | 
				
			||||||
                override fun liked(likeButton: LikeButton) {
 | 
					                override fun liked(likeButton: LikeButton) {
 | 
				
			||||||
                    val (id) = items[adapterPosition]
 | 
					                    val (id) = items[adapterPosition]
 | 
				
			||||||
 | 
					                    if (c.isNetworkAccessible(null)) {
 | 
				
			||||||
                        api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
 | 
					                        api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
                            override fun onResponse(
 | 
					                            override fun onResponse(
 | 
				
			||||||
                                call: Call<SuccessResponse>,
 | 
					                                call: Call<SuccessResponse>,
 | 
				
			||||||
@@ -133,10 +143,16 @@ class ItemCardAdapter(
 | 
				
			|||||||
                                ).show()
 | 
					                                ).show()
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        thread {
 | 
				
			||||||
 | 
					                            db.actionsDao().insertAllActions(ActionEntity(id, false, false, true, false))
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                override fun unLiked(likeButton: LikeButton) {
 | 
					                override fun unLiked(likeButton: LikeButton) {
 | 
				
			||||||
                    val (id) = items[adapterPosition]
 | 
					                    val (id) = items[adapterPosition]
 | 
				
			||||||
 | 
					                    if (c.isNetworkAccessible(null)) {
 | 
				
			||||||
                        api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
 | 
					                        api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
                            override fun onResponse(
 | 
					                            override fun onResponse(
 | 
				
			||||||
                                call: Call<SuccessResponse>,
 | 
					                                call: Call<SuccessResponse>,
 | 
				
			||||||
@@ -156,11 +172,17 @@ class ItemCardAdapter(
 | 
				
			|||||||
                                ).show()
 | 
					                                ).show()
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        thread {
 | 
				
			||||||
 | 
					                            db.actionsDao().insertAllActions(ActionEntity(id, false, false, false, true))
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,19 +2,26 @@ package apps.amine.bou.readerforselfoss.adapters
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.support.constraint.ConstraintLayout
 | 
					import androidx.constraintlayout.widget.ConstraintLayout
 | 
				
			||||||
import android.support.v7.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import android.text.Html
 | 
					import android.text.Html
 | 
				
			||||||
 | 
					import android.text.Spannable
 | 
				
			||||||
 | 
					import android.text.style.ClickableSpan
 | 
				
			||||||
import android.util.TypedValue
 | 
					import android.util.TypedValue
 | 
				
			||||||
import android.view.LayoutInflater
 | 
					import android.view.LayoutInflater
 | 
				
			||||||
 | 
					import android.view.MotionEvent
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
import android.view.ViewGroup
 | 
					import android.view.ViewGroup
 | 
				
			||||||
 | 
					import android.widget.TextView
 | 
				
			||||||
import android.widget.Toast
 | 
					import android.widget.Toast
 | 
				
			||||||
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
 | 
				
			||||||
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.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.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.bitmapCenterCrop
 | 
					import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
 | 
				
			||||||
@@ -39,18 +46,18 @@ class ItemListAdapter(
 | 
				
			|||||||
    override val app: Activity,
 | 
					    override val app: Activity,
 | 
				
			||||||
    override var items: ArrayList<Item>,
 | 
					    override var items: ArrayList<Item>,
 | 
				
			||||||
    override val api: SelfossApi,
 | 
					    override val api: SelfossApi,
 | 
				
			||||||
 | 
					    override val db: AppDatabase,
 | 
				
			||||||
    private val helper: CustomTabActivityHelper,
 | 
					    private val helper: CustomTabActivityHelper,
 | 
				
			||||||
    private val clickBehavior: Boolean,
 | 
					 | 
				
			||||||
    private val internalBrowser: Boolean,
 | 
					    private val internalBrowser: Boolean,
 | 
				
			||||||
    private val articleViewer: Boolean,
 | 
					    private val articleViewer: Boolean,
 | 
				
			||||||
    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
 | 
				
			||||||
    private val c: Context = app.baseContext
 | 
					    private val c: Context = app.baseContext
 | 
				
			||||||
    private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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(
 | 
				
			||||||
@@ -67,6 +74,8 @@ class ItemListAdapter(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        holder.mView.title.text = Html.fromHtml(itm.title)
 | 
					        holder.mView.title.text = Html.fromHtml(itm.title)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        holder.mView.title.setOnTouchListener(LinkOnTouchListener())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        holder.mView.title.setLinkTextColor(appColors.colorAccent)
 | 
					        holder.mView.title.setLinkTextColor(appColors.colorAccent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
 | 
					        holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
 | 
				
			||||||
@@ -101,24 +110,11 @@ 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)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // TODO: maybe handle this differently. It crashes when changing tab
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            if (bars[position]) {
 | 
					 | 
				
			||||||
                holder.mView.actionBar.visibility = View.VISIBLE
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                holder.mView.actionBar.visibility = View.GONE
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } catch (e: IndexOutOfBoundsException) {
 | 
					 | 
				
			||||||
            holder.mView.actionBar.visibility = View.GONE
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        holder.mView.favButton.isLiked = itm.starred
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun getItemCount(): Int = items.size
 | 
					    override fun getItemCount(): Int = items.size
 | 
				
			||||||
@@ -126,76 +122,13 @@ class ItemListAdapter(
 | 
				
			|||||||
    inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
 | 
					    inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        init {
 | 
					        init {
 | 
				
			||||||
            handleClickListeners()
 | 
					 | 
				
			||||||
            handleCustomTabActions()
 | 
					            handleCustomTabActions()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private fun handleClickListeners() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mView.favButton.setOnLikeListener(object : OnLikeListener {
 | 
					 | 
				
			||||||
                override fun liked(likeButton: LikeButton) {
 | 
					 | 
				
			||||||
                    val (id) = items[adapterPosition]
 | 
					 | 
				
			||||||
                    api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
 | 
					 | 
				
			||||||
                        override fun onResponse(
 | 
					 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                            response: Response<SuccessResponse>
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        override fun onFailure(
 | 
					 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                            t: Throwable
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                            mView.favButton.isLiked = false
 | 
					 | 
				
			||||||
                            Toast.makeText(
 | 
					 | 
				
			||||||
                                c,
 | 
					 | 
				
			||||||
                                R.string.cant_mark_favortie,
 | 
					 | 
				
			||||||
                                Toast.LENGTH_SHORT
 | 
					 | 
				
			||||||
                            ).show()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                override fun unLiked(likeButton: LikeButton) {
 | 
					 | 
				
			||||||
                    val (id) = items[adapterPosition]
 | 
					 | 
				
			||||||
                    api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
 | 
					 | 
				
			||||||
                        override fun onResponse(
 | 
					 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                            response: Response<SuccessResponse>
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        override fun onFailure(
 | 
					 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                            t: Throwable
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                            mView.favButton.isLiked = true
 | 
					 | 
				
			||||||
                            Toast.makeText(
 | 
					 | 
				
			||||||
                                c,
 | 
					 | 
				
			||||||
                                R.string.cant_unmark_favortie,
 | 
					 | 
				
			||||||
                                Toast.LENGTH_SHORT
 | 
					 | 
				
			||||||
                            ).show()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mView.shareBtn.setOnClickListener {
 | 
					 | 
				
			||||||
                c.shareLink(items[adapterPosition].getLinkDecoded())
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mView.browserBtn.setOnClickListener {
 | 
					 | 
				
			||||||
                c.openInBrowserAsNewTask(items[adapterPosition])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private fun handleCustomTabActions() {
 | 
					        private fun handleCustomTabActions() {
 | 
				
			||||||
            val customTabsIntent = c.buildCustomTabsIntent()
 | 
					            val customTabsIntent = c.buildCustomTabsIntent()
 | 
				
			||||||
            helper.bindCustomTabsService(app)
 | 
					            helper.bindCustomTabsService(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!clickBehavior) {
 | 
					 | 
				
			||||||
            mView.setOnClickListener {
 | 
					            mView.setOnClickListener {
 | 
				
			||||||
                c.openItemUrl(
 | 
					                c.openItemUrl(
 | 
				
			||||||
                    items,
 | 
					                    items,
 | 
				
			||||||
@@ -207,34 +140,6 @@ class ItemListAdapter(
 | 
				
			|||||||
                    app
 | 
					                    app
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
                mView.setOnLongClickListener {
 | 
					 | 
				
			||||||
                    actionBarShowHide()
 | 
					 | 
				
			||||||
                    true
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                mView.setOnClickListener { actionBarShowHide() }
 | 
					 | 
				
			||||||
                mView.setOnLongClickListener {
 | 
					 | 
				
			||||||
                    c.openItemUrl(
 | 
					 | 
				
			||||||
                        items,
 | 
					 | 
				
			||||||
                        adapterPosition,
 | 
					 | 
				
			||||||
                        items[adapterPosition].getLinkDecoded(),
 | 
					 | 
				
			||||||
                        customTabsIntent,
 | 
					 | 
				
			||||||
                        internalBrowser,
 | 
					 | 
				
			||||||
                        articleViewer,
 | 
					 | 
				
			||||||
                        app
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    true
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private fun actionBarShowHide() {
 | 
					 | 
				
			||||||
            bars[adapterPosition] = true
 | 
					 | 
				
			||||||
            if (mView.actionBar.visibility == View.GONE) {
 | 
					 | 
				
			||||||
                mView.actionBar.visibility = View.VISIBLE
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                mView.actionBar.visibility = View.GONE
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,29 +2,37 @@ package apps.amine.bou.readerforselfoss.adapters
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.graphics.Color
 | 
					import android.graphics.Color
 | 
				
			||||||
import android.support.design.widget.Snackbar
 | 
					import com.google.android.material.snackbar.Snackbar
 | 
				
			||||||
import android.support.v7.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import android.widget.TextView
 | 
					import android.widget.TextView
 | 
				
			||||||
import android.widget.Toast
 | 
					import android.widget.Toast
 | 
				
			||||||
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
 | 
				
			||||||
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.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.persistence.toEntity
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.succeeded
 | 
					import apps.amine.bou.readerforselfoss.utils.succeeded
 | 
				
			||||||
import org.acra.ACRA
 | 
					import org.acra.ACRA
 | 
				
			||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
import retrofit2.Callback
 | 
					import retrofit2.Callback
 | 
				
			||||||
import retrofit2.Response
 | 
					import retrofit2.Response
 | 
				
			||||||
 | 
					import kotlin.concurrent.thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>() {
 | 
					abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>() {
 | 
				
			||||||
    abstract var items: ArrayList<Item>
 | 
					    abstract var items: ArrayList<Item>
 | 
				
			||||||
    abstract val api: SelfossApi
 | 
					    abstract val api: SelfossApi
 | 
				
			||||||
 | 
					    abstract val db: AppDatabase
 | 
				
			||||||
    abstract val debugReadingItems: Boolean
 | 
					    abstract val debugReadingItems: Boolean
 | 
				
			||||||
    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>) {
 | 
				
			||||||
@@ -33,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),
 | 
				
			||||||
@@ -42,9 +50,13 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
            .setAction(R.string.undo_string) {
 | 
					            .setAction(R.string.undo_string) {
 | 
				
			||||||
                items.add(position, i)
 | 
					                items.add(position, i)
 | 
				
			||||||
 | 
					                thread {
 | 
				
			||||||
 | 
					                    db.itemsDao().insertAllItems(i.toEntity())
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                notifyItemInserted(position)
 | 
					                notifyItemInserted(position)
 | 
				
			||||||
                updateItems(items)
 | 
					                updateItems(items)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (app.isNetworkAccessible(null)) {
 | 
				
			||||||
                    api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> {
 | 
					                    api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
                        override fun onResponse(
 | 
					                        override fun onResponse(
 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					                            call: Call<SuccessResponse>,
 | 
				
			||||||
@@ -54,28 +66,92 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                        override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
					                        override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
				
			||||||
                            items.remove(i)
 | 
					                            items.remove(i)
 | 
				
			||||||
 | 
					                            thread {
 | 
				
			||||||
 | 
					                                db.itemsDao().delete(i.toEntity())
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
                            notifyItemRemoved(position)
 | 
					                            notifyItemRemoved(position)
 | 
				
			||||||
                            updateItems(items)
 | 
					                            updateItems(items)
 | 
				
			||||||
                        doUnmark(i, position)
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    thread {
 | 
				
			||||||
 | 
					                        db.actionsDao().insertAllActions(ActionEntity(i.id, false, true, false, false))
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val view = s.view
 | 
					        val view = s.view
 | 
				
			||||||
        val tv: TextView = view.findViewById(android.support.design.R.id.snackbar_text)
 | 
					        val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
 | 
				
			||||||
        tv.setTextColor(Color.WHITE)
 | 
					        tv.setTextColor(Color.WHITE)
 | 
				
			||||||
        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) {
 | 
				
			||||||
        val i = items[position]
 | 
					        val i = items[position]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (i.unread) {
 | 
				
			||||||
 | 
					            readItemAtIndex(position)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            unreadItemAtIndex(position)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun readItemAtIndex(position: Int) {
 | 
				
			||||||
 | 
					        val i = items[position]
 | 
				
			||||||
        items.remove(i)
 | 
					        items.remove(i)
 | 
				
			||||||
        notifyItemRemoved(position)
 | 
					        notifyItemRemoved(position)
 | 
				
			||||||
        updateItems(items)
 | 
					        updateItems(items)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        thread {
 | 
				
			||||||
 | 
					            db.itemsDao().delete(i.toEntity())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (app.isNetworkAccessible(null)) {
 | 
				
			||||||
            api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
 | 
					            api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
                override fun onResponse(
 | 
					                override fun onResponse(
 | 
				
			||||||
                    call: Call<SuccessResponse>,
 | 
					                    call: Call<SuccessResponse>,
 | 
				
			||||||
@@ -83,7 +159,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()} " +
 | 
				
			||||||
@@ -93,7 +169,8 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
 | 
				
			|||||||
                        ACRA.getErrorReporter().maybeHandleSilentException(Exception(message), app)
 | 
					                        ACRA.getErrorReporter().maybeHandleSilentException(Exception(message), app)
 | 
				
			||||||
                        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) {
 | 
				
			||||||
@@ -106,12 +183,78 @@ 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(position, i)
 | 
				
			||||||
 | 
					                    notifyItemInserted(position)
 | 
				
			||||||
 | 
					                    updateItems(items)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    thread {
 | 
				
			||||||
 | 
					                        db.itemsDao().insertAllItems(i.toEntity())
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            thread {
 | 
				
			||||||
 | 
					                db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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)
 | 
					                    items.add(i)
 | 
				
			||||||
                    notifyItemInserted(position)
 | 
					                    notifyItemInserted(position)
 | 
				
			||||||
                    updateItems(items)
 | 
					                    updateItems(items)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    thread {
 | 
				
			||||||
 | 
					                        db.itemsDao().delete(i.toEntity())
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            thread {
 | 
				
			||||||
 | 
					                db.actionsDao().insertAllActions(ActionEntity(i.id, false, true, false, false))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun addItemAtIndex(item: Item, position: Int) {
 | 
					    fun addItemAtIndex(item: Item, position: Int) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,17 +2,19 @@ package apps.amine.bou.readerforselfoss.adapters
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.support.constraint.ConstraintLayout
 | 
					import androidx.constraintlayout.widget.ConstraintLayout
 | 
				
			||||||
import android.support.v7.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import android.view.LayoutInflater
 | 
					import android.view.LayoutInflater
 | 
				
			||||||
import android.view.ViewGroup
 | 
					import android.view.ViewGroup
 | 
				
			||||||
import android.widget.Button
 | 
					import android.widget.Button
 | 
				
			||||||
import android.widget.Toast
 | 
					import android.widget.Toast
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R
 | 
					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.Sources
 | 
					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.toTextDrawableString
 | 
					import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
 | 
				
			||||||
import com.amulyakhare.textdrawable.TextDrawable
 | 
					import com.amulyakhare.textdrawable.TextDrawable
 | 
				
			||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
					import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
				
			||||||
@@ -23,11 +25,12 @@ import retrofit2.Response
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class SourcesListAdapter(
 | 
					class SourcesListAdapter(
 | 
				
			||||||
    private val app: Activity,
 | 
					    private val app: Activity,
 | 
				
			||||||
    private val items: ArrayList<Sources>,
 | 
					    private val items: ArrayList<Source>,
 | 
				
			||||||
    private val api: SelfossApi
 | 
					    private val api: SelfossApi
 | 
				
			||||||
) : 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(
 | 
				
			||||||
@@ -40,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)
 | 
				
			||||||
@@ -51,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
 | 
				
			||||||
@@ -70,6 +74,7 @@ class SourcesListAdapter(
 | 
				
			|||||||
            val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
 | 
					            val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            deleteBtn.setOnClickListener {
 | 
					            deleteBtn.setOnClickListener {
 | 
				
			||||||
 | 
					                if (c.isNetworkAccessible(null)) {
 | 
				
			||||||
                    val (id) = items[adapterPosition]
 | 
					                    val (id) = items[adapterPosition]
 | 
				
			||||||
                    api.deleteSource(id).enqueue(object : Callback<SuccessResponse> {
 | 
					                    api.deleteSource(id).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
                        override fun onResponse(
 | 
					                        override fun onResponse(
 | 
				
			||||||
@@ -101,3 +106,4 @@ class SourcesListAdapter(
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -91,9 +103,11 @@ class SelfossApi(
 | 
				
			|||||||
                    .build()
 | 
					                    .build()
 | 
				
			||||||
            service = retrofit.create(SelfossService::class.java)
 | 
					            service = retrofit.create(SelfossService::class.java)
 | 
				
			||||||
        } catch (e: IllegalArgumentException) {
 | 
					        } catch (e: IllegalArgumentException) {
 | 
				
			||||||
 | 
					            if (callingActivity != null) {
 | 
				
			||||||
                Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true)
 | 
					                Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun login(): Call<SuccessResponse> =
 | 
					    fun login(): Call<SuccessResponse> =
 | 
				
			||||||
        service.loginToSelfoss(config.userLogin, config.userPassword)
 | 
					        service.loginToSelfoss(config.userLogin, config.userPassword)
 | 
				
			||||||
@@ -125,6 +139,9 @@ class SelfossApi(
 | 
				
			|||||||
    ): Call<List<Item>> =
 | 
					    ): Call<List<Item>> =
 | 
				
			||||||
        getItems("starred", tag, sourceId, search, itemsNumber, offset)
 | 
					        getItems("starred", tag, sourceId, search, itemsNumber, offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun allItems(): Call<List<Item>> =
 | 
				
			||||||
 | 
					        service.allItems(userName, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun getItems(
 | 
					    private fun getItems(
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
        tag: String?,
 | 
					        tag: String?,
 | 
				
			||||||
@@ -159,7 +176,7 @@ class SelfossApi(
 | 
				
			|||||||
    fun update(): Call<String> =
 | 
					    fun update(): Call<String> =
 | 
				
			||||||
        service.update(userName, password)
 | 
					        service.update(userName, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val sources: Call<List<Sources>>
 | 
					    val sources: Call<List<Source>>
 | 
				
			||||||
        get() = service.sources(userName, password)
 | 
					        get() = service.sources(userName, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun deleteSource(id: String): Call<SuccessResponse> =
 | 
					    fun deleteSource(id: String): Call<SuccessResponse> =
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,13 +9,13 @@ import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			|||||||
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
 | 
					import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
 | 
				
			||||||
import com.google.gson.annotations.SerializedName
 | 
					import com.google.gson.annotations.SerializedName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private fun constructUrl(config: Config?, path: String, file: String): String {
 | 
					private fun constructUrl(config: Config?, path: String, file: String?): String {
 | 
				
			||||||
    val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon()
 | 
					 | 
				
			||||||
    baseUriBuilder.appendPath(path).appendPath(file)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return if (file.isEmptyOrNullOrNullString()) {
 | 
					    return if (file.isEmptyOrNullOrNullString()) {
 | 
				
			||||||
        ""
 | 
					        ""
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
 | 
					        val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon()
 | 
				
			||||||
 | 
					        baseUriBuilder.appendPath(path).appendPath(file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        baseUriBuilder.toString()
 | 
					        baseUriBuilder.toString()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -42,10 +42,10 @@ data class Spout(
 | 
				
			|||||||
    @SerializedName("description") val description: String
 | 
					    @SerializedName("description") val description: String
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data class Sources(
 | 
					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 {
 | 
				
			||||||
@@ -154,3 +154,26 @@ 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)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -27,6 +27,12 @@ internal interface SelfossService {
 | 
				
			|||||||
        @Query("offset") offset: Int
 | 
					        @Query("offset") offset: Int
 | 
				
			||||||
    ): Call<List<Item>>
 | 
					    ): Call<List<Item>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GET("items")
 | 
				
			||||||
 | 
					    fun allItems(
 | 
				
			||||||
 | 
					        @Query("username") username: String,
 | 
				
			||||||
 | 
					        @Query("password") password: String
 | 
				
			||||||
 | 
					    ): Call<List<Item>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Headers("Content-Type: application/x-www-form-urlencoded")
 | 
					    @Headers("Content-Type: application/x-www-form-urlencoded")
 | 
				
			||||||
    @POST("mark/{id}")
 | 
					    @POST("mark/{id}")
 | 
				
			||||||
    fun markAsRead(
 | 
					    fun markAsRead(
 | 
				
			||||||
@@ -95,7 +101,7 @@ internal interface SelfossService {
 | 
				
			|||||||
    fun sources(
 | 
					    fun sources(
 | 
				
			||||||
        @Query("username") username: String,
 | 
					        @Query("username") username: String,
 | 
				
			||||||
        @Query("password") password: String
 | 
					        @Query("password") password: String
 | 
				
			||||||
    ): Call<List<Sources>>
 | 
					    ): Call<List<Source>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @DELETE("source/{id}")
 | 
					    @DELETE("source/{id}")
 | 
				
			||||||
    fun deleteSource(
 | 
					    fun deleteSource(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,152 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.background
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.app.NotificationManager
 | 
				
			||||||
 | 
					import android.app.PendingIntent
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.content.Intent
 | 
				
			||||||
 | 
					import android.preference.PreferenceManager
 | 
				
			||||||
 | 
					import androidx.core.app.NotificationCompat
 | 
				
			||||||
 | 
					import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
 | 
				
			||||||
 | 
					import androidx.core.app.NotificationCompat.PRIORITY_LOW
 | 
				
			||||||
 | 
					import androidx.room.Room
 | 
				
			||||||
 | 
					import androidx.work.Worker
 | 
				
			||||||
 | 
					import androidx.work.WorkerParameters
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.MainActivity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.persistence.toEntity
 | 
				
			||||||
 | 
					import org.acra.ACRA
 | 
				
			||||||
 | 
					import retrofit2.Call
 | 
				
			||||||
 | 
					import retrofit2.Callback
 | 
				
			||||||
 | 
					import retrofit2.Response
 | 
				
			||||||
 | 
					import java.util.*
 | 
				
			||||||
 | 
					import kotlin.concurrent.schedule
 | 
				
			||||||
 | 
					import kotlin.concurrent.thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
 | 
				
			||||||
 | 
					    lateinit var db: AppDatabase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun doWork(): Result {
 | 
				
			||||||
 | 
					        if (context.isNetworkAccessible(null)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val notificationManager =
 | 
				
			||||||
 | 
					                applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val notification = NotificationCompat.Builder(applicationContext, Config.syncChannelId)
 | 
				
			||||||
 | 
					                .setContentTitle(context.getString(R.string.loading_notification_title))
 | 
				
			||||||
 | 
					                .setContentText(context.getString(R.string.loading_notification_text))
 | 
				
			||||||
 | 
					                .setOngoing(true)
 | 
				
			||||||
 | 
					                .setPriority(PRIORITY_LOW)
 | 
				
			||||||
 | 
					                .setChannelId(Config.syncChannelId)
 | 
				
			||||||
 | 
					                .setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            notificationManager.notify(1, notification.build())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val settings =
 | 
				
			||||||
 | 
					                this.context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
				
			||||||
 | 
					            val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context)
 | 
				
			||||||
 | 
					            val notifyNewItems = sharedPref.getBoolean("notify_new_items", false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            db = Room.databaseBuilder(
 | 
				
			||||||
 | 
					                applicationContext,
 | 
				
			||||||
 | 
					                AppDatabase::class.java, "selfoss-database"
 | 
				
			||||||
 | 
					            ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val api = SelfossApi(
 | 
				
			||||||
 | 
					                this.context,
 | 
				
			||||||
 | 
					                null,
 | 
				
			||||||
 | 
					                settings.getBoolean("isSelfSignedCert", false),
 | 
				
			||||||
 | 
					                sharedPref.getString("api_timeout", "-1").toLong(),
 | 
				
			||||||
 | 
					                sharedPref.getBoolean("should_log_everything", false)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            api.allItems().enqueue(object : Callback<List<Item>> {
 | 
				
			||||||
 | 
					                override fun onFailure(call: Call<List<Item>>, t: Throwable) {
 | 
				
			||||||
 | 
					                    Timer("", false).schedule(4000) {
 | 
				
			||||||
 | 
					                        notificationManager.cancel(1)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                override fun onResponse(
 | 
				
			||||||
 | 
					                    call: Call<List<Item>>,
 | 
				
			||||||
 | 
					                    response: Response<List<Item>>
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    thread {
 | 
				
			||||||
 | 
					                        if (response.body() != null) {
 | 
				
			||||||
 | 
					                            val apiItems = (response.body() as ArrayList<Item>)
 | 
				
			||||||
 | 
					                            db.itemsDao().deleteAllItems()
 | 
				
			||||||
 | 
					                            db.itemsDao()
 | 
				
			||||||
 | 
					                                .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) {
 | 
				
			||||||
 | 
					                            notificationManager.cancel(1)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            thread {
 | 
				
			||||||
 | 
					                val actions = db.actionsDao().actions()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                actions.forEach { action ->
 | 
				
			||||||
 | 
					                    when {
 | 
				
			||||||
 | 
					                        action.read -> doAndReportOnFail(api.markItem(action.articleId), action)
 | 
				
			||||||
 | 
					                        action.unread -> doAndReportOnFail(api.unmarkItem(action.articleId), action)
 | 
				
			||||||
 | 
					                        action.starred -> doAndReportOnFail(api.starrItem(action.articleId), action)
 | 
				
			||||||
 | 
					                        action.unstarred -> doAndReportOnFail(
 | 
				
			||||||
 | 
					                            api.unstarrItem(action.articleId),
 | 
				
			||||||
 | 
					                            action
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Result.SUCCESS
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun <T> doAndReportOnFail(call: Call<T>, action: ActionEntity) {
 | 
				
			||||||
 | 
					        call.enqueue(object : Callback<T> {
 | 
				
			||||||
 | 
					            override fun onResponse(
 | 
				
			||||||
 | 
					                call: Call<T>,
 | 
				
			||||||
 | 
					                response: Response<T>
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                thread {
 | 
				
			||||||
 | 
					                    db.actionsDao().delete(action)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            override fun onFailure(call: Call<T>, t: Throwable) {
 | 
				
			||||||
 | 
					                ACRA.getErrorReporter().maybeHandleSilentException(t, context)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,41 +1,54 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.fragments
 | 
					package apps.amine.bou.readerforselfoss.fragments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.Intent
 | 
					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.net.Uri
 | 
					 | 
				
			||||||
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.support.customtabs.CustomTabsIntent
 | 
					import android.view.InflateException
 | 
				
			||||||
import android.support.design.widget.FloatingActionButton
 | 
					import androidx.browser.customtabs.CustomTabsIntent
 | 
				
			||||||
import android.support.v4.app.Fragment
 | 
					import com.google.android.material.floatingactionbutton.FloatingActionButton
 | 
				
			||||||
import android.support.v4.content.ContextCompat
 | 
					import androidx.fragment.app.Fragment
 | 
				
			||||||
import android.support.v4.widget.NestedScrollView
 | 
					import androidx.core.content.ContextCompat
 | 
				
			||||||
import android.support.v7.app.AlertDialog
 | 
					import androidx.core.widget.NestedScrollView
 | 
				
			||||||
import android.view.LayoutInflater
 | 
					import android.view.LayoutInflater
 | 
				
			||||||
import android.view.MenuItem
 | 
					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 apps.amine.bou.readerforselfoss.R
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
 | 
					import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
 | 
					import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
 | 
				
			||||||
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.SuccessResponse
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3
 | 
				
			||||||
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.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.openItemUrl
 | 
					import apps.amine.bou.readerforselfoss.utils.openItemUrl
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.shareLink
 | 
					import apps.amine.bou.readerforselfoss.utils.shareLink
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
 | 
					import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.toPx
 | 
					import apps.amine.bou.readerforselfoss.utils.succeeded
 | 
				
			||||||
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
 | 
				
			||||||
@@ -43,44 +56,61 @@ import retrofit2.Callback
 | 
				
			|||||||
import retrofit2.Response
 | 
					import retrofit2.Response
 | 
				
			||||||
import java.net.MalformedURLException
 | 
					import java.net.MalformedURLException
 | 
				
			||||||
import java.net.URL
 | 
					import java.net.URL
 | 
				
			||||||
 | 
					import 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
 | 
				
			||||||
    private lateinit var contentImage: String
 | 
					    private lateinit var contentImage: String
 | 
				
			||||||
    private lateinit var contentTitle: String
 | 
					    private lateinit var contentTitle: String
 | 
				
			||||||
    private var showMalformedUrl: Boolean = false
 | 
					 | 
				
			||||||
    private lateinit var editor: SharedPreferences.Editor
 | 
					    private lateinit var editor: SharedPreferences.Editor
 | 
				
			||||||
    private lateinit var fab: FloatingActionButton
 | 
					    private lateinit var fab: FloatingActionButton
 | 
				
			||||||
    private lateinit var appColors: AppColors
 | 
					    private lateinit var appColors: AppColors
 | 
				
			||||||
 | 
					    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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pageNumber = arguments!!.getInt(ARG_POSITION)
 | 
					        pageNumber = arguments!!.getInt(ARG_POSITION)
 | 
				
			||||||
        allItems = arguments!!.getParcelableArrayList(ARG_ITEMS)
 | 
					        allItems = arguments!!.getParcelableArrayList(ARG_ITEMS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        db = Room.databaseBuilder(
 | 
				
			||||||
 | 
					            context!!,
 | 
				
			||||||
 | 
					            AppDatabase::class.java, "selfoss-database"
 | 
				
			||||||
 | 
					        ).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? {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            rootView = inflater
 | 
					            rootView = inflater
 | 
				
			||||||
                .inflate(R.layout.fragment_article, container, false) as ViewGroup
 | 
					                .inflate(R.layout.fragment_article, container, false) as ViewGroup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -90,25 +120,43 @@ class ArticleFragment : Fragment() {
 | 
				
			|||||||
            contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!)
 | 
					            contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!)
 | 
				
			||||||
            contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
 | 
					            contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fab = rootView.fab
 | 
					            prefs = PreferenceManager.getDefaultSharedPreferences(activity)
 | 
				
			||||||
 | 
					            editor = prefs.edit()
 | 
				
			||||||
 | 
					            fontSize = prefs.getString("reader_font_size", "16").toInt()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            font = prefs.getString("reader_font", "")
 | 
				
			||||||
 | 
					            if (font.isNotEmpty()) {
 | 
				
			||||||
 | 
					                resId = context!!.resources.getIdentifier(font, "font", context!!.packageName)
 | 
				
			||||||
 | 
					                typeface = ResourcesCompat.getFont(context!!, resId)!!
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            refreshAlignment()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val settings = activity!!.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
				
			||||||
 | 
					            val debugReadingItems = prefs.getBoolean("read_debug", false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val api = SelfossApi(
 | 
				
			||||||
 | 
					                context!!,
 | 
				
			||||||
 | 
					                activity!!,
 | 
				
			||||||
 | 
					                settings.getBoolean("isSelfSignedCert", false),
 | 
				
			||||||
 | 
					                prefs.getString("api_timeout", "-1").toLong(),
 | 
				
			||||||
 | 
					                prefs.getBoolean("should_log_everything", false)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            fab = rootView!!.fab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
 | 
					            fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            fab.rippleColor = appColors.colorAccentDark
 | 
					            fab.rippleColor = appColors.colorAccentDark
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val floatingToolbar: FloatingToolbar = rootView.floatingToolbar
 | 
					            val floatingToolbar: FloatingToolbar = rootView!!.floatingToolbar
 | 
				
			||||||
            floatingToolbar.attachFab(fab)
 | 
					            floatingToolbar.attachFab(fab)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            floatingToolbar.background = ColorDrawable(appColors.colorAccent)
 | 
					            floatingToolbar.background = ColorDrawable(appColors.colorAccent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val customTabsIntent = activity!!.buildCustomTabsIntent()
 | 
					            val customTabsIntent = activity!!.buildCustomTabsIntent()
 | 
				
			||||||
            mCustomTabActivityHelper = CustomTabActivityHelper()
 | 
					            mCustomTabActivityHelper = CustomTabActivityHelper()
 | 
				
			||||||
        mCustomTabActivityHelper.bindCustomTabsService(activity)
 | 
					            mCustomTabActivityHelper!!.bindCustomTabsService(activity)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
 | 
					 | 
				
			||||||
        editor = prefs.edit()
 | 
					 | 
				
			||||||
        fontSize = prefs.getString("reader_font_size", "14").toInt()
 | 
					 | 
				
			||||||
        showMalformedUrl = prefs.getBoolean("show_error_malformed_url", true)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            floatingToolbar.setClickListener(
 | 
					            floatingToolbar.setClickListener(
 | 
				
			||||||
@@ -116,7 +164,7 @@ class ArticleFragment : Fragment() {
 | 
				
			|||||||
                    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(),
 | 
				
			||||||
@@ -126,6 +174,41 @@ class ArticleFragment : Fragment() {
 | 
				
			|||||||
                                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 {
 | 
				
			||||||
 | 
					                                thread {
 | 
				
			||||||
 | 
					                                    db.actionsDao().insertAllActions(ActionEntity(allItems[pageNumber.toInt()].id, false, true, false, false))
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
                            else -> Unit
 | 
					                            else -> Unit
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@@ -135,29 +218,35 @@ class ArticleFragment : Fragment() {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rootView.source.text = contentSource
 | 
					            rootView!!.source.text = contentSource
 | 
				
			||||||
 | 
					            if (typeface != null) {
 | 
				
			||||||
 | 
					                rootView!!.source.typeface = typeface
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (contentText.isEmptyOrNullOrNullString()) {
 | 
					            if (contentText.isEmptyOrNullOrNullString()) {
 | 
				
			||||||
                getContentFromMercury(customTabsIntent, prefs)
 | 
					                getContentFromMercury(customTabsIntent, prefs)
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
            rootView.titleView.text = contentTitle
 | 
					                rootView!!.titleView.text = contentTitle
 | 
				
			||||||
 | 
					                if (typeface != null) {
 | 
				
			||||||
 | 
					                    rootView!!.titleView.typeface = typeface
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            htmlToWebview(contentText, prefs)
 | 
					                htmlToWebview()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
 | 
					                if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
 | 
				
			||||||
                rootView.imageView.visibility = View.VISIBLE
 | 
					                    rootView!!.imageView.visibility = View.VISIBLE
 | 
				
			||||||
                    Glide
 | 
					                    Glide
 | 
				
			||||||
                        .with(context!!)
 | 
					                        .with(context!!)
 | 
				
			||||||
                        .asBitmap()
 | 
					                        .asBitmap()
 | 
				
			||||||
                    .load(contentImage)
 | 
					                        .loadMaybeBasicAuth(config, contentImage)
 | 
				
			||||||
                        .apply(RequestOptions.fitCenterTransform())
 | 
					                        .apply(RequestOptions.fitCenterTransform())
 | 
				
			||||||
                    .into(rootView.imageView)
 | 
					                        .into(rootView!!.imageView)
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                rootView.imageView.visibility = View.GONE
 | 
					                    rootView!!.imageView.visibility = View.GONE
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rootView.nestedScrollView.setOnScrollChangeListener(
 | 
					            rootView!!.nestedScrollView.setOnScrollChangeListener(
 | 
				
			||||||
                NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
 | 
					                NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
 | 
				
			||||||
                    if (scrollY > oldScrollY) {
 | 
					                    if (scrollY > oldScrollY) {
 | 
				
			||||||
                        fab.hide()
 | 
					                        fab.hide()
 | 
				
			||||||
@@ -167,14 +256,39 @@ class ArticleFragment : Fragment() {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } catch (e: InflateException) {
 | 
				
			||||||
 | 
					            AlertDialog.Builder(context!!)
 | 
				
			||||||
 | 
					                .setMessage(context!!.getString(R.string.webview_dialog_issue_message))
 | 
				
			||||||
 | 
					                .setTitle(context!!.getString(R.string.webview_dialog_issue_title))
 | 
				
			||||||
 | 
					                .setPositiveButton(android.R.string.ok
 | 
				
			||||||
 | 
					                ) { dialog, which ->
 | 
				
			||||||
 | 
					                    val sharedPref = PreferenceManager.getDefaultSharedPreferences(context!!)
 | 
				
			||||||
 | 
					                    val editor = sharedPref.edit()
 | 
				
			||||||
 | 
					                    editor.putBoolean("prefer_article_viewer", false)
 | 
				
			||||||
 | 
					                    editor.commit()
 | 
				
			||||||
 | 
					                    activity!!.finish()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .create()
 | 
				
			||||||
 | 
					                .show()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        rootView.progressBar.visibility = View.VISIBLE
 | 
					        if ((context != null && context!!.isNetworkAccessible(null)) || context == null) {
 | 
				
			||||||
 | 
					            rootView!!.progressBar.visibility = View.VISIBLE
 | 
				
			||||||
            val parser = MercuryApi(
 | 
					            val parser = MercuryApi(
 | 
				
			||||||
                prefs.getBoolean("should_log_everything", false)
 | 
					                prefs.getBoolean("should_log_everything", false)
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@@ -189,8 +303,17 @@ 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 {
 | 
				
			||||||
 | 
					                                        // 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
 | 
				
			||||||
 | 
					                                    } catch (e: MalformedURLException) {
 | 
				
			||||||
 | 
					                                        // Mercury returned a relative url. We do nothing.
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
                                } catch (e: Exception) {
 | 
					                                } catch (e: Exception) {
 | 
				
			||||||
                                    if (context != null) {
 | 
					                                    if (context != null) {
 | 
				
			||||||
                                        ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
 | 
					                                        ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
 | 
				
			||||||
@@ -198,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!!)
 | 
				
			||||||
@@ -207,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) {
 | 
				
			||||||
@@ -228,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!!)
 | 
				
			||||||
@@ -259,14 +383,20 @@ 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
 | 
				
			||||||
@@ -278,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
 | 
				
			||||||
@@ -302,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
 | 
				
			||||||
@@ -319,49 +449,33 @@ class ArticleFragment : Fragment() {
 | 
				
			|||||||
            val itemUrl = URL(url)
 | 
					            val itemUrl = URL(url)
 | 
				
			||||||
            baseUrl = itemUrl.protocol + "://" + itemUrl.host
 | 
					            baseUrl = itemUrl.protocol + "://" + itemUrl.host
 | 
				
			||||||
        } catch (e: MalformedURLException) {
 | 
					        } catch (e: MalformedURLException) {
 | 
				
			||||||
            if (showMalformedUrl && context != null) {
 | 
					            ACRA.getErrorReporter().maybeHandleSilentException(e, activity!!)
 | 
				
			||||||
                val alertDialog = AlertDialog.Builder(context!!).create()
 | 
					 | 
				
			||||||
                alertDialog.setTitle("Error")
 | 
					 | 
				
			||||||
                alertDialog.setMessage("You are encountering a bug that I can't solve. Can you please contact me to solve the issue, please ?")
 | 
					 | 
				
			||||||
                alertDialog.setButton(
 | 
					 | 
				
			||||||
                    AlertDialog.BUTTON_POSITIVE,
 | 
					 | 
				
			||||||
                    "Send mail",
 | 
					 | 
				
			||||||
                    { dialog, _ ->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        // This won't be translated because it should only be temporary.
 | 
					 | 
				
			||||||
                        val to = Config.feedbackEmail
 | 
					 | 
				
			||||||
                        val subject= "[ReaderForSelfoss MalformedURLException]"
 | 
					 | 
				
			||||||
                        val body= "Please specify the source, item and spout you are using for the url below : \n ${e.message}"
 | 
					 | 
				
			||||||
                        val mailTo = "mailto:" + to + "?&subject=" + Uri.encode(subject) + "&body=" + Uri.encode(body)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        val emailIntent = Intent(Intent.ACTION_VIEW)
 | 
					 | 
				
			||||||
                        emailIntent.data = Uri.parse(mailTo)
 | 
					 | 
				
			||||||
                        startActivity(emailIntent)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        dialog.dismiss()
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                alertDialog.setButton(
 | 
					 | 
				
			||||||
                    AlertDialog.BUTTON_NEUTRAL,
 | 
					 | 
				
			||||||
                    "Not now",
 | 
					 | 
				
			||||||
                    { dialog, _ -> dialog.dismiss() }
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                alertDialog.setButton(
 | 
					 | 
				
			||||||
                    AlertDialog.BUTTON_NEGATIVE,
 | 
					 | 
				
			||||||
                    "Don't show anymore.",
 | 
					 | 
				
			||||||
                    { dialog, _ ->
 | 
					 | 
				
			||||||
                        editor.putBoolean("show_error_malformed_url", false)
 | 
					 | 
				
			||||||
                        editor.apply()
 | 
					 | 
				
			||||||
                        dialog.dismiss()
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                alertDialog.show()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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,
 | 
				
			||||||
            """<style>
 | 
					            """<html>
 | 
				
			||||||
 | 
					                |<head>
 | 
				
			||||||
 | 
					                |   <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
 | 
					                |   <style>
 | 
				
			||||||
                |      img {
 | 
					                |      img {
 | 
				
			||||||
                |        display: inline-block;
 | 
					                |        display: inline-block;
 | 
				
			||||||
                |        height: auto;
 | 
					                |        height: auto;
 | 
				
			||||||
@@ -375,27 +489,34 @@ 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;
 | 
				
			||||||
                |        width:100%;
 | 
					                |        width:100%;
 | 
				
			||||||
                |        background-color: $stringBackgroundColor;
 | 
					                |        background-color: $stringBackgroundColor;
 | 
				
			||||||
                |}</style>$c""".trimMargin(),
 | 
					                |      }
 | 
				
			||||||
            "text/html; charset=utf-8",
 | 
					                |   </style>
 | 
				
			||||||
 | 
					                |   $fontLinkAndStyle
 | 
				
			||||||
 | 
					                |</head>
 | 
				
			||||||
 | 
					                |<body>
 | 
				
			||||||
 | 
					                |   $contentText
 | 
				
			||||||
 | 
					                |</body>""".trimMargin(),
 | 
				
			||||||
 | 
					            "text/html",
 | 
				
			||||||
            "utf-8",
 | 
					            "utf-8",
 | 
				
			||||||
            null
 | 
					            null
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.persistence.dao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.room.Dao
 | 
				
			||||||
 | 
					import androidx.room.Delete
 | 
				
			||||||
 | 
					import androidx.room.Insert
 | 
				
			||||||
 | 
					import androidx.room.OnConflictStrategy
 | 
				
			||||||
 | 
					import androidx.room.Query
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Dao
 | 
				
			||||||
 | 
					interface ActionsDao {
 | 
				
			||||||
 | 
					    @Query("SELECT * FROM actions order by id asc")
 | 
				
			||||||
 | 
					    fun actions(): List<ActionEntity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
				
			||||||
 | 
					    fun insertAllActions(vararg actions: ActionEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Query("DELETE FROM actions WHERE articleid = :article_id AND read = 1")
 | 
				
			||||||
 | 
					    fun deleteReadActionForArticle(article_id: String)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Delete
 | 
				
			||||||
 | 
					    fun delete(action: ActionEntity)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.persistence.dao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.room.Delete
 | 
				
			||||||
 | 
					import androidx.room.Dao
 | 
				
			||||||
 | 
					import androidx.room.Insert
 | 
				
			||||||
 | 
					import androidx.room.OnConflictStrategy
 | 
				
			||||||
 | 
					import androidx.room.Query
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Dao
 | 
				
			||||||
 | 
					interface DrawerDataDao {
 | 
				
			||||||
 | 
					    @Query("SELECT * FROM tags")
 | 
				
			||||||
 | 
					    fun tags(): List<TagEntity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Query("SELECT * FROM sources")
 | 
				
			||||||
 | 
					    fun sources(): List<SourceEntity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
				
			||||||
 | 
					    fun insertAllTags(vararg tags: TagEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
				
			||||||
 | 
					    fun insertAllSources(vararg sources: SourceEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Query("DELETE FROM tags")
 | 
				
			||||||
 | 
					    fun deleteAllTags()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Query("DELETE FROM sources")
 | 
				
			||||||
 | 
					    fun deleteAllSources()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Delete
 | 
				
			||||||
 | 
					    fun deleteTag(tag: TagEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Delete
 | 
				
			||||||
 | 
					    fun deleteSource(source: SourceEntity)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.persistence.dao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.room.Dao
 | 
				
			||||||
 | 
					import androidx.room.Delete
 | 
				
			||||||
 | 
					import androidx.room.Insert
 | 
				
			||||||
 | 
					import androidx.room.OnConflictStrategy
 | 
				
			||||||
 | 
					import androidx.room.Query
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity
 | 
				
			||||||
 | 
					import androidx.room.Update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Dao
 | 
				
			||||||
 | 
					interface ItemsDao {
 | 
				
			||||||
 | 
					    @Query("SELECT * FROM items order by id desc")
 | 
				
			||||||
 | 
					    fun items(): List<ItemEntity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
				
			||||||
 | 
					    fun insertAllItems(vararg items: ItemEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Query("DELETE FROM items")
 | 
				
			||||||
 | 
					    fun deleteAllItems()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Delete
 | 
				
			||||||
 | 
					    fun delete(item: ItemEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Update
 | 
				
			||||||
 | 
					    fun updateItem(item: ItemEntity)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.persistence.database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.room.RoomDatabase
 | 
				
			||||||
 | 
					import androidx.room.Database
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.dao.ActionsDao
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.dao.DrawerDataDao
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.dao.ItemsDao
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Database(entities = [TagEntity::class, SourceEntity::class, ItemEntity::class, ActionEntity::class], version = 3)
 | 
				
			||||||
 | 
					abstract class AppDatabase : RoomDatabase() {
 | 
				
			||||||
 | 
					    abstract fun drawerDataDao(): DrawerDataDao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    abstract fun itemsDao(): ItemsDao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    abstract fun actionsDao(): ActionsDao
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.persistence.entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.room.ColumnInfo
 | 
				
			||||||
 | 
					import androidx.room.Entity
 | 
				
			||||||
 | 
					import androidx.room.PrimaryKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Entity(tableName = "actions")
 | 
				
			||||||
 | 
					data class ActionEntity(
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "articleid")
 | 
				
			||||||
 | 
					    val articleId: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "read")
 | 
				
			||||||
 | 
					    val read: Boolean,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "unread")
 | 
				
			||||||
 | 
					    val unread: Boolean,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "starred")
 | 
				
			||||||
 | 
					    var starred: Boolean,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "unstarred")
 | 
				
			||||||
 | 
					    var unstarred: Boolean
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    @PrimaryKey(autoGenerate = true)
 | 
				
			||||||
 | 
					    var id: Int = 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.persistence.entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.room.ColumnInfo
 | 
				
			||||||
 | 
					import androidx.room.Entity
 | 
				
			||||||
 | 
					import androidx.room.PrimaryKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Entity(tableName = "tags")
 | 
				
			||||||
 | 
					data class TagEntity(
 | 
				
			||||||
 | 
					    @PrimaryKey
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "tag")
 | 
				
			||||||
 | 
					    val tag: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "color")
 | 
				
			||||||
 | 
					    val color: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "unread")
 | 
				
			||||||
 | 
					    val unread: Int
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Entity(tableName = "sources")
 | 
				
			||||||
 | 
					data class SourceEntity(
 | 
				
			||||||
 | 
					    @PrimaryKey
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "id")
 | 
				
			||||||
 | 
					    val id: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "title")
 | 
				
			||||||
 | 
					    val title: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "tags")
 | 
				
			||||||
 | 
					    val tags: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "spout")
 | 
				
			||||||
 | 
					    val spout: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "error")
 | 
				
			||||||
 | 
					    val error: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "icon")
 | 
				
			||||||
 | 
					    val icon: String
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.persistence.entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.room.ColumnInfo
 | 
				
			||||||
 | 
					import androidx.room.Entity
 | 
				
			||||||
 | 
					import androidx.room.PrimaryKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Entity(tableName = "items")
 | 
				
			||||||
 | 
					data class ItemEntity(
 | 
				
			||||||
 | 
					    @PrimaryKey
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "id")
 | 
				
			||||||
 | 
					    val id: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "datetime")
 | 
				
			||||||
 | 
					    val datetime: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "title")
 | 
				
			||||||
 | 
					    val title: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "content")
 | 
				
			||||||
 | 
					    val content: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "unread")
 | 
				
			||||||
 | 
					    val unread: Boolean,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "starred")
 | 
				
			||||||
 | 
					    var starred: Boolean,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "thumbnail")
 | 
				
			||||||
 | 
					    val thumbnail: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "icon")
 | 
				
			||||||
 | 
					    val icon: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "link")
 | 
				
			||||||
 | 
					    val link: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "sourcetitle")
 | 
				
			||||||
 | 
					    val sourcetitle: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "tags")
 | 
				
			||||||
 | 
					    val tags: String
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.persistence.migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.sqlite.db.SupportSQLiteDatabase
 | 
				
			||||||
 | 
					import androidx.room.migration.Migration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					val MIGRATION_1_2: Migration = object : Migration(1, 2) {
 | 
				
			||||||
 | 
					    override fun migrate(database: SupportSQLiteDatabase) {
 | 
				
			||||||
 | 
					        database.execSQL("CREATE TABLE IF NOT EXISTS `items` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					val MIGRATION_2_3: Migration = object : Migration(2, 3) {
 | 
				
			||||||
 | 
					    override fun migrate(database: SupportSQLiteDatabase) {
 | 
				
			||||||
 | 
					        database.execSQL("CREATE TABLE IF NOT EXISTS `actions` (`id` INTEGER NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL, PRIMARY KEY(`id`))")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,13 +4,13 @@ import android.content.res.Configuration;
 | 
				
			|||||||
import android.os.Build;
 | 
					import android.os.Build;
 | 
				
			||||||
import android.os.Bundle;
 | 
					import android.os.Bundle;
 | 
				
			||||||
import android.preference.PreferenceActivity;
 | 
					import android.preference.PreferenceActivity;
 | 
				
			||||||
import android.support.annotation.LayoutRes;
 | 
					import androidx.annotation.LayoutRes;
 | 
				
			||||||
import android.support.annotation.NonNull;
 | 
					import androidx.annotation.NonNull;
 | 
				
			||||||
import android.support.annotation.Nullable;
 | 
					import androidx.annotation.Nullable;
 | 
				
			||||||
import android.support.design.widget.AppBarLayout;
 | 
					import com.google.android.material.appbar.AppBarLayout;
 | 
				
			||||||
import android.support.v7.app.ActionBar;
 | 
					import androidx.appcompat.app.ActionBar;
 | 
				
			||||||
import android.support.v7.app.AppCompatDelegate;
 | 
					import androidx.appcompat.app.AppCompatDelegate;
 | 
				
			||||||
import android.support.v7.widget.Toolbar;
 | 
					import androidx.appcompat.widget.Toolbar;
 | 
				
			||||||
import android.view.LayoutInflater;
 | 
					import android.view.LayoutInflater;
 | 
				
			||||||
import android.view.MenuInflater;
 | 
					import android.view.MenuInflater;
 | 
				
			||||||
import android.view.View;
 | 
					import android.view.View;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
@@ -19,11 +20,12 @@ import android.preference.PreferenceActivity;
 | 
				
			|||||||
import android.preference.PreferenceFragment;
 | 
					import android.preference.PreferenceFragment;
 | 
				
			||||||
import android.preference.PreferenceManager;
 | 
					import android.preference.PreferenceManager;
 | 
				
			||||||
import android.preference.SwitchPreference;
 | 
					import android.preference.SwitchPreference;
 | 
				
			||||||
import android.support.v7.app.ActionBar;
 | 
					import androidx.appcompat.app.ActionBar;
 | 
				
			||||||
import android.text.Editable;
 | 
					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;
 | 
				
			||||||
@@ -31,7 +33,6 @@ import android.widget.Toast;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.BuildConfig;
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R;
 | 
					import apps.amine.bou.readerforselfoss.R;
 | 
				
			||||||
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.Config;
 | 
				
			||||||
@@ -125,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 +158,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
 | 
				
			|||||||
        return PreferenceFragment.class.getName().equals(fragmentName)
 | 
					        return PreferenceFragment.class.getName().equals(fragmentName)
 | 
				
			||||||
                || 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)
 | 
				
			||||||
 | 
					                || 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);
 | 
				
			||||||
@@ -153,17 +177,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
 | 
				
			|||||||
            addPreferencesFromResource(R.xml.pref_general);
 | 
					            addPreferencesFromResource(R.xml.pref_general);
 | 
				
			||||||
            setHasOptionsMenu(true);
 | 
					            setHasOptionsMenu(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            SwitchPreference cardViewActive = (SwitchPreference) findPreference("card_view_active");
 | 
					 | 
				
			||||||
            final SwitchPreference tabOnTap = (SwitchPreference) findPreference("tab_on_tap");
 | 
					 | 
				
			||||||
            tabOnTap.setEnabled(!cardViewActive.isChecked());
 | 
					 | 
				
			||||||
            cardViewActive.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
 | 
					 | 
				
			||||||
                public boolean onPreferenceChange(Preference preference, Object newValue) {
 | 
					 | 
				
			||||||
                    boolean isEnabled = (Boolean) newValue;
 | 
					 | 
				
			||||||
                    tabOnTap.setEnabled(!isEnabled);
 | 
					 | 
				
			||||||
                    return true;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            EditTextPreference itemsNumber = (EditTextPreference) findPreference("prefer_api_items_number");
 | 
					            EditTextPreference itemsNumber = (EditTextPreference) findPreference("prefer_api_items_number");
 | 
				
			||||||
            itemsNumber.getEditText().setFilters(new InputFilter[]{
 | 
					            itemsNumber.getEditText().setFilters(new InputFilter[]{
 | 
				
			||||||
                    new InputFilter() {
 | 
					                    new InputFilter() {
 | 
				
			||||||
@@ -375,6 +388,47 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 | 
				
			||||||
 | 
					    public static class OfflinePreferenceFragment extends PreferenceFragment {
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public void onCreate(Bundle savedInstanceState) {
 | 
				
			||||||
 | 
					            super.onCreate(savedInstanceState);
 | 
				
			||||||
 | 
					            addPreferencesFromResource(R.xml.pref_offline);
 | 
				
			||||||
 | 
					            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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @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) {
 | 
				
			||||||
        int id = item.getItemId();
 | 
					        int id = item.getItemId();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,8 @@ package apps.amine.bou.readerforselfoss.themes
 | 
				
			|||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					import android.preference.PreferenceManager
 | 
				
			||||||
import android.support.annotation.ColorInt
 | 
					import androidx.annotation.ColorInt
 | 
				
			||||||
import android.support.v7.view.ContextThemeWrapper
 | 
					import androidx.appcompat.view.ContextThemeWrapper
 | 
				
			||||||
import android.util.TypedValue
 | 
					import android.util.TypedValue
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
import android.view.LayoutInflater
 | 
					import android.view.LayoutInflater
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.transformers
 | 
					package apps.amine.bou.readerforselfoss.transformers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.support.v4.view.ViewPager
 | 
					import androidx.viewpager.widget.ViewPager
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DepthPageTransformer : ViewPager.PageTransformer {
 | 
					class DepthPageTransformer : ViewPager.PageTransformer {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -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(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,10 @@ class Config(c: Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const val trackerUrl = "https://github.com/aminecmi/ReaderforSelfoss/issues"
 | 
					        const val trackerUrl = "https://github.com/aminecmi/ReaderforSelfoss/issues"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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()))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -2,18 +2,25 @@ 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
 | 
				
			||||||
import android.net.Uri
 | 
					import android.net.Uri
 | 
				
			||||||
import android.support.customtabs.CustomTabsIntent
 | 
					import android.text.Spannable
 | 
				
			||||||
 | 
					import android.text.style.ClickableSpan
 | 
				
			||||||
 | 
					import androidx.browser.customtabs.CustomTabsIntent
 | 
				
			||||||
import android.util.Patterns
 | 
					import android.util.Patterns
 | 
				
			||||||
 | 
					import android.view.MotionEvent
 | 
				
			||||||
 | 
					import android.view.View
 | 
				
			||||||
 | 
					import android.widget.TextView
 | 
				
			||||||
import android.widget.Toast
 | 
					import android.widget.Toast
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.ReaderActivity
 | 
					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 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,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)
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
        app.startActivity(intent)
 | 
					        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) {
 | 
				
			||||||
@@ -146,3 +157,40 @@ fun Context.openInBrowserAsNewTask(i: Item) {
 | 
				
			|||||||
    intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
 | 
					    intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
 | 
				
			||||||
    startActivity(intent)
 | 
					    startActivity(intent)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LinkOnTouchListener: View.OnTouchListener {
 | 
				
			||||||
 | 
					    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
 | 
				
			||||||
 | 
					        var ret = false
 | 
				
			||||||
 | 
					        val widget: TextView = v as TextView
 | 
				
			||||||
 | 
					        val text: CharSequence = widget.text
 | 
				
			||||||
 | 
					        val stext = Spannable.Factory.getInstance().newSpannable(text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val action = event!!.action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (action == MotionEvent.ACTION_UP ||
 | 
				
			||||||
 | 
					            action == MotionEvent.ACTION_DOWN) {
 | 
				
			||||||
 | 
					            var x: Float = event.x
 | 
				
			||||||
 | 
					            var y: Float = event.y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            x -= widget.totalPaddingLeft
 | 
				
			||||||
 | 
					            y -= widget.totalPaddingTop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            x += widget.scrollX
 | 
				
			||||||
 | 
					            y += widget.scrollY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val layout = widget.layout
 | 
				
			||||||
 | 
					            val line = layout.getLineForVertical(y.toInt())
 | 
				
			||||||
 | 
					            val off = layout.getOffsetForHorizontal(line, x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val link = stext.getSpans(off, off, ClickableSpan::class.java)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (link.isNotEmpty()) {
 | 
				
			||||||
 | 
					                if (action == MotionEvent.ACTION_UP) {
 | 
				
			||||||
 | 
					                    link[0].onClick(widget)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                ret = true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return ret
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,54 +0,0 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.utils
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.content.Context
 | 
					 | 
				
			||||||
import android.support.design.widget.CoordinatorLayout
 | 
					 | 
				
			||||||
import android.support.design.widget.FloatingActionButton
 | 
					 | 
				
			||||||
import android.util.AttributeSet
 | 
					 | 
				
			||||||
import android.view.View
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ScrollAwareFABBehavior(
 | 
					 | 
				
			||||||
    context: Context,
 | 
					 | 
				
			||||||
    attrs: AttributeSet
 | 
					 | 
				
			||||||
) : CoordinatorLayout.Behavior<FloatingActionButton>() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun onStartNestedScroll(
 | 
					 | 
				
			||||||
        coordinatorLayout: CoordinatorLayout,
 | 
					 | 
				
			||||||
        child: FloatingActionButton,
 | 
					 | 
				
			||||||
        directTargetChild: View,
 | 
					 | 
				
			||||||
        target: View,
 | 
					 | 
				
			||||||
        nestedScrollAxes: Int
 | 
					 | 
				
			||||||
    ): Boolean {
 | 
					 | 
				
			||||||
        return true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun onNestedScroll(
 | 
					 | 
				
			||||||
        coordinatorLayout: CoordinatorLayout,
 | 
					 | 
				
			||||||
        child: FloatingActionButton,
 | 
					 | 
				
			||||||
        target: View,
 | 
					 | 
				
			||||||
        dxConsumed: Int,
 | 
					 | 
				
			||||||
        dyConsumed: Int,
 | 
					 | 
				
			||||||
        dxUnconsumed: Int,
 | 
					 | 
				
			||||||
        dyUnconsumed: Int
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        super.onNestedScroll(
 | 
					 | 
				
			||||||
            coordinatorLayout,
 | 
					 | 
				
			||||||
            child,
 | 
					 | 
				
			||||||
            target,
 | 
					 | 
				
			||||||
            dxConsumed,
 | 
					 | 
				
			||||||
            dyConsumed,
 | 
					 | 
				
			||||||
            dxUnconsumed,
 | 
					 | 
				
			||||||
            dyUnconsumed
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        if (dyConsumed > 0 && child.visibility == View.VISIBLE) {
 | 
					 | 
				
			||||||
            child.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
 | 
					 | 
				
			||||||
                override fun onHidden(fab: FloatingActionButton?) {
 | 
					 | 
				
			||||||
                    super.onHidden(fab)
 | 
					 | 
				
			||||||
                    fab!!.visibility = View.INVISIBLE
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        } else if (dyConsumed < 0 && child.visibility != View.VISIBLE) {
 | 
					 | 
				
			||||||
            child.show()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -4,10 +4,10 @@ package apps.amine.bou.readerforselfoss.utils.customtabs;
 | 
				
			|||||||
import android.app.Activity;
 | 
					import android.app.Activity;
 | 
				
			||||||
import android.net.Uri;
 | 
					import android.net.Uri;
 | 
				
			||||||
import android.os.Bundle;
 | 
					import android.os.Bundle;
 | 
				
			||||||
import android.support.customtabs.CustomTabsClient;
 | 
					import androidx.browser.customtabs.CustomTabsClient;
 | 
				
			||||||
import android.support.customtabs.CustomTabsIntent;
 | 
					import androidx.browser.customtabs.CustomTabsIntent;
 | 
				
			||||||
import android.support.customtabs.CustomTabsServiceConnection;
 | 
					import androidx.browser.customtabs.CustomTabsServiceConnection;
 | 
				
			||||||
import android.support.customtabs.CustomTabsSession;
 | 
					import androidx.browser.customtabs.CustomTabsSession;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ import android.content.IntentFilter;
 | 
				
			|||||||
import android.content.pm.PackageManager;
 | 
					import android.content.pm.PackageManager;
 | 
				
			||||||
import android.content.pm.ResolveInfo;
 | 
					import android.content.pm.ResolveInfo;
 | 
				
			||||||
import android.net.Uri;
 | 
					import android.net.Uri;
 | 
				
			||||||
import android.support.customtabs.CustomTabsService;
 | 
					import androidx.browser.customtabs.CustomTabsService;
 | 
				
			||||||
import android.text.TextUtils;
 | 
					import android.text.TextUtils;
 | 
				
			||||||
import android.util.Log;
 | 
					import android.util.Log;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,8 @@ package apps.amine.bou.readerforselfoss.utils.customtabs;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.ComponentName;
 | 
					import android.content.ComponentName;
 | 
				
			||||||
import android.support.customtabs.CustomTabsClient;
 | 
					import androidx.browser.customtabs.CustomTabsClient;
 | 
				
			||||||
import android.support.customtabs.CustomTabsServiceConnection;
 | 
					import androidx.browser.customtabs.CustomTabsServiceConnection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.lang.ref.WeakReference;
 | 
					import java.lang.ref.WeakReference;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.utils.customtabs;
 | 
					package apps.amine.bou.readerforselfoss.utils.customtabs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.support.customtabs.CustomTabsClient;
 | 
					import androidx.browser.customtabs.CustomTabsClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public interface ServiceConnectionCallback {
 | 
					public interface ServiceConnectionCallback {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomBaseViewHolder.java */
 | 
					/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomBaseViewHolder.java */
 | 
				
			||||||
package apps.amine.bou.readerforselfoss.utils.drawer
 | 
					package apps.amine.bou.readerforselfoss.utils.drawer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.support.v7.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
import android.widget.ImageView
 | 
					import android.widget.ImageView
 | 
				
			||||||
import android.widget.TextView
 | 
					import android.widget.TextView
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,10 +2,10 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.utils.drawer
 | 
					package apps.amine.bou.readerforselfoss.utils.drawer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.net.Uri
 | 
					import android.net.Uri
 | 
				
			||||||
import android.support.annotation.ColorInt
 | 
					import androidx.annotation.ColorInt
 | 
				
			||||||
import android.support.annotation.ColorRes
 | 
					import androidx.annotation.ColorRes
 | 
				
			||||||
import android.support.annotation.StringRes
 | 
					import androidx.annotation.StringRes
 | 
				
			||||||
import android.support.v7.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.mikepenz.materialdrawer.holder.ColorHolder
 | 
					import com.mikepenz.materialdrawer.holder.ColorHolder
 | 
				
			||||||
import com.mikepenz.materialdrawer.holder.ImageHolder
 | 
					import com.mikepenz.materialdrawer.holder.ImageHolder
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomUrlPrimaryDrawerItem.java */
 | 
					/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomUrlPrimaryDrawerItem.java */
 | 
				
			||||||
package apps.amine.bou.readerforselfoss.utils.drawer
 | 
					package apps.amine.bou.readerforselfoss.utils.drawer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.support.annotation.LayoutRes
 | 
					import androidx.annotation.LayoutRes
 | 
				
			||||||
import android.support.annotation.StringRes
 | 
					import androidx.annotation.StringRes
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
import android.widget.TextView
 | 
					import android.widget.TextView
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
 | 
					import android.graphics.drawable.Drawable
 | 
				
			||||||
 | 
					import android.util.Base64
 | 
				
			||||||
 | 
					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?) {
 | 
				
			||||||
@@ -37,3 +37,23 @@ fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
 | 
				
			|||||||
                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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.utils.network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.graphics.Color
 | 
				
			||||||
 | 
					import android.net.ConnectivityManager
 | 
				
			||||||
 | 
					import android.net.NetworkInfo
 | 
				
			||||||
 | 
					import android.view.View
 | 
				
			||||||
 | 
					import android.widget.TextView
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
 | 
					import com.google.android.material.snackbar.Snackbar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var snackBarShown = false
 | 
				
			||||||
 | 
					var view: View? = null
 | 
				
			||||||
 | 
					lateinit var s: Snackbar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun Context.isNetworkAccessible(v: View?, overrideOffline: Boolean = false): Boolean {
 | 
				
			||||||
 | 
					    val cm = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
 | 
				
			||||||
 | 
					    val activeNetwork: NetworkInfo? = cm.activeNetworkInfo
 | 
				
			||||||
 | 
					    val networkIsAccessible = activeNetwork != null && activeNetwork.isConnectedOrConnecting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (v != null && (!networkIsAccessible || overrideOffline) && (!snackBarShown || v != view)) {
 | 
				
			||||||
 | 
					        view = v
 | 
				
			||||||
 | 
					        s = Snackbar
 | 
				
			||||||
 | 
					            .make(
 | 
				
			||||||
 | 
					                v,
 | 
				
			||||||
 | 
					                R.string.no_network_connectivity,
 | 
				
			||||||
 | 
					                Snackbar.LENGTH_INDEFINITE
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        s.setAction(android.R.string.ok) {
 | 
				
			||||||
 | 
					            snackBarShown = false
 | 
				
			||||||
 | 
					            s.dismiss()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val view = s.view
 | 
				
			||||||
 | 
					        val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
 | 
				
			||||||
 | 
					        tv.setTextColor(Color.WHITE)
 | 
				
			||||||
 | 
					        s.show()
 | 
				
			||||||
 | 
					        snackBarShown = true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (snackBarShown && networkIsAccessible && !overrideOffline) {
 | 
				
			||||||
 | 
					        s.dismiss()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return if(overrideOffline) overrideOffline else networkIsAccessible
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.utils.persistence
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.Tag
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun TagEntity.toView(): Tag =
 | 
				
			||||||
 | 
					        Tag(
 | 
				
			||||||
 | 
					            this.tag,
 | 
				
			||||||
 | 
					            this.color,
 | 
				
			||||||
 | 
					            this.unread
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun SourceEntity.toView(): Source =
 | 
				
			||||||
 | 
					        Source(
 | 
				
			||||||
 | 
					            this.id,
 | 
				
			||||||
 | 
					            this.title,
 | 
				
			||||||
 | 
					            SelfossTagType(this.tags),
 | 
				
			||||||
 | 
					            this.spout,
 | 
				
			||||||
 | 
					            this.error,
 | 
				
			||||||
 | 
					            this.icon
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun Source.toEntity(): SourceEntity =
 | 
				
			||||||
 | 
					        SourceEntity(
 | 
				
			||||||
 | 
					            this.id,
 | 
				
			||||||
 | 
					            this.title,
 | 
				
			||||||
 | 
					            this.tags.tags,
 | 
				
			||||||
 | 
					            this.spout,
 | 
				
			||||||
 | 
					            this.error,
 | 
				
			||||||
 | 
					            this.icon.orEmpty()
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun Tag.toEntity(): TagEntity =
 | 
				
			||||||
 | 
					        TagEntity(
 | 
				
			||||||
 | 
					            this.tag,
 | 
				
			||||||
 | 
					            this.color,
 | 
				
			||||||
 | 
					            this.unread
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun ItemEntity.toView(): Item =
 | 
				
			||||||
 | 
					        Item(
 | 
				
			||||||
 | 
					            this.id,
 | 
				
			||||||
 | 
					            this.datetime,
 | 
				
			||||||
 | 
					            this.title,
 | 
				
			||||||
 | 
					            this.content,
 | 
				
			||||||
 | 
					            this.unread,
 | 
				
			||||||
 | 
					            this.starred,
 | 
				
			||||||
 | 
					            this.thumbnail,
 | 
				
			||||||
 | 
					            this.icon,
 | 
				
			||||||
 | 
					            this.link,
 | 
				
			||||||
 | 
					            this.sourcetitle,
 | 
				
			||||||
 | 
					            SelfossTagType(this.tags)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun Item.toEntity(): ItemEntity =
 | 
				
			||||||
 | 
					    ItemEntity(
 | 
				
			||||||
 | 
					        this.id,
 | 
				
			||||||
 | 
					        this.datetime,
 | 
				
			||||||
 | 
					        this.title,
 | 
				
			||||||
 | 
					        this.content,
 | 
				
			||||||
 | 
					        this.unread,
 | 
				
			||||||
 | 
					        this.starred,
 | 
				
			||||||
 | 
					        this.thumbnail,
 | 
				
			||||||
 | 
					        this.icon,
 | 
				
			||||||
 | 
					        this.link,
 | 
				
			||||||
 | 
					        this.sourcetitle,
 | 
				
			||||||
 | 
					        this.tags.tags
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
 | 
					        android:width="24dp"
 | 
				
			||||||
 | 
					        android:height="24dp"
 | 
				
			||||||
 | 
					        android:viewportWidth="24.0"
 | 
				
			||||||
 | 
					        android:viewportHeight="24.0">
 | 
				
			||||||
 | 
					    <path
 | 
				
			||||||
 | 
					        android:fillColor="#FFFFFFFF"
 | 
				
			||||||
 | 
					        android:pathData="M3,21h18v-2L3,19v2zM3,17h18v-2L3,15v2zM3,13h18v-2L3,11v2zM3,9h18L21,7L3,7v2zM3,3v2h18L21,3L3,3z"/>
 | 
				
			||||||
 | 
					</vector>
 | 
				
			||||||
@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
 | 
					        android:width="24dp"
 | 
				
			||||||
 | 
					        android:height="24dp"
 | 
				
			||||||
 | 
					        android:viewportWidth="24.0"
 | 
				
			||||||
 | 
					        android:viewportHeight="24.0">
 | 
				
			||||||
 | 
					    <path
 | 
				
			||||||
 | 
					        android:fillColor="#FFFFFFFF"
 | 
				
			||||||
 | 
					        android:pathData="M15,15L3,15v2h12v-2zM15,7L3,7v2h12L15,7zM3,13h18v-2L3,11v2zM3,21h18v-2L3,19v2zM3,3v2h18L21,3L3,3z"/>
 | 
				
			||||||
 | 
					</vector>
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 680 B  | 
| 
		 Before Width: | Height: | Size: 134 B  | 
| 
		 Before Width: | Height: | Size: 239 B  | 
| 
		 Before Width: | Height: | Size: 271 B  | 
| 
		 Before Width: | Height: | Size: 216 B  | 
| 
		 Before Width: | Height: | Size: 206 B  | 
| 
		 Before Width: | Height: | Size: 221 B  | 
| 
		 Before Width: | Height: | Size: 458 B  | 
| 
		 Before Width: | Height: | Size: 275 B  | 
| 
		 Before Width: | Height: | Size: 361 B  | 
| 
		 Before Width: | Height: | Size: 301 B  | 
| 
		 Before Width: | Height: | Size: 551 B  | 
| 
		 Before Width: | Height: | Size: 355 B  | 
| 
		 Before Width: | Height: | Size: 551 B  | 
| 
		 Before Width: | Height: | Size: 204 B  | 
| 
		 Before Width: | Height: | Size: 187 B  | 
| 
		 Before Width: | Height: | Size: 422 B  | 
| 
		 Before Width: | Height: | Size: 473 B  | 
| 
		 Before Width: | Height: | Size: 498 B  | 
| 
		 Before Width: | Height: | Size: 453 B  | 
| 
		 Before Width: | Height: | Size: 398 B  | 
| 
		 Before Width: | Height: | Size: 397 B  | 
| 
		 Before Width: | Height: | Size: 442 B  | 
| 
		 Before Width: | Height: | Size: 116 B  | 
| 
		 Before Width: | Height: | Size: 174 B  | 
| 
		 Before Width: | Height: | Size: 212 B  | 
| 
		 Before Width: | Height: | Size: 136 B  | 
| 
		 Before Width: | Height: | Size: 134 B  | 
| 
		 Before Width: | Height: | Size: 175 B  | 
| 
		 Before Width: | Height: | Size: 268 B  | 
| 
		 Before Width: | Height: | Size: 213 B  | 
| 
		 Before Width: | Height: | Size: 247 B  | 
| 
		 Before Width: | Height: | Size: 208 B  | 
| 
		 Before Width: | Height: | Size: 352 B  | 
| 
		 Before Width: | Height: | Size: 241 B  | 
| 
		 Before Width: | Height: | Size: 355 B  | 
| 
		 Before Width: | Height: | Size: 157 B  |