Compare commits
	
		
			65 Commits
		
	
	
		
			a094d88799
			...
			v123030681
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					d9d057c8dc | ||
| 
						 | 
					1f3fa0c4a6 | ||
| 
						 | 
					dea3def385 | ||
| 
						 | 
					f72ef2f5d4 | ||
| 
						 | 
					f28cb759df | ||
| 
						 | 
					b9d69c3e64 | ||
| 
						 | 
					c2a1c9eaac | ||
| 
						 | 
					bf37209a15 | ||
| 
						 | 
					2c558fe6fd | ||
| 
						 | 
					ad88011454 | ||
| 
						 | 
					559c17bc1d | ||
| 
						 | 
					ab9c46f0eb | ||
| 
						 | 
					aa799d2ca8 | ||
| 
						 | 
					177c978474 | ||
| 
						 | 
					39b9991413 | ||
| 
						 | 
					b303f110f1 | ||
| 
						 | 
					f851941a6a | ||
| 
						 | 
					a313552976 | ||
| 
						 | 
					6ac97ed3fe | ||
| 
						 | 
					d583b937b7 | ||
| 
						 | 
					15b9a2d935 | ||
| 
						 | 
					5a8ce15961 | ||
| e1c64cef46 | |||
| ee064f3cb4 | |||
| 3e46e2ff29 | |||
| f28e702549 | |||
| 
						 | 
					fc31a4399c | ||
| 
						 | 
					9b23053b66 | ||
| 389a04d250 | |||
| 
						 | 
					40e1d1478b | ||
| 
						 | 
					2154ff3c33 | ||
| 2245565f95 | |||
| 014858f06b | |||
| 3f1f86a78e | |||
| a549169a7c | |||
| be7cae365a | |||
| cef3b2e593 | |||
| ae927ebc57 | |||
| 
						 | 
					90532cf501 | ||
| 
						 | 
					ab0678d61e | ||
| 
						 | 
					a1b7d22d26 | ||
| 
						 | 
					29eae4b1f6 | ||
| 
						 | 
					f5bbc63481 | ||
| ddc72d85b0 | |||
| 68bbf5b2d3 | |||
| 
						 | 
					95e76a55da | ||
| 2b6659f4ec | |||
| 
						 | 
					e0c118a73e | ||
| 
						 | 
					4e61b2aed6 | ||
| 
						 | 
					ba2758c0a3 | ||
| 
						 | 
					c718b966a1 | ||
| 
						 | 
					99438e142f | ||
| 
						 | 
					4d8076c3cf | ||
| 
						 | 
					db75c5b74a | ||
| 
						 | 
					966a082147 | ||
| 
						 | 
					cd20a5ec29 | ||
| 
						 | 
					cc4c1c9201 | ||
| 
						 | 
					ff021d572c | ||
| 
						 | 
					89992967be | ||
| 
						 | 
					3c68bde62b | ||
| 
						 | 
					c38251f5b3 | ||
| 
						 | 
					a01f6d2322 | ||
| 
						 | 
					417a33eb25 | ||
| 
						 | 
					2e7f7f23b3 | ||
| 
						 | 
					e5e182761e | 
							
								
								
									
										10
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -43,12 +43,20 @@ type: docker
 | 
				
			|||||||
name: Publish
 | 
					name: Publish
 | 
				
			||||||
 | 
					
 | 
				
			||||||
steps:
 | 
					steps:
 | 
				
			||||||
  - name: createTag
 | 
					  - name: createTagAndChangelog
 | 
				
			||||||
    image: ubuntu:latest
 | 
					    image: ubuntu:latest
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - apt-get update && apt-get install -y git
 | 
					      - apt-get update && apt-get install -y git
 | 
				
			||||||
 | 
					      - git fetch --tags -p
 | 
				
			||||||
 | 
					      - PREV=$(git describe --tags --abbrev=0)
 | 
				
			||||||
      - ./build.sh --publish --from-ci
 | 
					      - ./build.sh --publish --from-ci
 | 
				
			||||||
      - git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
 | 
					      - git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
 | 
				
			||||||
 | 
					      - VER=$(git describe --tags --abbrev=0)
 | 
				
			||||||
 | 
					      - CHANGELOG=$(git log $PREV..HEAD --pretty="- %s")
 | 
				
			||||||
 | 
					      - echo "**$VER**\n\n$CHANGELOG\n\n--------------------------------------------------------------------\n\n$(cat CHANGELOG.md)" > CHANGELOG.md
 | 
				
			||||||
 | 
					      - git add CHANGELOG.md
 | 
				
			||||||
 | 
					      - git commit -m "Changelog for $VER [CI SKIP]"
 | 
				
			||||||
 | 
					      - git push pushing master
 | 
				
			||||||
      - git push pushing --tags
 | 
					      - git push pushing --tags
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      TZ: Europe/Paris
 | 
					      TZ: Europe/Paris
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										111
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										111
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,3 +1,114 @@
 | 
				
			|||||||
 | 
					**v123030621**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- fix: url required issue.
 | 
				
			||||||
 | 
					- fix: Canvas reused issue.
 | 
				
			||||||
 | 
					- Changelog for v123020572 [CI SKIP]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123020572**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- fix: requirecontext issues ?
 | 
				
			||||||
 | 
					- debug: activity not found exception.
 | 
				
			||||||
 | 
					- Changelog for v123020571 [CI SKIP]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123020571**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- chore: remove errors logging.
 | 
				
			||||||
 | 
					- fix: quickfix for url param not provided for some sources.
 | 
				
			||||||
 | 
					- Update 'CHANGELOG.md'
 | 
				
			||||||
 | 
					- Changelog for v123020523 [CI SKIP]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123020523**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- fix: Git changelog.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123020491**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- fix: Fixed acra bug reporting.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123010301**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Chore: acra config.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123010281**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- improvement: Improve right to left support (#130) Co-authored-by: davidoskky <davidoskky@hidden.hidden> Co-committed-by: davidoskky <davidoskky@hidden.hidden>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123010261**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- feat: Handle public instances (#126) Co-authored-by: davidoskky <davidoskky@hidden.hidden> Co-committed-by: davidoskky <davidoskky@hidden.hidden>
 | 
				
			||||||
 | 
					- ci: Pull request should trigger ci.
 | 
				
			||||||
 | 
					- fix: Complete the disconnection before redirecting to the login screen
 | 
				
			||||||
 | 
					- Complete the disconnection before redirecting to the login screen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123010241**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Merge pull request 'feat: swipe down to close images' (#122) from davidoskky/ReaderForSelfoss-multiplatform:swipe_down into master
 | 
				
			||||||
 | 
					- Remove unnecessary definition
 | 
				
			||||||
 | 
					- Remove unused import
 | 
				
			||||||
 | 
					- Adjust the image closing animation
 | 
				
			||||||
 | 
					- Add a dark hue to the underlying article when swiping to close images
 | 
				
			||||||
 | 
					- Rename activity style to avoid interferences
 | 
				
			||||||
 | 
					- Adapt the style of the image activity to the rest of the application
 | 
				
			||||||
 | 
					- Resolve issues when swiping down to close images
 | 
				
			||||||
 | 
					- Close the image fragment only if the image has been dragged down
 | 
				
			||||||
 | 
					- Animate swipe down to close images
 | 
				
			||||||
 | 
					- Swipe down to close images
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123010041**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Merge pull request 'scroll-tag-filters' (#124) from scroll-tag-filters into master
 | 
				
			||||||
 | 
					- fix: added POST_NOTIFICATIONS to fix notifications issues.
 | 
				
			||||||
 | 
					- fix: scrollable filter sheet.
 | 
				
			||||||
 | 
					- enhancement: Ellipsize chips text.
 | 
				
			||||||
 | 
					- Cleaning.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v122123641**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- feat: Disable the failing source in the filter sheet.
 | 
				
			||||||
 | 
					- feat: Display the source error in the sources list.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v122123631**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153)
 | 
				
			||||||
 | 
					- build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153)
 | 
				
			||||||
 | 
					- debug: trying to resolve `Canvas: trying to use a recycled bitmap`.
 | 
				
			||||||
 | 
					- fix: NPE may be caused by the binding or the title that was null.
 | 
				
			||||||
 | 
					- chore: Skip drone pipeline on changelog push.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v122123621**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- fix: Automatic CHANGELOG generation.
 | 
				
			||||||
 | 
					- Merge pull request 'Sources Upsert' (#119) from sources-edit into master
 | 
				
			||||||
 | 
					- Source update screen.
 | 
				
			||||||
 | 
					- Sources menu.
 | 
				
			||||||
 | 
					- chore: Automatic CHANGELOG generation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# V2/Multiplatform rewrite
 | 
					# V2/Multiplatform rewrite
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**v1**
 | 
					**v1**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,7 @@ fun gitVersion(): String {
 | 
				
			|||||||
    val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
 | 
					    val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
 | 
				
			||||||
    process = if (maybeTagOfCurrentCommit.isEmpty()) {
 | 
					    process = if (maybeTagOfCurrentCommit.isEmpty()) {
 | 
				
			||||||
        println("No tag on current commit. Will take the latest one.")
 | 
					        println("No tag on current commit. Will take the latest one.")
 | 
				
			||||||
        execWithOutput("git -C ../ for-each-ref refs/tags --sort=-authordate --format='%(refname:short)' --count=1")
 | 
					        execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1")
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        println("Tag found on current commit")
 | 
					        println("Tag found on current commit")
 | 
				
			||||||
        execWithOutput("git -C ../ describe --contains HEAD")
 | 
					        execWithOutput("git -C ../ describe --contains HEAD")
 | 
				
			||||||
@@ -66,7 +66,7 @@ android {
 | 
				
			|||||||
        jvmTarget = "11"
 | 
					        jvmTarget = "11"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    compileSdk = 33
 | 
					    compileSdk = 33
 | 
				
			||||||
    buildToolsVersion = "31.0.0"
 | 
					    buildToolsVersion = "33.0.0"
 | 
				
			||||||
    buildFeatures {
 | 
					    buildFeatures {
 | 
				
			||||||
        viewBinding = true
 | 
					        viewBinding = true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -115,7 +115,6 @@ dependencies {
 | 
				
			|||||||
    implementation(project(":shared"))
 | 
					    implementation(project(":shared"))
 | 
				
			||||||
    implementation("com.google.android.material:material:1.5.0")
 | 
					    implementation("com.google.android.material:material:1.5.0")
 | 
				
			||||||
    implementation("androidx.appcompat:appcompat:1.4.1")
 | 
					    implementation("androidx.appcompat:appcompat:1.4.1")
 | 
				
			||||||
    implementation("androidx.constraintlayout:constraintlayout:2.1.3")
 | 
					 | 
				
			||||||
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
 | 
					    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    implementation("androidx.preference:preference-ktx:1.1.1")
 | 
					    implementation("androidx.preference:preference-ktx:1.1.1")
 | 
				
			||||||
@@ -131,7 +130,7 @@ dependencies {
 | 
				
			|||||||
    implementation("androidx.cardview:cardview:1.0.0")
 | 
					    implementation("androidx.cardview:cardview:1.0.0")
 | 
				
			||||||
    implementation("androidx.annotation:annotation:1.3.0")
 | 
					    implementation("androidx.annotation:annotation:1.3.0")
 | 
				
			||||||
    implementation("androidx.work:work-runtime-ktx:2.7.1")
 | 
					    implementation("androidx.work:work-runtime-ktx:2.7.1")
 | 
				
			||||||
    implementation("androidx.constraintlayout:constraintlayout:2.1.3")
 | 
					    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
 | 
				
			||||||
    implementation("org.jsoup:jsoup:1.14.3")
 | 
					    implementation("org.jsoup:jsoup:1.14.3")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //multidex
 | 
					    //multidex
 | 
				
			||||||
@@ -146,8 +145,8 @@ dependencies {
 | 
				
			|||||||
    implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
 | 
					    implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // glide
 | 
					    // glide
 | 
				
			||||||
    kapt("com.github.bumptech.glide:compiler:4.14.2")
 | 
					    kapt("com.github.bumptech.glide:compiler:4.15.0")
 | 
				
			||||||
    implementation("com.github.bumptech.glide:okhttp3-integration:4.14.2")
 | 
					    implementation("com.github.bumptech.glide:okhttp3-integration:4.15.0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Themes
 | 
					    // Themes
 | 
				
			||||||
    implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
 | 
					    implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools">
 | 
					    xmlns:tools="http://schemas.android.com/tools">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
 | 
				
			||||||
    <uses-permission android:name="android.permission.INTERNET" />
 | 
					    <uses-permission android:name="android.permission.INTERNET" />
 | 
				
			||||||
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 | 
					    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,7 +53,7 @@
 | 
				
			|||||||
                android:value=".HomeActivity" />
 | 
					                android:value=".HomeActivity" />
 | 
				
			||||||
        </activity>
 | 
					        </activity>
 | 
				
			||||||
        <activity
 | 
					        <activity
 | 
				
			||||||
            android:name=".AddSourceActivity"
 | 
					            android:name=".UpsertSourceActivity"
 | 
				
			||||||
            android:parentActivityName=".SourcesActivity"
 | 
					            android:parentActivityName=".SourcesActivity"
 | 
				
			||||||
            android:exported="true">
 | 
					            android:exported="true">
 | 
				
			||||||
            <meta-data
 | 
					            <meta-data
 | 
				
			||||||
@@ -69,7 +70,8 @@
 | 
				
			|||||||
            android:name=".ReaderActivity">
 | 
					            android:name=".ReaderActivity">
 | 
				
			||||||
        </activity>
 | 
					        </activity>
 | 
				
			||||||
        <activity
 | 
					        <activity
 | 
				
			||||||
            android:name=".ImageActivity">
 | 
					            android:name=".ImageActivity"
 | 
				
			||||||
 | 
					            android:theme="@style/Theme.AppCompat.ImageActivity">
 | 
				
			||||||
        </activity>
 | 
					        </activity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <meta-data android:name="android.webkit.WebView.MetricsOptOut"
 | 
					        <meta-data android:name="android.webkit.WebView.MetricsOptOut"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,7 @@ import com.ashokvarma.bottomnavigation.TextBadgeItem
 | 
				
			|||||||
import kotlinx.coroutines.CoroutineScope
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
import kotlinx.coroutines.Dispatchers
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
import kotlinx.coroutines.launch
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
 | 
					import kotlinx.coroutines.runBlocking
 | 
				
			||||||
import org.kodein.di.DIAware
 | 
					import org.kodein.di.DIAware
 | 
				
			||||||
import org.kodein.di.android.closestDI
 | 
					import org.kodein.di.android.closestDI
 | 
				
			||||||
import org.kodein.di.instance
 | 
					import org.kodein.di.instance
 | 
				
			||||||
@@ -114,10 +115,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val swipeDirs = if (appSettingsService.getPublicAccess()) {
 | 
				
			||||||
 | 
					            0
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val simpleItemTouchCallback =
 | 
					        val simpleItemTouchCallback =
 | 
				
			||||||
            object : ItemTouchHelper.SimpleCallback(
 | 
					            object : ItemTouchHelper.SimpleCallback(
 | 
				
			||||||
                0,
 | 
					                0,
 | 
				
			||||||
                ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
 | 
					                swipeDirs
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                override fun getSwipeDirs(
 | 
					                override fun getSwipeDirs(
 | 
				
			||||||
                    recyclerView: RecyclerView,
 | 
					                    recyclerView: RecyclerView,
 | 
				
			||||||
@@ -366,7 +373,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    fun fetchOnEmptyList() {
 | 
					    fun fetchOnEmptyList() {
 | 
				
			||||||
        binding.recyclerView.doOnNextLayout {
 | 
					        binding.recyclerView.doOnNextLayout {
 | 
				
			||||||
            // TODO: do if last element (or is empty ?)
 | 
					 | 
				
			||||||
            getElementsAccordingToTab(true)
 | 
					            getElementsAccordingToTab(true)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -511,6 +517,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
				
			|||||||
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
 | 
					    override fun onCreateOptionsMenu(menu: Menu): Boolean {
 | 
				
			||||||
        val inflater = menuInflater
 | 
					        val inflater = menuInflater
 | 
				
			||||||
        inflater.inflate(R.menu.home_menu, menu)
 | 
					        inflater.inflate(R.menu.home_menu, menu)
 | 
				
			||||||
 | 
					        if (appSettingsService.getPublicAccess()) {
 | 
				
			||||||
 | 
					            menu.removeItem(R.id.readAll)
 | 
				
			||||||
 | 
					            menu.removeItem(R.id.action_sources)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val searchItem = menu.findItem(R.id.action_search)
 | 
					        val searchItem = menu.findItem(R.id.action_search)
 | 
				
			||||||
        val searchView = searchItem.getActionView() as SearchView
 | 
					        val searchView = searchItem.getActionView() as SearchView
 | 
				
			||||||
@@ -539,11 +549,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
				
			|||||||
            R.id.refresh -> {
 | 
					            R.id.refresh -> {
 | 
				
			||||||
                needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
 | 
					                needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
 | 
				
			||||||
                    Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
 | 
					                    Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
 | 
				
			||||||
                    // TODO: Use Dispatchers.IO
 | 
					 | 
				
			||||||
                    CoroutineScope(Dispatchers.Main).launch {
 | 
					                    CoroutineScope(Dispatchers.Main).launch {
 | 
				
			||||||
                        val updatedRemote = repository.updateRemote()
 | 
					                        val updatedRemote = repository.updateRemote()
 | 
				
			||||||
                        if (updatedRemote) {
 | 
					                        if (updatedRemote) {
 | 
				
			||||||
                            // TODO: Send toast messages from the repository
 | 
					 | 
				
			||||||
                            Toast.makeText(
 | 
					                            Toast.makeText(
 | 
				
			||||||
                                this@HomeActivity,
 | 
					                                this@HomeActivity,
 | 
				
			||||||
                                R.string.refresh_success_response, Toast.LENGTH_LONG
 | 
					                                R.string.refresh_success_response, Toast.LENGTH_LONG
 | 
				
			||||||
@@ -591,18 +599,22 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
				
			|||||||
                return true
 | 
					                return true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            R.id.action_disconnect -> {
 | 
					            R.id.action_disconnect -> {
 | 
				
			||||||
                CoroutineScope(Dispatchers.Main).launch {
 | 
					                runBlocking {
 | 
				
			||||||
                    repository.logout()
 | 
					                    repository.logout()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                this@HomeActivity.finish()
 | 
					 | 
				
			||||||
                val intent = Intent(this, LoginActivity::class.java)
 | 
					                val intent = Intent(this, LoginActivity::class.java)
 | 
				
			||||||
                this.startActivity(intent)
 | 
					                this.startActivity(intent)
 | 
				
			||||||
 | 
					                finish()
 | 
				
			||||||
                return true
 | 
					                return true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            R.id.action_settings -> {
 | 
					            R.id.action_settings -> {
 | 
				
			||||||
                settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
 | 
					                settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
 | 
				
			||||||
                return true
 | 
					                return true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            R.id.action_sources -> {
 | 
				
			||||||
 | 
					                startActivity(Intent(this, SourcesActivity::class.java))
 | 
				
			||||||
 | 
					                return true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            else -> return super.onOptionsItemSelected(item)
 | 
					            else -> return super.onOptionsItemSelected(item)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.android
 | 
				
			|||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
import android.view.MenuItem
 | 
					import android.view.MenuItem
 | 
				
			||||||
import androidx.appcompat.app.AppCompatActivity
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
 | 
					import androidx.constraintlayout.motion.widget.MotionLayout
 | 
				
			||||||
import androidx.fragment.app.Fragment
 | 
					import androidx.fragment.app.Fragment
 | 
				
			||||||
import androidx.fragment.app.FragmentActivity
 | 
					import androidx.fragment.app.FragmentActivity
 | 
				
			||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
 | 
					import androidx.viewpager2.adapter.FragmentStateAdapter
 | 
				
			||||||
@@ -23,7 +24,6 @@ class ImageActivity : AppCompatActivity() {
 | 
				
			|||||||
        setContentView(view)
 | 
					        setContentView(view)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setSupportActionBar(binding.toolBar)
 | 
					        setSupportActionBar(binding.toolBar)
 | 
				
			||||||
        supportActionBar?.setDisplayShowTitleEnabled(false)
 | 
					 | 
				
			||||||
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
					        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        allImages = intent.getStringArrayListExtra("allImages") as ArrayList<String>
 | 
					        allImages = intent.getStringArrayListExtra("allImages") as ArrayList<String>
 | 
				
			||||||
@@ -31,12 +31,35 @@ class ImageActivity : AppCompatActivity() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        binding.pager.adapter = ScreenSlidePagerAdapter(this)
 | 
					        binding.pager.adapter = ScreenSlidePagerAdapter(this)
 | 
				
			||||||
        binding.pager.setCurrentItem(position, false)
 | 
					        binding.pager.setCurrentItem(position, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val transitionListener = object : MotionLayout.TransitionListener {
 | 
				
			||||||
 | 
					            override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
 | 
				
			||||||
 | 
					                // Nothing
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                // Nothing
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
 | 
				
			||||||
 | 
					                if (motionLayout?.currentState == binding.root.endState) {
 | 
				
			||||||
 | 
					                    onBackPressedDispatcher.onBackPressed()
 | 
				
			||||||
 | 
					                    overridePendingTransition(0, 0)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
 | 
				
			||||||
 | 
					                // Nothing
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        binding.root.setTransitionListener(transitionListener)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
					    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
				
			||||||
        when (item.itemId) {
 | 
					        when (item.itemId) {
 | 
				
			||||||
            android.R.id.home -> {
 | 
					            android.R.id.home -> {
 | 
				
			||||||
                onBackPressed()
 | 
					                onBackPressedDispatcher.onBackPressed()
 | 
				
			||||||
                return true
 | 
					                return true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,6 @@ import org.acra.ACRA
 | 
				
			|||||||
import org.kodein.di.DIAware
 | 
					import org.kodein.di.DIAware
 | 
				
			||||||
import org.kodein.di.android.closestDI
 | 
					import org.kodein.di.android.closestDI
 | 
				
			||||||
import org.kodein.di.instance
 | 
					import org.kodein.di.instance
 | 
				
			||||||
import java.security.MessageDigest
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LoginActivity : AppCompatActivity(), DIAware {
 | 
					class LoginActivity : AppCompatActivity(), DIAware {
 | 
				
			||||||
@@ -129,7 +128,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private fun goToMain() {
 | 
					    private fun goToMain() {
 | 
				
			||||||
        CoroutineScope(Dispatchers.Main).launch {
 | 
					        CoroutineScope(Dispatchers.Main).launch {
 | 
				
			||||||
            repository.updateApiVersion()
 | 
					            repository.updateApiInformation()
 | 
				
			||||||
            ACRA.errorReporter.putCustomData("SELFOSS_API_VERSION", appSettingsService.getApiVersion().toString())
 | 
					            ACRA.errorReporter.putCustomData("SELFOSS_API_VERSION", appSettingsService.getApiVersion().toString())
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        val intent = Intent(this, HomeActivity::class.java)
 | 
					        val intent = Intent(this, HomeActivity::class.java)
 | 
				
			||||||
@@ -157,44 +156,9 @@ class LoginActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
        val login = binding.loginView.text.toString().trim()
 | 
					        val login = binding.loginView.text.toString().trim()
 | 
				
			||||||
        val password = binding.passwordView.text.toString().trim()
 | 
					        val password = binding.passwordView.text.toString().trim()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var cancel = false
 | 
					        failInvalidUrl(url)
 | 
				
			||||||
        var focusView: View? = null
 | 
					        failLoginDetails(password, login)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (url.isBaseUrlInvalid()) {
 | 
					 | 
				
			||||||
            binding.urlView.error = getString(R.string.login_url_problem)
 | 
					 | 
				
			||||||
            focusView = binding.urlView
 | 
					 | 
				
			||||||
            cancel = true
 | 
					 | 
				
			||||||
            inValidCount++
 | 
					 | 
				
			||||||
            if (inValidCount == 3) {
 | 
					 | 
				
			||||||
                val alertDialog = AlertDialog.Builder(this).create()
 | 
					 | 
				
			||||||
                alertDialog.setTitle(getString(R.string.warning_wrong_url))
 | 
					 | 
				
			||||||
                alertDialog.setMessage(getString(R.string.text_wrong_url))
 | 
					 | 
				
			||||||
                alertDialog.setButton(
 | 
					 | 
				
			||||||
                    AlertDialog.BUTTON_NEUTRAL,
 | 
					 | 
				
			||||||
                    "OK"
 | 
					 | 
				
			||||||
                ) { dialog, _ -> dialog.dismiss() }
 | 
					 | 
				
			||||||
                alertDialog.show()
 | 
					 | 
				
			||||||
                inValidCount = 0
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (isWithLogin) {
 | 
					 | 
				
			||||||
            if (TextUtils.isEmpty(password)) {
 | 
					 | 
				
			||||||
                binding.passwordView.error = getString(R.string.error_invalid_password)
 | 
					 | 
				
			||||||
                focusView = binding.passwordView
 | 
					 | 
				
			||||||
                cancel = true
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (TextUtils.isEmpty(login)) {
 | 
					 | 
				
			||||||
                binding.loginView.error = getString(R.string.error_field_required)
 | 
					 | 
				
			||||||
                focusView = binding.loginView
 | 
					 | 
				
			||||||
                cancel = true
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (cancel) {
 | 
					 | 
				
			||||||
            focusView?.requestFocus()
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
        showProgress(true)
 | 
					        showProgress(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        repository.refreshLoginInformation(url, login, password)
 | 
					        repository.refreshLoginInformation(url, login, password)
 | 
				
			||||||
@@ -221,6 +185,55 @@ class LoginActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
            showProgress(false)
 | 
					            showProgress(false)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun failLoginDetails(
 | 
				
			||||||
 | 
					        password: String,
 | 
				
			||||||
 | 
					        login: String
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        var lastFocusedView: View? = null
 | 
				
			||||||
 | 
					        var cancel = false
 | 
				
			||||||
 | 
					        if (isWithLogin) {
 | 
				
			||||||
 | 
					            if (TextUtils.isEmpty(password)) {
 | 
				
			||||||
 | 
					                binding.passwordView.error = getString(R.string.error_invalid_password)
 | 
				
			||||||
 | 
					                lastFocusedView = binding.passwordView
 | 
				
			||||||
 | 
					                cancel = true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (TextUtils.isEmpty(login)) {
 | 
				
			||||||
 | 
					                binding.loginView.error = getString(R.string.error_field_required)
 | 
				
			||||||
 | 
					                lastFocusedView = binding.loginView
 | 
				
			||||||
 | 
					                cancel = true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        maybeCancelAndFocusView(cancel, lastFocusedView)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun failInvalidUrl(url: String) {
 | 
				
			||||||
 | 
					        val focusView = binding.urlView
 | 
				
			||||||
 | 
					        var cancel = false
 | 
				
			||||||
 | 
					        if (url.isBaseUrlInvalid()) {
 | 
				
			||||||
 | 
					            cancel = true
 | 
				
			||||||
 | 
					            binding.urlView.error = getString(R.string.login_url_problem)
 | 
				
			||||||
 | 
					            inValidCount++
 | 
				
			||||||
 | 
					            if (inValidCount == 3) {
 | 
				
			||||||
 | 
					                val alertDialog = AlertDialog.Builder(this).create()
 | 
				
			||||||
 | 
					                alertDialog.setTitle(getString(R.string.warning_wrong_url))
 | 
				
			||||||
 | 
					                alertDialog.setMessage(getString(R.string.text_wrong_url))
 | 
				
			||||||
 | 
					                alertDialog.setButton(
 | 
				
			||||||
 | 
					                    AlertDialog.BUTTON_NEUTRAL,
 | 
				
			||||||
 | 
					                    "OK"
 | 
				
			||||||
 | 
					                ) { dialog, _ -> dialog.dismiss() }
 | 
				
			||||||
 | 
					                alertDialog.show()
 | 
				
			||||||
 | 
					                inValidCount = 0
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        maybeCancelAndFocusView(cancel, focusView)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun maybeCancelAndFocusView(cancel: Boolean, focusView: View?) {
 | 
				
			||||||
 | 
					        if (cancel) {
 | 
				
			||||||
 | 
					            focusView?.requestFocus()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun showProgress(show: Boolean) {
 | 
					    private fun showProgress(show: Boolean) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,7 @@ package bou.amine.apps.readerforselfossv2.android
 | 
				
			|||||||
import android.app.NotificationChannel
 | 
					import android.app.NotificationChannel
 | 
				
			||||||
import android.app.NotificationManager
 | 
					import android.app.NotificationManager
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.graphics.drawable.Drawable
 | 
					 | 
				
			||||||
import android.net.Uri
 | 
					 | 
				
			||||||
import android.os.Build
 | 
					import android.os.Build
 | 
				
			||||||
import android.widget.ImageView
 | 
					 | 
				
			||||||
import android.widget.Toast
 | 
					import android.widget.Toast
 | 
				
			||||||
import androidx.lifecycle.DefaultLifecycleObserver
 | 
					import androidx.lifecycle.DefaultLifecycleObserver
 | 
				
			||||||
import androidx.lifecycle.LifecycleOwner
 | 
					import androidx.lifecycle.LifecycleOwner
 | 
				
			||||||
@@ -18,8 +15,6 @@ import bou.amine.apps.readerforselfossv2.dao.DriverFactory
 | 
				
			|||||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
 | 
					import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
					import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
					import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
				
			||||||
import com.bumptech.glide.Glide
 | 
					 | 
				
			||||||
import com.bumptech.glide.request.RequestOptions
 | 
					 | 
				
			||||||
import com.github.ln_12.library.ConnectivityStatus
 | 
					import com.github.ln_12.library.ConnectivityStatus
 | 
				
			||||||
import io.github.aakira.napier.DebugAntilog
 | 
					import io.github.aakira.napier.DebugAntilog
 | 
				
			||||||
import io.github.aakira.napier.Napier
 | 
					import io.github.aakira.napier.Napier
 | 
				
			||||||
@@ -39,10 +34,18 @@ import org.kodein.di.*
 | 
				
			|||||||
class MyApp : MultiDexApplication(), DIAware {
 | 
					class MyApp : MultiDexApplication(), DIAware {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override val di by DI.lazy {
 | 
					    override val di by DI.lazy {
 | 
				
			||||||
 | 
					        bind<AppSettingsService>() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess()) }
 | 
				
			||||||
        import(networkModule)
 | 
					        import(networkModule)
 | 
				
			||||||
        bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
 | 
					        bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
 | 
				
			||||||
        bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
 | 
					        bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
 | 
				
			||||||
        bind<Repository>() with singleton { Repository(instance(), instance(), isConnectionAvailable, instance()) }
 | 
					        bind<Repository>() with singleton {
 | 
				
			||||||
 | 
					            Repository(
 | 
				
			||||||
 | 
					                instance(),
 | 
				
			||||||
 | 
					                instance(),
 | 
				
			||||||
 | 
					                isConnectionAvailable,
 | 
				
			||||||
 | 
					                instance()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
 | 
					        bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
 | 
				
			||||||
        bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
 | 
					        bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -64,7 +67,12 @@ class MyApp : MultiDexApplication(), DIAware {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            handleNotificationChannels()
 | 
					            handleNotificationChannels()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
 | 
					            ProcessLifecycleOwner.get().lifecycle.addObserver(
 | 
				
			||||||
 | 
					                AppLifeCycleObserver(
 | 
				
			||||||
 | 
					                    connectivityStatus,
 | 
				
			||||||
 | 
					                    repository
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            CoroutineScope(Dispatchers.Main).launch {
 | 
					            CoroutineScope(Dispatchers.Main).launch {
 | 
				
			||||||
                viewModel.networkAvailableProvider.collect { networkAvailable ->
 | 
					                viewModel.networkAvailableProvider.collect { networkAvailable ->
 | 
				
			||||||
@@ -83,6 +91,8 @@ class MyApp : MultiDexApplication(), DIAware {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        repository.migrate(driverFactory)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun attachBaseContext(base: Context?) {
 | 
					    override fun attachBaseContext(base: Context?) {
 | 
				
			||||||
@@ -91,22 +101,38 @@ class MyApp : MultiDexApplication(), DIAware {
 | 
				
			|||||||
        initAcra {
 | 
					        initAcra {
 | 
				
			||||||
            reportFormat = StringFormat.JSON
 | 
					            reportFormat = StringFormat.JSON
 | 
				
			||||||
            reportContent = listOf(
 | 
					            reportContent = listOf(
 | 
				
			||||||
                ReportField.REPORT_ID, ReportField.INSTALLATION_ID,
 | 
					                ReportField.REPORT_ID,
 | 
				
			||||||
                ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME,
 | 
					                ReportField.INSTALLATION_ID,
 | 
				
			||||||
                ReportField.BUILD, ReportField.ANDROID_VERSION, ReportField.BRAND, ReportField.PHONE_MODEL,
 | 
					                ReportField.APP_VERSION_CODE,
 | 
				
			||||||
                ReportField.AVAILABLE_MEM_SIZE, ReportField.TOTAL_MEM_SIZE,
 | 
					                ReportField.APP_VERSION_NAME,
 | 
				
			||||||
                ReportField.STACK_TRACE, ReportField.APPLICATION_LOG, ReportField.LOGCAT,
 | 
					                ReportField.BUILD,
 | 
				
			||||||
                ReportField.INITIAL_CONFIGURATION, ReportField.CRASH_CONFIGURATION, ReportField.IS_SILENT,
 | 
					                ReportField.ANDROID_VERSION,
 | 
				
			||||||
                ReportField.USER_APP_START_DATE, ReportField.USER_COMMENT, ReportField.USER_CRASH_DATE, ReportField.USER_EMAIL, ReportField.CUSTOM_DATA)
 | 
					                ReportField.BRAND,
 | 
				
			||||||
 | 
					                ReportField.PHONE_MODEL,
 | 
				
			||||||
 | 
					                ReportField.AVAILABLE_MEM_SIZE,
 | 
				
			||||||
 | 
					                ReportField.TOTAL_MEM_SIZE,
 | 
				
			||||||
 | 
					                ReportField.STACK_TRACE,
 | 
				
			||||||
 | 
					                ReportField.APPLICATION_LOG,
 | 
				
			||||||
 | 
					                ReportField.LOGCAT,
 | 
				
			||||||
 | 
					                ReportField.INITIAL_CONFIGURATION,
 | 
				
			||||||
 | 
					                ReportField.CRASH_CONFIGURATION,
 | 
				
			||||||
 | 
					                ReportField.IS_SILENT,
 | 
				
			||||||
 | 
					                ReportField.USER_APP_START_DATE,
 | 
				
			||||||
 | 
					                ReportField.USER_COMMENT,
 | 
				
			||||||
 | 
					                ReportField.USER_CRASH_DATE,
 | 
				
			||||||
 | 
					                ReportField.USER_EMAIL,
 | 
				
			||||||
 | 
					                ReportField.CUSTOM_DATA
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            toast {
 | 
					            toast {
 | 
				
			||||||
                //required
 | 
					                //required
 | 
				
			||||||
                text = getString(R.string.crash_toast_text)
 | 
					                text = getString(R.string.crash_toast_text)
 | 
				
			||||||
                length = Toast.LENGTH_SHORT
 | 
					                length = Toast.LENGTH_SHORT
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            httpSender {
 | 
					            httpSender {
 | 
				
			||||||
                uri = "https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
 | 
					                uri =
 | 
				
			||||||
                basicAuthLogin = "LMTlLZuazADohTCm"
 | 
					                    "https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
 | 
				
			||||||
                basicAuthPassword = "he6ghHp83F0PYPfh"
 | 
					                basicAuthLogin = "qMEscjj89Gwt6cPR"
 | 
				
			||||||
 | 
					                basicAuthPassword = "Yo58QFlGzFaWlBzP"
 | 
				
			||||||
                httpMethod = HttpSender.Method.POST
 | 
					                httpMethod = HttpSender.Method.POST
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -122,7 +148,11 @@ class MyApp : MultiDexApplication(), DIAware {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            val newItemsChannelname = getString(R.string.new_items_channel_sync)
 | 
					            val newItemsChannelname = getString(R.string.new_items_channel_sync)
 | 
				
			||||||
            val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
 | 
					            val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
 | 
				
			||||||
            val newItemsChannelmChannel = NotificationChannel(AppSettingsService.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
 | 
					            val newItemsChannelmChannel = NotificationChannel(
 | 
				
			||||||
 | 
					                AppSettingsService.newItemsChannelId,
 | 
				
			||||||
 | 
					                newItemsChannelname,
 | 
				
			||||||
 | 
					                newItemsChannelimportance
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            notificationManager.createNotificationChannel(mChannel)
 | 
					            notificationManager.createNotificationChannel(mChannel)
 | 
				
			||||||
            notificationManager.createNotificationChannel(newItemsChannelmChannel)
 | 
					            notificationManager.createNotificationChannel(newItemsChannelmChannel)
 | 
				
			||||||
@@ -136,13 +166,17 @@ class MyApp : MultiDexApplication(), DIAware {
 | 
				
			|||||||
            if (e is NoClassDefFoundError && e.stackTrace.asList().any {
 | 
					            if (e is NoClassDefFoundError && e.stackTrace.asList().any {
 | 
				
			||||||
                    it.toString().contains("android.view.ViewDebug")
 | 
					                    it.toString().contains("android.view.ViewDebug")
 | 
				
			||||||
                }) {
 | 
					                }) {
 | 
				
			||||||
 | 
					                // Nothing
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                oldHandler.uncaughtException(thread, e)
 | 
					                oldHandler.uncaughtException(thread, e)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver {
 | 
					    class AppLifeCycleObserver(
 | 
				
			||||||
 | 
					        val connectivityStatus: ConnectivityStatus,
 | 
				
			||||||
 | 
					        val repository: Repository
 | 
				
			||||||
 | 
					    ) : DefaultLifecycleObserver {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        override fun onResume(owner: LifecycleOwner) {
 | 
					        override fun onResume(owner: LifecycleOwner) {
 | 
				
			||||||
            super.onResume(owner)
 | 
					            super.onResume(owner)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -84,7 +84,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun readItem(item: SelfossModel.Item) {
 | 
					    private fun readItem(item: SelfossModel.Item) {
 | 
				
			||||||
        if (appSettingsService.isMarkOnScrollEnabled()) {
 | 
					        if (appSettingsService.isMarkOnScrollEnabled() && !appSettingsService.getPublicAccess()) {
 | 
				
			||||||
            CoroutineScope(Dispatchers.IO).launch {
 | 
					            CoroutineScope(Dispatchers.IO).launch {
 | 
				
			||||||
                repository.markAsRead(item)
 | 
					                repository.markAsRead(item)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -137,12 +137,17 @@ class ReaderActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
        inflater.inflate(R.menu.reader_menu, menu)
 | 
					        inflater.inflate(R.menu.reader_menu, menu)
 | 
				
			||||||
        toolbarMenu = menu
 | 
					        toolbarMenu = menu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        alignmentMenu()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (appSettingsService.getPublicAccess()) {
 | 
				
			||||||
 | 
					            menu.removeItem(R.id.star)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
            if (allItems.isNotEmpty() && allItems[currentItem].starred) {
 | 
					            if (allItems.isNotEmpty() && allItems[currentItem].starred) {
 | 
				
			||||||
                canRemoveFromFavorite()
 | 
					                canRemoveFromFavorite()
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                canFavorite()
 | 
					                canFavorite()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        alignmentMenu()
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            binding.pager.registerOnPageChangeCallback(
 | 
					            binding.pager.registerOnPageChangeCallback(
 | 
				
			||||||
                object : ViewPager2.OnPageChangeCallback() {
 | 
					                object : ViewPager2.OnPageChangeCallback() {
 | 
				
			||||||
@@ -159,6 +164,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return true
 | 
					        return true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -177,20 +183,18 @@ class ReaderActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        when (item.itemId) {
 | 
					        when (item.itemId) {
 | 
				
			||||||
            android.R.id.home -> {
 | 
					            android.R.id.home -> {
 | 
				
			||||||
                onBackPressed()
 | 
					                onBackPressedDispatcher.onBackPressed()
 | 
				
			||||||
                return true
 | 
					                return true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            R.id.star -> {
 | 
					            R.id.star -> {
 | 
				
			||||||
                if (allItems[binding.pager.currentItem].starred) {
 | 
					                if (allItems[binding.pager.currentItem].starred) {
 | 
				
			||||||
                    CoroutineScope(Dispatchers.IO).launch {
 | 
					                    CoroutineScope(Dispatchers.IO).launch {
 | 
				
			||||||
                        repository.unstarr(allItems[binding.pager.currentItem])
 | 
					                        repository.unstarr(allItems[binding.pager.currentItem])
 | 
				
			||||||
                        // TODO: Handle failure
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    afterUnsave()
 | 
					                    afterUnsave()
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    CoroutineScope(Dispatchers.IO).launch {
 | 
					                    CoroutineScope(Dispatchers.IO).launch {
 | 
				
			||||||
                        repository.starr(allItems[binding.pager.currentItem])
 | 
					                        repository.starr(allItems[binding.pager.currentItem])
 | 
				
			||||||
                        // TODO: Handle failure
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    afterSave()
 | 
					                    afterSave()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -73,7 +73,7 @@ class SourcesActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding.fab.setOnClickListener {
 | 
					        binding.fab.setOnClickListener {
 | 
				
			||||||
            startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
 | 
					            startActivity(Intent(this@SourcesActivity, UpsertSourceActivity::class.java))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,12 +3,15 @@ package bou.amine.apps.readerforselfossv2.android
 | 
				
			|||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
import android.widget.*
 | 
					import android.widget.AdapterView
 | 
				
			||||||
 | 
					import android.widget.ArrayAdapter
 | 
				
			||||||
 | 
					import android.widget.TextView
 | 
				
			||||||
 | 
					import android.widget.Toast
 | 
				
			||||||
import androidx.appcompat.app.AppCompatActivity
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
import androidx.constraintlayout.widget.ConstraintLayout
 | 
					import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
 | 
					 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
 | 
					import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
 | 
					import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
 | 
				
			||||||
 | 
					import bou.amine.apps.readerforselfossv2.model.SelfossModel
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
					import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
					import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
				
			||||||
import kotlinx.coroutines.CoroutineScope
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
@@ -19,11 +22,12 @@ import org.kodein.di.android.closestDI
 | 
				
			|||||||
import org.kodein.di.instance
 | 
					import org.kodein.di.instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AddSourceActivity : AppCompatActivity(), DIAware {
 | 
					class UpsertSourceActivity : AppCompatActivity(), DIAware {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var existingSource: SelfossModel.Source? = null
 | 
				
			||||||
    private var mSpoutsValue: String? = null
 | 
					    private var mSpoutsValue: String? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private lateinit var binding: ActivityAddSourceBinding
 | 
					    private lateinit var binding: ActivityUpsertSourceBinding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override val di by closestDI()
 | 
					    override val di by closestDI()
 | 
				
			||||||
    private val repository: Repository by instance()
 | 
					    private val repository: Repository by instance()
 | 
				
			||||||
@@ -31,26 +35,46 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
        super.onCreate(savedInstanceState)
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
        binding = ActivityAddSourceBinding.inflate(layoutInflater)
 | 
					        binding = ActivityUpsertSourceBinding.inflate(layoutInflater)
 | 
				
			||||||
        val view = binding.root
 | 
					        val view = binding.root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        existingSource = repository.getSelectedSource()
 | 
				
			||||||
 | 
					        if (existingSource != null) {
 | 
				
			||||||
 | 
					            binding.formContainer.visibility = View.GONE
 | 
				
			||||||
 | 
					            binding.progress.visibility = View.VISIBLE
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val title = if (existingSource == null) R.string.add_source else R.string.update_source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        supportFragmentManager.addOnBackStackChangedListener {
 | 
				
			||||||
 | 
					            if (supportFragmentManager.backStackEntryCount == 0) {
 | 
				
			||||||
 | 
					                setTitle(title)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setContentView(view)
 | 
					        setContentView(view)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setSupportActionBar(binding.toolbar)
 | 
					        setSupportActionBar(binding.toolbar)
 | 
				
			||||||
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
					        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
				
			||||||
        supportActionBar?.setDisplayShowHomeEnabled(true)
 | 
					        supportActionBar?.setDisplayShowHomeEnabled(true)
 | 
				
			||||||
 | 
					        supportActionBar?.title = resources.getString(title)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        maybeGetDetailsFromIntentSharing(intent, binding.sourceUri, binding.nameInput)
 | 
					
 | 
				
			||||||
 | 
					        maybeGetDetailsFromIntentSharing(intent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding.saveBtn.setOnClickListener {
 | 
					        binding.saveBtn.setOnClickListener {
 | 
				
			||||||
            handleSaveSource(
 | 
					            handleSaveSource()
 | 
				
			||||||
                binding.tags,
 | 
					 | 
				
			||||||
                binding.nameInput.text.toString(),
 | 
					 | 
				
			||||||
                binding.sourceUri.text.toString()
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun initFields(items: Map<String, SelfossModel.Spout>) {
 | 
				
			||||||
 | 
					        binding.nameInput.setText(existingSource!!.title)
 | 
				
			||||||
 | 
					        binding.tags.setText(existingSource!!.tags.joinToString(", "))
 | 
				
			||||||
 | 
					        binding.sourceUri.setText(existingSource!!.params?.url)
 | 
				
			||||||
 | 
					        binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout))
 | 
				
			||||||
 | 
					        binding.progress.visibility = View.GONE
 | 
				
			||||||
 | 
					        binding.formContainer.visibility = View.VISIBLE
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onResume() {
 | 
					    override fun onResume() {
 | 
				
			||||||
        super.onResume()
 | 
					        super.onResume()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,17 +82,13 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
        if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) {
 | 
					        if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) {
 | 
				
			||||||
            mustLoginToAddSource()
 | 
					            mustLoginToAddSource()
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer)
 | 
					            handleSpoutsSpinner()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun handleSpoutsSpinner(
 | 
					    private fun handleSpoutsSpinner() {
 | 
				
			||||||
        spoutsSpinner: Spinner,
 | 
					 | 
				
			||||||
        mProgress: ProgressBar,
 | 
					 | 
				
			||||||
        formContainer: ConstraintLayout
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        val spoutsKV = HashMap<String, String>()
 | 
					        val spoutsKV = HashMap<String, String>()
 | 
				
			||||||
        spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
 | 
					        binding.spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
 | 
				
			||||||
            override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) {
 | 
					            override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) {
 | 
				
			||||||
                if (view != null) {
 | 
					                if (view != null) {
 | 
				
			||||||
                    val spoutName = (view as TextView).text.toString()
 | 
					                    val spoutName = (view as TextView).text.toString()
 | 
				
			||||||
@@ -84,11 +104,11 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        fun handleSpoutFailure(networkIssue: Boolean = false) {
 | 
					        fun handleSpoutFailure(networkIssue: Boolean = false) {
 | 
				
			||||||
            Toast.makeText(
 | 
					            Toast.makeText(
 | 
				
			||||||
                this@AddSourceActivity,
 | 
					                this@UpsertSourceActivity,
 | 
				
			||||||
                if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
 | 
					                if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
 | 
				
			||||||
                Toast.LENGTH_SHORT
 | 
					                Toast.LENGTH_SHORT
 | 
				
			||||||
            ).show()
 | 
					            ).show()
 | 
				
			||||||
            mProgress.visibility = View.GONE
 | 
					            binding.progress.visibility = View.GONE
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        CoroutineScope(Dispatchers.Main).launch {
 | 
					        CoroutineScope(Dispatchers.Main).launch {
 | 
				
			||||||
@@ -100,17 +120,21 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
                        spoutsKV[value.name] = key
 | 
					                        spoutsKV[value.name] = key
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    mProgress.visibility = View.GONE
 | 
					                    binding.progress.visibility = View.GONE
 | 
				
			||||||
                    formContainer.visibility = View.VISIBLE
 | 
					                    binding.formContainer.visibility = View.VISIBLE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    val spinnerArrayAdapter =
 | 
					                    val spinnerArrayAdapter =
 | 
				
			||||||
                        ArrayAdapter(
 | 
					                        ArrayAdapter(
 | 
				
			||||||
                            this@AddSourceActivity,
 | 
					                            this@UpsertSourceActivity,
 | 
				
			||||||
                            android.R.layout.simple_spinner_item,
 | 
					                            android.R.layout.simple_spinner_item,
 | 
				
			||||||
                            itemsStrings
 | 
					                            itemsStrings
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                    spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
 | 
					                    spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
 | 
				
			||||||
                    spoutsSpinner.adapter = spinnerArrayAdapter
 | 
					                    binding.spoutsSpinner.adapter = spinnerArrayAdapter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (existingSource != null) {
 | 
				
			||||||
 | 
					                        initFields(items)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    handleSpoutFailure()
 | 
					                    handleSpoutFailure()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -121,13 +145,11 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun maybeGetDetailsFromIntentSharing(
 | 
					    private fun maybeGetDetailsFromIntentSharing(
 | 
				
			||||||
        intent: Intent,
 | 
					        intent: Intent
 | 
				
			||||||
        sourceUri: EditText,
 | 
					 | 
				
			||||||
        nameInput: EditText
 | 
					 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
 | 
					        if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
 | 
				
			||||||
            sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
 | 
					            binding.sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
 | 
				
			||||||
            nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
 | 
					            binding.nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -138,7 +160,8 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
        finish()
 | 
					        finish()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun handleSaveSource(tags: EditText, title: String, url: String) {
 | 
					    private fun handleSaveSource() {
 | 
				
			||||||
 | 
					        val url = binding.sourceUri.text.toString()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val sourceDetailsUnavailable =
 | 
					        val sourceDetailsUnavailable =
 | 
				
			||||||
            title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
 | 
					            title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
 | 
				
			||||||
@@ -149,18 +172,27 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            else -> {
 | 
					            else -> {
 | 
				
			||||||
                CoroutineScope(Dispatchers.Main).launch {
 | 
					                CoroutineScope(Dispatchers.Main).launch {
 | 
				
			||||||
                    val successfullyAddedSource = repository.createSource(
 | 
					                    val successfullyAddedSource = if (existingSource != null) {
 | 
				
			||||||
                        title,
 | 
					                        repository.updateSource(
 | 
				
			||||||
 | 
					                            existingSource!!.id,
 | 
				
			||||||
 | 
					                            binding.nameInput.text.toString(),
 | 
				
			||||||
                            url,
 | 
					                            url,
 | 
				
			||||||
                            mSpoutsValue!!,
 | 
					                            mSpoutsValue!!,
 | 
				
			||||||
                        tags.text.toString(),
 | 
					                            binding.tags.text.toString()
 | 
				
			||||||
                        "",
 | 
					 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        repository.createSource(
 | 
				
			||||||
 | 
					                            binding.nameInput.text.toString(),
 | 
				
			||||||
 | 
					                            url,
 | 
				
			||||||
 | 
					                            mSpoutsValue!!,
 | 
				
			||||||
 | 
					                            binding.tags.text.toString(),
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                    if (successfullyAddedSource) {
 | 
					                    if (successfullyAddedSource) {
 | 
				
			||||||
                        finish()
 | 
					                        finish()
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        Toast.makeText(
 | 
					                        Toast.makeText(
 | 
				
			||||||
                            this@AddSourceActivity,
 | 
					                            this@UpsertSourceActivity,
 | 
				
			||||||
                            R.string.cant_create_source,
 | 
					                            R.string.cant_create_source,
 | 
				
			||||||
                            Toast.LENGTH_SHORT
 | 
					                            Toast.LENGTH_SHORT
 | 
				
			||||||
                        ).show()
 | 
					                        ).show()
 | 
				
			||||||
@@ -169,4 +201,9 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onDestroy() {
 | 
				
			||||||
 | 
					        super.onDestroy()
 | 
				
			||||||
 | 
					        repository.unsetSelectedSource()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -56,6 +56,10 @@ class ItemCardAdapter(
 | 
				
			|||||||
            val itm = items[position]
 | 
					            val itm = items[position]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            binding.favButton.isSelected = itm.starred
 | 
					            binding.favButton.isSelected = itm.starred
 | 
				
			||||||
 | 
					            if (appSettingsService.getPublicAccess()) {
 | 
				
			||||||
 | 
					                binding.favButton.visibility = View.GONE
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            binding.title.text = itm.title.getHtmlDecoded()
 | 
					            binding.title.text = itm.title.getHtmlDecoded()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            binding.title.setOnTouchListener(LinkOnTouchListener())
 | 
					            binding.title.setOnTouchListener(LinkOnTouchListener())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,12 +2,15 @@ package bou.amine.apps.readerforselfossv2.android.adapters
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.content.Intent
 | 
				
			||||||
import android.view.LayoutInflater
 | 
					import android.view.LayoutInflater
 | 
				
			||||||
 | 
					import android.view.View
 | 
				
			||||||
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 androidx.constraintlayout.widget.ConstraintLayout
 | 
					import androidx.constraintlayout.widget.ConstraintLayout
 | 
				
			||||||
import androidx.recyclerview.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
 | 
					import bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.R
 | 
					import bou.amine.apps.readerforselfossv2.android.R
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
 | 
					import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
 | 
					import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
 | 
				
			||||||
@@ -58,6 +61,13 @@ class SourcesListAdapter(
 | 
				
			|||||||
            c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
 | 
					            c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (itm.error.isNotBlank()) {
 | 
				
			||||||
 | 
					            binding.errorText.visibility = View.VISIBLE
 | 
				
			||||||
 | 
					            binding.errorText.text = itm.error
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            binding.errorText.visibility = View.GONE
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding.sourceTitle.text = itm.title.getHtmlDecoded()
 | 
					        binding.sourceTitle.text = itm.title.getHtmlDecoded()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -94,6 +104,14 @@ class SourcesListAdapter(
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mView.setOnClickListener {
 | 
				
			||||||
 | 
					                val source = items[bindingAdapterPosition]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                repository.setSelectedSource(source)
 | 
				
			||||||
 | 
					                app.startActivity(Intent(app, UpsertSourceActivity::class.java))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,8 @@ import org.kodein.di.instance
 | 
				
			|||||||
import java.util.*
 | 
					import java.util.*
 | 
				
			||||||
import kotlin.concurrent.schedule
 | 
					import kotlin.concurrent.schedule
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params), DIAware {
 | 
					class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params),
 | 
				
			||||||
 | 
					    DIAware {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override val di by lazy { (applicationContext as MyApp).di }
 | 
					    override val di by lazy { (applicationContext as MyApp).di }
 | 
				
			||||||
    private val repository: Repository by instance()
 | 
					    private val repository: Repository by instance()
 | 
				
			||||||
@@ -68,7 +69,6 @@ override fun doWork(): Result {
 | 
				
			|||||||
        newItems: List<SelfossModel.Item>?,
 | 
					        newItems: List<SelfossModel.Item>?,
 | 
				
			||||||
        notificationManager: NotificationManager
 | 
					        notificationManager: NotificationManager
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        // TODO: Check if this coroutine is actually required
 | 
					 | 
				
			||||||
        CoroutineScope(Dispatchers.IO).launch {
 | 
					        CoroutineScope(Dispatchers.IO).launch {
 | 
				
			||||||
            val apiItems = newItems.orEmpty()
 | 
					            val apiItems = newItems.orEmpty()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -84,10 +84,14 @@ override fun doWork(): Result {
 | 
				
			|||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    0
 | 
					                    0
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                    val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, pflags)
 | 
					                val pendingIntent: PendingIntent =
 | 
				
			||||||
 | 
					                    PendingIntent.getActivity(context, 0, intent, pflags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                val newItemsNotification =
 | 
					                val newItemsNotification =
 | 
				
			||||||
                        NotificationCompat.Builder(applicationContext, AppSettingsService.newItemsChannelId)
 | 
					                    NotificationCompat.Builder(
 | 
				
			||||||
 | 
					                        applicationContext,
 | 
				
			||||||
 | 
					                        AppSettingsService.newItemsChannelId
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                        .setContentTitle(context.getString(R.string.new_items_notification_title))
 | 
					                        .setContentTitle(context.getString(R.string.new_items_notification_title))
 | 
				
			||||||
                        .setContentText(
 | 
					                        .setContentText(
 | 
				
			||||||
                            context.getString(
 | 
					                            context.getString(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
package bou.amine.apps.readerforselfossv2.android.fragments
 | 
					package bou.amine.apps.readerforselfossv2.android.fragments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.ActivityNotFoundException
 | 
				
			||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.content.res.ColorStateList
 | 
					import android.content.res.ColorStateList
 | 
				
			||||||
import android.content.res.TypedArray
 | 
					import android.content.res.TypedArray
 | 
				
			||||||
@@ -28,7 +29,9 @@ import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
 | 
				
			|||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
 | 
					import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
 | 
					import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
 | 
					import bou.amine.apps.readerforselfossv2.android.utils.shareLink
 | 
				
			||||||
 | 
					import bou.amine.apps.readerforselfossv2.model.MercuryModel
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
 | 
					import bou.amine.apps.readerforselfossv2.model.SelfossModel
 | 
				
			||||||
 | 
					import bou.amine.apps.readerforselfossv2.model.StatusAndData
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
					import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
 | 
					import bou.amine.apps.readerforselfossv2.rest.MercuryApi
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
					import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
				
			||||||
@@ -55,6 +58,8 @@ import java.util.*
 | 
				
			|||||||
import java.util.concurrent.ExecutionException
 | 
					import java.util.concurrent.ExecutionException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private const val IMAGE_JPG = "image/jpg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ArticleFragment : Fragment(), DIAware {
 | 
					class ArticleFragment : Fragment(), DIAware {
 | 
				
			||||||
    private var fontSize: Int = 16
 | 
					    private var fontSize: Int = 16
 | 
				
			||||||
    private lateinit var item: SelfossModel.Item
 | 
					    private lateinit var item: SelfossModel.Item
 | 
				
			||||||
@@ -66,8 +71,7 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
    private lateinit var allImages: ArrayList<String>
 | 
					    private lateinit var allImages: ArrayList<String>
 | 
				
			||||||
    private lateinit var fab: FloatingActionButton
 | 
					    private lateinit var fab: FloatingActionButton
 | 
				
			||||||
    private lateinit var textAlignment: String
 | 
					    private lateinit var textAlignment: String
 | 
				
			||||||
    private var _binding: FragmentArticleBinding? = null
 | 
					    private lateinit var binding: FragmentArticleBinding
 | 
				
			||||||
    private val binding get() = _binding
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override val di: DI by closestDI()
 | 
					    override val di: DI by closestDI()
 | 
				
			||||||
    private val repository: Repository by instance()
 | 
					    private val repository: Repository by instance()
 | 
				
			||||||
@@ -95,7 +99,7 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
        savedInstanceState: Bundle?
 | 
					        savedInstanceState: Bundle?
 | 
				
			||||||
    ): View {
 | 
					    ): View {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            _binding = FragmentArticleBinding.inflate(inflater, container, false)
 | 
					            binding = FragmentArticleBinding.inflate(inflater, container, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            url = item.getLinkDecoded()
 | 
					            url = item.getLinkDecoded()
 | 
				
			||||||
            contentText = item.content
 | 
					            contentText = item.content
 | 
				
			||||||
@@ -110,13 +114,93 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            refreshAlignment()
 | 
					            refreshAlignment()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            fab = binding!!.fab
 | 
					            fab = binding.fab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
 | 
					            fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            fab.rippleColor = resources.getColor(R.color.colorAccentDark)
 | 
					            fab.rippleColor = resources.getColor(R.color.colorAccentDark)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val floatingToolbar: FloatingToolbar = binding!!.floatingToolbar
 | 
					            val floatingToolbar: FloatingToolbar = handleFloatingToolbar()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (staticBar) {
 | 
				
			||||||
 | 
					                fab.hide()
 | 
				
			||||||
 | 
					                floatingToolbar.show()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            binding.source.text = contentSource
 | 
				
			||||||
 | 
					            if (typeface != null) {
 | 
				
			||||||
 | 
					                binding.source.typeface = typeface
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            handleContent()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            binding.nestedScrollView.setOnScrollChangeListener(
 | 
				
			||||||
 | 
					                NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
 | 
				
			||||||
 | 
					                    if (scrollY > oldScrollY) {
 | 
				
			||||||
 | 
					                        floatingToolbar.hide()
 | 
				
			||||||
 | 
					                        fab.hide()
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        if (staticBar) {
 | 
				
			||||||
 | 
					                            floatingToolbar.show()
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } catch (e: InflateException) {
 | 
				
			||||||
 | 
					            e.sendSilentlyWithAcraWithName("webview not available")
 | 
				
			||||||
 | 
					            if (context != null) {
 | 
				
			||||||
 | 
					                AlertDialog.Builder(requireContext())
 | 
				
			||||||
 | 
					                    .setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
 | 
				
			||||||
 | 
					                    .setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
 | 
				
			||||||
 | 
					                    .setPositiveButton(
 | 
				
			||||||
 | 
					                        android.R.string.ok
 | 
				
			||||||
 | 
					                    ) { _, _ ->
 | 
				
			||||||
 | 
					                        appSettingsService.disableArticleViewer()
 | 
				
			||||||
 | 
					                        requireActivity().finish()
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    .create()
 | 
				
			||||||
 | 
					                    .show()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return binding.root
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun handleContent() {
 | 
				
			||||||
 | 
					        if (contentText.isEmptyOrNullOrNullString()) {
 | 
				
			||||||
 | 
					            if (repository.isNetworkAvailable()) {
 | 
				
			||||||
 | 
					                getContentFromMercury()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            binding.titleView.text = contentTitle
 | 
				
			||||||
 | 
					            if (typeface != null) {
 | 
				
			||||||
 | 
					                binding.titleView.typeface = typeface
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            htmlToWebview()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
 | 
				
			||||||
 | 
					                binding.imageView.visibility = View.VISIBLE
 | 
				
			||||||
 | 
					                Glide
 | 
				
			||||||
 | 
					                    .with(requireContext())
 | 
				
			||||||
 | 
					                    .asBitmap()
 | 
				
			||||||
 | 
					                    .load(contentImage)
 | 
				
			||||||
 | 
					                    .apply(RequestOptions.fitCenterTransform())
 | 
				
			||||||
 | 
					                    .into(binding.imageView)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                binding.imageView.visibility = View.GONE
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun handleFloatingToolbar(): FloatingToolbar {
 | 
				
			||||||
 | 
					        val floatingToolbar: FloatingToolbar = binding.floatingToolbar
 | 
				
			||||||
 | 
					        if (appSettingsService.getPublicAccess()) {
 | 
				
			||||||
 | 
					            floatingToolbar.setMenu(R.menu.reader_toolbar_no_read)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        floatingToolbar.attachFab(fab)
 | 
					        floatingToolbar.attachFab(fab)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
 | 
					        floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
 | 
				
			||||||
@@ -155,78 +239,11 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                override fun onItemLongClick(item: MenuItem?) {
 | 
					                override fun onItemLongClick(item: MenuItem?) {
 | 
				
			||||||
 | 
					                    // We do nothing
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        return floatingToolbar
 | 
				
			||||||
            if (staticBar) {
 | 
					 | 
				
			||||||
                fab.hide()
 | 
					 | 
				
			||||||
                floatingToolbar.show()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            binding!!.source.text = contentSource
 | 
					 | 
				
			||||||
            if (typeface != null) {
 | 
					 | 
				
			||||||
                binding!!.source.typeface = typeface
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (contentText.isEmptyOrNullOrNullString()) {
 | 
					 | 
				
			||||||
                getContentFromMercury()
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                binding!!.titleView.text = contentTitle
 | 
					 | 
				
			||||||
                if (typeface != null) {
 | 
					 | 
				
			||||||
                    binding!!.titleView.typeface = typeface
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                htmlToWebview()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
 | 
					 | 
				
			||||||
                    binding!!.imageView.visibility = View.VISIBLE
 | 
					 | 
				
			||||||
                    Glide
 | 
					 | 
				
			||||||
                        .with(requireContext())
 | 
					 | 
				
			||||||
                        .asBitmap()
 | 
					 | 
				
			||||||
                        .load(contentImage)
 | 
					 | 
				
			||||||
                        .apply(RequestOptions.fitCenterTransform())
 | 
					 | 
				
			||||||
                        .into(binding!!.imageView)
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    binding!!.imageView.visibility = View.GONE
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            binding!!.nestedScrollView.setOnScrollChangeListener(
 | 
					 | 
				
			||||||
                NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
 | 
					 | 
				
			||||||
                    if (scrollY > oldScrollY) {
 | 
					 | 
				
			||||||
                        floatingToolbar.hide()
 | 
					 | 
				
			||||||
                        fab.hide()
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        if (staticBar) {
 | 
					 | 
				
			||||||
                            floatingToolbar.show()
 | 
					 | 
				
			||||||
                        } else {
 | 
					 | 
				
			||||||
                            if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        } catch (e: InflateException) {
 | 
					 | 
				
			||||||
            e.sendSilentlyWithAcraWithName("webview not available")
 | 
					 | 
				
			||||||
            AlertDialog.Builder(requireContext())
 | 
					 | 
				
			||||||
                .setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
 | 
					 | 
				
			||||||
                .setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
 | 
					 | 
				
			||||||
                .setPositiveButton(android.R.string.ok
 | 
					 | 
				
			||||||
                ) { _, _ ->
 | 
					 | 
				
			||||||
                    appSettingsService.disableArticleViewer()
 | 
					 | 
				
			||||||
                    requireActivity().finish()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                .create()
 | 
					 | 
				
			||||||
                .show()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return binding!!.root
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun onDestroyView() {
 | 
					 | 
				
			||||||
        super.onDestroyView()
 | 
					 | 
				
			||||||
        _binding = null
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun refreshAlignment() {
 | 
					    private fun refreshAlignment() {
 | 
				
			||||||
@@ -238,42 +255,41 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun getContentFromMercury() {
 | 
					    private fun getContentFromMercury() {
 | 
				
			||||||
        if (repository.isNetworkAvailable()) {
 | 
					        binding.progressBar.visibility = View.VISIBLE
 | 
				
			||||||
            binding!!.progressBar.visibility = View.VISIBLE
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        CoroutineScope(Dispatchers.Main).launch {
 | 
					        CoroutineScope(Dispatchers.Main).launch {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                val response = mercuryApi.query(url)
 | 
					                val response = mercuryApi.query(url)
 | 
				
			||||||
                if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) {
 | 
					                if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) {
 | 
				
			||||||
                        binding!!.titleView.text = response.data!!.title.orEmpty()
 | 
					                    binding.titleView.text = response.data!!.title.orEmpty()
 | 
				
			||||||
                        try {
 | 
					 | 
				
			||||||
                    if (typeface != null) {
 | 
					                    if (typeface != null) {
 | 
				
			||||||
                                binding!!.titleView.typeface = typeface
 | 
					                        binding.titleView.typeface = typeface
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                        } catch (e: Exception) {
 | 
					 | 
				
			||||||
                            e.sendSilentlyWithAcraWithName("getContentFromMercury > typeface")
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        try {
 | 
					 | 
				
			||||||
                            // Note: Mercury may return relative urls... If it does the url val will not be changed.
 | 
					 | 
				
			||||||
                    URL(response.data!!.url)
 | 
					                    URL(response.data!!.url)
 | 
				
			||||||
                    url = response.data!!.url
 | 
					                    url = response.data!!.url
 | 
				
			||||||
                        } catch (e: MalformedURLException) {
 | 
					 | 
				
			||||||
                            // Mercury returned a relative url
 | 
					 | 
				
			||||||
                            e.sendSilentlyWithAcraWithName("getContentFromMercury > malformedurlexception")
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        try {
 | 
					 | 
				
			||||||
                    contentText = response.data!!.content.orEmpty()
 | 
					                    contentText = response.data!!.content.orEmpty()
 | 
				
			||||||
                    htmlToWebview()
 | 
					                    htmlToWebview()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    handleLeadImage(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    binding.nestedScrollView.scrollTo(0, 0)
 | 
				
			||||||
 | 
					                    binding.progressBar.visibility = View.GONE
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    openInBrowserAfterFailing()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (e: SocketTimeoutException) {
 | 
				
			||||||
 | 
					                openInBrowserAfterFailing()
 | 
				
			||||||
            } catch (e: Exception) {
 | 
					            } catch (e: Exception) {
 | 
				
			||||||
                            e.sendSilentlyWithAcraWithName("getContentFromMercury > contenttext or html")
 | 
					                e.sendSilentlyWithAcraWithName("getContentFromMercury > $url")
 | 
				
			||||||
 | 
					                openInBrowserAfterFailing()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun handleLeadImage(response: StatusAndData<MercuryModel.ParsedContent>) {
 | 
				
			||||||
        if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
 | 
					        if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
 | 
				
			||||||
                            try {
 | 
					            binding.imageView.visibility = View.VISIBLE
 | 
				
			||||||
                                binding!!.imageView.visibility = View.VISIBLE
 | 
					 | 
				
			||||||
                                try {
 | 
					 | 
				
			||||||
            Glide
 | 
					            Glide
 | 
				
			||||||
                .with(requireContext())
 | 
					                .with(requireContext())
 | 
				
			||||||
                .asBitmap()
 | 
					                .asBitmap()
 | 
				
			||||||
@@ -281,44 +297,84 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
                    response.data!!.lead_image_url.orEmpty()
 | 
					                    response.data!!.lead_image_url.orEmpty()
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                .apply(RequestOptions.fitCenterTransform())
 | 
					                .apply(RequestOptions.fitCenterTransform())
 | 
				
			||||||
                                        .into(binding!!.imageView)
 | 
					                .into(binding.imageView)
 | 
				
			||||||
                                } catch (e: Exception) {
 | 
					 | 
				
			||||||
                                    e.sendSilentlyWithAcraWithName("getContentFromMercury > glide lead image")
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            } catch (e: Exception) {
 | 
					 | 
				
			||||||
                                e.sendSilentlyWithAcraWithName("getContentFromMercury > outside glide lead image")
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
                            binding!!.imageView.visibility = View.GONE
 | 
					            binding.imageView.visibility = View.GONE
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun handleImageLoading() {
 | 
				
			||||||
 | 
					        binding.webcontent.webViewClient = object : WebViewClient() {
 | 
				
			||||||
 | 
					            @Deprecated("Deprecated in Java")
 | 
				
			||||||
 | 
					            override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
 | 
				
			||||||
 | 
					                return if (context != null && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
 | 
				
			||||||
                    try {
 | 
					                    try {
 | 
				
			||||||
                            binding!!.nestedScrollView.scrollTo(0, 0)
 | 
					                        requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
 | 
				
			||||||
                            binding!!.progressBar.visibility = View.GONE
 | 
					                    } catch (e: ActivityNotFoundException) {
 | 
				
			||||||
                        } catch (e: Exception) {
 | 
					                        e.sendSilentlyWithAcraWithName("activityNotFound > $url")
 | 
				
			||||||
                            e.sendSilentlyWithAcraWithName("getContentFromMercury > scrollview")
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    true
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                        openInBrowserAfterFailing()
 | 
					                    false
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                } catch (e: SocketTimeoutException) {
 | 
					 | 
				
			||||||
                    openInBrowserAfterFailing()
 | 
					 | 
				
			||||||
                } catch (e: Exception) {
 | 
					 | 
				
			||||||
                    e.sendSilentlyWithAcraWithName("getContentFromMercury > whole thing")
 | 
					 | 
				
			||||||
                    openInBrowserAfterFailing()
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @Deprecated("Deprecated in Java")
 | 
				
			||||||
 | 
					            override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
 | 
				
			||||||
 | 
					                val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
 | 
				
			||||||
 | 
					                if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US)
 | 
				
			||||||
 | 
					                        .contains(".jpeg")
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        val image =
 | 
				
			||||||
 | 
					                            Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
				
			||||||
 | 
					                        return WebResourceResponse(
 | 
				
			||||||
 | 
					                            IMAGE_JPG,
 | 
				
			||||||
 | 
					                            "UTF-8",
 | 
				
			||||||
 | 
					                            getBitmapInputStream(image, Bitmap.CompressFormat.JPEG)
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    } catch (e: ExecutionException) {
 | 
				
			||||||
 | 
					                        // Do nothing
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else if (url.lowercase(Locale.US).contains(".png")) {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        val image =
 | 
				
			||||||
 | 
					                            Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
				
			||||||
 | 
					                        return WebResourceResponse(
 | 
				
			||||||
 | 
					                            IMAGE_JPG,
 | 
				
			||||||
 | 
					                            "UTF-8",
 | 
				
			||||||
 | 
					                            getBitmapInputStream(image, Bitmap.CompressFormat.PNG)
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    } catch (e: ExecutionException) {
 | 
				
			||||||
 | 
					                        // Do nothing
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else if (url.lowercase(Locale.US).contains(".webp")) {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        val image =
 | 
				
			||||||
 | 
					                            Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
				
			||||||
 | 
					                        return WebResourceResponse(
 | 
				
			||||||
 | 
					                            IMAGE_JPG,
 | 
				
			||||||
 | 
					                            "UTF-8",
 | 
				
			||||||
 | 
					                            getBitmapInputStream(image, Bitmap.CompressFormat.WEBP)
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    } catch (e: ExecutionException) {
 | 
				
			||||||
 | 
					                        // Do nothing
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return super.shouldInterceptRequest(view, url)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun htmlToWebview() {
 | 
					    private fun htmlToWebview() {
 | 
				
			||||||
 | 
					        if (context != null) {
 | 
				
			||||||
            val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
 | 
					            val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
 | 
				
			||||||
            val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
 | 
					            val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding!!.webcontent.settings.standardFontFamily = a.getString(0)
 | 
					            binding.webcontent.settings.standardFontFamily = a.getString(0)
 | 
				
			||||||
        binding!!.webcontent.visibility = View.VISIBLE
 | 
					            binding.webcontent.visibility = View.VISIBLE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val colorOnSurface = TypedValue()
 | 
					            val colorOnSurface = TypedValue()
 | 
				
			||||||
            requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
 | 
					            requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
 | 
				
			||||||
@@ -326,60 +382,22 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
            val colorSurface = TypedValue()
 | 
					            val colorSurface = TypedValue()
 | 
				
			||||||
            requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
 | 
					            requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding!!.webcontent.settings.useWideViewPort = true
 | 
					            binding.webcontent.settings.useWideViewPort = true
 | 
				
			||||||
        binding!!.webcontent.settings.loadWithOverviewMode = true
 | 
					            binding.webcontent.settings.loadWithOverviewMode = true
 | 
				
			||||||
        binding!!.webcontent.settings.javaScriptEnabled = false
 | 
					            binding.webcontent.settings.javaScriptEnabled = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding!!.webcontent.webViewClient = object : WebViewClient() {
 | 
					            handleImageLoading()
 | 
				
			||||||
            @Deprecated("Deprecated in Java")
 | 
					 | 
				
			||||||
            override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
 | 
					 | 
				
			||||||
                if (binding!!.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
 | 
					 | 
				
			||||||
                    requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return true
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            @Deprecated("Deprecated in Java")
 | 
					            val gestureDetector =
 | 
				
			||||||
            override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
 | 
					                GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
 | 
				
			||||||
                val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
 | 
					 | 
				
			||||||
                if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) {
 | 
					 | 
				
			||||||
                    try {
 | 
					 | 
				
			||||||
                        val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
					 | 
				
			||||||
                        return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
 | 
					 | 
				
			||||||
                    } catch ( e : ExecutionException) {
 | 
					 | 
				
			||||||
                        e.sendSilentlyWithAcraWithName("shouldInterceptRequest > jpeg > $url")
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else if (url.lowercase(Locale.US).contains(".png")) {
 | 
					 | 
				
			||||||
                    try {
 | 
					 | 
				
			||||||
                        val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
					 | 
				
			||||||
                        return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
 | 
					 | 
				
			||||||
                    } catch ( e : ExecutionException) {
 | 
					 | 
				
			||||||
                        e.sendSilentlyWithAcraWithName("shouldInterceptRequest > png > $url")
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else if (url.lowercase(Locale.US).contains(".webp")) {
 | 
					 | 
				
			||||||
                    try {
 | 
					 | 
				
			||||||
                        val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
					 | 
				
			||||||
                        return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
 | 
					 | 
				
			||||||
                    } catch ( e : ExecutionException) {
 | 
					 | 
				
			||||||
                        e.sendSilentlyWithAcraWithName("shouldInterceptRequest > webp > $url")
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return super.shouldInterceptRequest(view, url)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
 | 
					 | 
				
			||||||
                    override fun onSingleTapUp(e: MotionEvent): Boolean {
 | 
					                    override fun onSingleTapUp(e: MotionEvent): Boolean {
 | 
				
			||||||
                        return performClick()
 | 
					                        return performClick()
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding!!.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
 | 
					            binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding!!.webcontent.settings.layoutAlgorithm =
 | 
					            binding.webcontent.settings.layoutAlgorithm =
 | 
				
			||||||
                WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
 | 
					                WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var baseUrl: String? = null
 | 
					            var baseUrl: String? = null
 | 
				
			||||||
@@ -399,7 +417,12 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val fontLinkAndStyle = if (font.isNotEmpty()) {
 | 
					            val fontLinkAndStyle = if (font.isNotEmpty()) {
 | 
				
			||||||
            """<link href="https://fonts.googleapis.com/css?family=${fontName.replace(" ", "+")}" rel="stylesheet">
 | 
					                """<link href="https://fonts.googleapis.com/css?family=${
 | 
				
			||||||
 | 
					                    fontName.replace(
 | 
				
			||||||
 | 
					                        " ",
 | 
				
			||||||
 | 
					                        "+"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                }" rel="stylesheet">
 | 
				
			||||||
                |<style>
 | 
					                |<style>
 | 
				
			||||||
                |   * {
 | 
					                |   * {
 | 
				
			||||||
                |       font-family: '$fontName';
 | 
					                |       font-family: '$fontName';
 | 
				
			||||||
@@ -410,7 +433,7 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
                ""
 | 
					                ""
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding!!.webcontent.loadDataWithBaseURL(
 | 
					            binding.webcontent.loadDataWithBaseURL(
 | 
				
			||||||
                baseUrl,
 | 
					                baseUrl,
 | 
				
			||||||
                """<html>
 | 
					                """<html>
 | 
				
			||||||
                |<head>
 | 
					                |<head>
 | 
				
			||||||
@@ -423,7 +446,12 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
                |        max-width: 100%;
 | 
					                |        max-width: 100%;
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |      a {
 | 
					                |      a {
 | 
				
			||||||
                |        color: ${String.format("#%06X", 0xFFFFFF and resources.getColor(R.color.colorAccent))} !important;
 | 
					                |        color: ${
 | 
				
			||||||
 | 
					                    String.format(
 | 
				
			||||||
 | 
					                        "#%06X",
 | 
				
			||||||
 | 
					                        0xFFFFFF and resources.getColor(R.color.colorAccent)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                } !important;
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |      *:not(a) {
 | 
					                |      *:not(a) {
 | 
				
			||||||
                |        color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
 | 
					                |        color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
 | 
				
			||||||
@@ -434,11 +462,26 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
                |        word-break: break-word;
 | 
					                |        word-break: break-word;
 | 
				
			||||||
                |        overflow:hidden;
 | 
					                |        overflow:hidden;
 | 
				
			||||||
                |        line-height: 1.5em;
 | 
					                |        line-height: 1.5em;
 | 
				
			||||||
                |        background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
 | 
					                |        background-color: ${
 | 
				
			||||||
 | 
					                    String.format(
 | 
				
			||||||
 | 
					                        "#%06X",
 | 
				
			||||||
 | 
					                        0xFFFFFF and colorSurface.data
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |      body, html {
 | 
					                |      body, html {
 | 
				
			||||||
                |        background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)} !important;
 | 
					                |        background-color: ${
 | 
				
			||||||
                |        border-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)}  !important;
 | 
					                    String.format(
 | 
				
			||||||
 | 
					                        "#%06X",
 | 
				
			||||||
 | 
					                        0xFFFFFF and colorSurface.data
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                } !important;
 | 
				
			||||||
 | 
					                |        border-color: ${
 | 
				
			||||||
 | 
					                    String.format(
 | 
				
			||||||
 | 
					                        "#%06X",
 | 
				
			||||||
 | 
					                        0xFFFFFF and colorSurface.data
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                }  !important;
 | 
				
			||||||
                |        padding: 0 !important;
 | 
					                |        padding: 0 !important;
 | 
				
			||||||
                |        margin: 0 !important;
 | 
					                |        margin: 0 !important;
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
@@ -448,7 +491,12 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
                |      pre, code {
 | 
					                |      pre, code {
 | 
				
			||||||
                |        white-space: pre-wrap;
 | 
					                |        white-space: pre-wrap;
 | 
				
			||||||
                |        width:100%;
 | 
					                |        width:100%;
 | 
				
			||||||
                |        background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
 | 
					                |        background-color: ${
 | 
				
			||||||
 | 
					                    String.format(
 | 
				
			||||||
 | 
					                        "#%06X",
 | 
				
			||||||
 | 
					                        0xFFFFFF and colorSurface.data
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |   </style>
 | 
					                |   </style>
 | 
				
			||||||
                |   $fontLinkAndStyle
 | 
					                |   $fontLinkAndStyle
 | 
				
			||||||
@@ -461,19 +509,20 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
                null
 | 
					                null
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun scrollDown() {
 | 
					    fun scrollDown() {
 | 
				
			||||||
        val height = binding!!.nestedScrollView.measuredHeight
 | 
					        val height = binding.nestedScrollView.measuredHeight
 | 
				
			||||||
        binding!!.nestedScrollView.smoothScrollBy(0, height/2)
 | 
					        binding.nestedScrollView.smoothScrollBy(0, height / 2)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun scrollUp() {
 | 
					    fun scrollUp() {
 | 
				
			||||||
        val height = binding!!.nestedScrollView.measuredHeight
 | 
					        val height = binding.nestedScrollView.measuredHeight
 | 
				
			||||||
        binding!!.nestedScrollView.smoothScrollBy(0, -height/2)
 | 
					        binding.nestedScrollView.smoothScrollBy(0, -height / 2)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun openInBrowserAfterFailing() {
 | 
					    private fun openInBrowserAfterFailing() {
 | 
				
			||||||
        binding!!.progressBar.visibility = View.GONE
 | 
					        binding.progressBar.visibility = View.GONE
 | 
				
			||||||
        requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
 | 
					        requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -492,10 +541,11 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun performClick(): Boolean {
 | 
					    fun performClick(): Boolean {
 | 
				
			||||||
        if (binding!!.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
 | 
					        if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
 | 
				
			||||||
                binding!!.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
 | 
					            binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val position : Int = allImages.indexOf(binding!!.webcontent.hitTestResult.extra)
 | 
					            val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val intent = Intent(activity, ImageActivity::class.java)
 | 
					            val intent = Intent(activity, ImageActivity::class.java)
 | 
				
			||||||
            intent.putExtra("allImages", allImages)
 | 
					            intent.putExtra("allImages", allImages)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,9 @@ import android.content.Context
 | 
				
			|||||||
import android.graphics.Color
 | 
					import android.graphics.Color
 | 
				
			||||||
import android.graphics.drawable.Drawable
 | 
					import android.graphics.drawable.Drawable
 | 
				
			||||||
import android.graphics.drawable.GradientDrawable
 | 
					import android.graphics.drawable.GradientDrawable
 | 
				
			||||||
 | 
					import android.os.Build
 | 
				
			||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
 | 
					import android.text.TextUtils
 | 
				
			||||||
import android.view.LayoutInflater
 | 
					import android.view.LayoutInflater
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
import android.view.View.GONE
 | 
					import android.view.View.GONE
 | 
				
			||||||
@@ -12,15 +14,14 @@ import android.view.View.VISIBLE
 | 
				
			|||||||
import android.view.ViewGroup
 | 
					import android.view.ViewGroup
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.HomeActivity
 | 
					import bou.amine.apps.readerforselfossv2.android.HomeActivity
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.R
 | 
					import bou.amine.apps.readerforselfossv2.android.R
 | 
				
			||||||
 | 
					import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
 | 
					import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
					import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
					import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
 | 
					import bou.amine.apps.readerforselfossv2.utils.getIcon
 | 
				
			||||||
import com.bumptech.glide.Glide
 | 
					import com.bumptech.glide.Glide
 | 
				
			||||||
import com.bumptech.glide.load.DataSource
 | 
					import com.bumptech.glide.request.target.ViewTarget
 | 
				
			||||||
import com.bumptech.glide.load.engine.GlideException
 | 
					import com.bumptech.glide.request.transition.Transition
 | 
				
			||||||
import com.bumptech.glide.request.RequestListener
 | 
					 | 
				
			||||||
import com.bumptech.glide.request.target.Target
 | 
					 | 
				
			||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
 | 
					import com.google.android.material.bottomsheet.BottomSheetDialogFragment
 | 
				
			||||||
import com.google.android.material.chip.Chip
 | 
					import com.google.android.material.chip.Chip
 | 
				
			||||||
import kotlinx.coroutines.CoroutineScope
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
@@ -34,6 +35,7 @@ import org.kodein.di.instance
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
 | 
					class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lateinit var binding: FilterFragmentBinding
 | 
				
			||||||
    override val di: DI by closestDI()
 | 
					    override val di: DI by closestDI()
 | 
				
			||||||
    private val repository: Repository by instance()
 | 
					    private val repository: Repository by instance()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,8 +46,8 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
 | 
				
			|||||||
        container: ViewGroup?,
 | 
					        container: ViewGroup?,
 | 
				
			||||||
        savedInstanceState: Bundle?
 | 
					        savedInstanceState: Bundle?
 | 
				
			||||||
    ): View {
 | 
					    ): View {
 | 
				
			||||||
        val binding =
 | 
					        binding =
 | 
				
			||||||
            bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding.inflate(
 | 
					            FilterFragmentBinding.inflate(
 | 
				
			||||||
                inflater,
 | 
					                inflater,
 | 
				
			||||||
                container,
 | 
					                container,
 | 
				
			||||||
                false
 | 
					                false
 | 
				
			||||||
@@ -53,84 +55,52 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        val context: Context? = context
 | 
					        val context: Context? = context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val tagGroup = binding.tagsGroup
 | 
					 | 
				
			||||||
        val sourceGroup = binding.sourcesGroup
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (context == null) {
 | 
					        if (context == null) {
 | 
				
			||||||
            dismiss()
 | 
					            dismiss()
 | 
				
			||||||
            Exception("FilterSheetFragment context is null").sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView")
 | 
					            Exception("FilterSheetFragment context is null").sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView")
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            CoroutineScope(Dispatchers.Main).launch {
 | 
					            CoroutineScope(Dispatchers.Main).launch {
 | 
				
			||||||
                val tags = repository.getTags()
 | 
					                handleTagChips(context)
 | 
				
			||||||
 | 
					                handleSourceChips(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                tags.forEach { tag ->
 | 
					                binding.progressBar2.visibility = GONE
 | 
				
			||||||
                    val c = Chip(context)
 | 
					                binding.filterView.visibility = VISIBLE
 | 
				
			||||||
                    c.text = tag.tag
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    val gd = GradientDrawable()
 | 
					 | 
				
			||||||
                    val gdColor = try {
 | 
					 | 
				
			||||||
                        Color.parseColor(tag.color)
 | 
					 | 
				
			||||||
                    } catch (e: IllegalArgumentException) {
 | 
					 | 
				
			||||||
                        e.sendSilentlyWithAcraWithName("color issue " + tag.color)
 | 
					 | 
				
			||||||
                        resources.getColor(R.color.colorPrimary)
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
                    gd.setColor(gdColor)
 | 
					 | 
				
			||||||
                    gd.shape = GradientDrawable.RECTANGLE
 | 
					 | 
				
			||||||
                    gd.setSize(30, 30)
 | 
					 | 
				
			||||||
                    gd.cornerRadius = 30F
 | 
					 | 
				
			||||||
                    c.chipIcon = gd
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    c.setOnCloseIconClickListener {
 | 
					 | 
				
			||||||
                        (it as Chip).isCloseIconVisible = false
 | 
					 | 
				
			||||||
                        selectedChip = null
 | 
					 | 
				
			||||||
                        repository.setTagFilter(null)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    c.setOnClickListener {
 | 
					        binding.floatingActionButton2.setOnClickListener {
 | 
				
			||||||
                        if (selectedChip != null) {
 | 
					            (activity as HomeActivity).getElementsAccordingToTab()
 | 
				
			||||||
                            selectedChip!!.isCloseIconVisible = false
 | 
					            (activity as HomeActivity).fetchOnEmptyList()
 | 
				
			||||||
 | 
					            dismiss()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
                        (it as Chip).isCloseIconVisible = true
 | 
					        return binding.root
 | 
				
			||||||
                        selectedChip = it
 | 
					 | 
				
			||||||
                        repository.setTagFilter(tag)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        repository.setSourceFilter(null)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (repository.tagFilter.value?.equals(tag) == true) {
 | 
					    private suspend fun handleSourceChips(
 | 
				
			||||||
                        c.isCloseIconVisible = true
 | 
					        context: Context
 | 
				
			||||||
                        selectedChip = c
 | 
					    ) {
 | 
				
			||||||
                    }
 | 
					        val sourceGroup = binding.sourcesGroup
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    tagGroup.addView(c)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        repository.getSources().forEach { source ->
 | 
					        repository.getSources().forEach { source ->
 | 
				
			||||||
            val c = Chip(context)
 | 
					            val c = Chip(context)
 | 
				
			||||||
 | 
					            c.ellipsize = TextUtils.TruncateAt.END
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Glide.with(context)
 | 
					            Glide.with(context)
 | 
				
			||||||
                .load(source.getIcon(repository.baseUrl))
 | 
					                .load(source.getIcon(repository.baseUrl))
 | 
				
			||||||
                        .listener(object : RequestListener<Drawable?> {
 | 
					                .into(object : ViewTarget<Chip?, Drawable?>(c) {
 | 
				
			||||||
                            override fun onLoadFailed(
 | 
					                    override fun onResourceReady(
 | 
				
			||||||
                                e: GlideException?,
 | 
					                        resource: Drawable,
 | 
				
			||||||
                                model: Any?,
 | 
					                        transition: Transition<in Drawable?>?
 | 
				
			||||||
                                target: Target<Drawable?>?,
 | 
					                    ) {
 | 
				
			||||||
                                isFirstResource: Boolean
 | 
					                        try {
 | 
				
			||||||
                            ): Boolean {
 | 
					                            c.chipIcon = resource
 | 
				
			||||||
                                return false
 | 
					                        } catch (e: Exception) {
 | 
				
			||||||
 | 
					                            e.sendSilentlyWithAcraWithName("sources > onResourceReady")
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            override fun onResourceReady(
 | 
					                })
 | 
				
			||||||
                                resource: Drawable?,
 | 
					 | 
				
			||||||
                                model: Any?,
 | 
					 | 
				
			||||||
                                target: Target<Drawable?>?,
 | 
					 | 
				
			||||||
                                dataSource: DataSource?,
 | 
					 | 
				
			||||||
                                isFirstResource: Boolean
 | 
					 | 
				
			||||||
                            ): Boolean {
 | 
					 | 
				
			||||||
                                c.chipIcon = resource
 | 
					 | 
				
			||||||
                                return false
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }).preload()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            c.text = source.title.getHtmlDecoded()
 | 
					            c.text = source.title.getHtmlDecoded()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -157,20 +127,69 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
 | 
				
			|||||||
                selectedChip = c
 | 
					                selectedChip = c
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            c.isEnabled = source.error.isBlank()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (source.error.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 | 
				
			||||||
 | 
					                c.tooltipText = source.error
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            sourceGroup.addView(c)
 | 
					            sourceGroup.addView(c)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                binding.progressBar2.visibility = GONE
 | 
					 | 
				
			||||||
                binding.filterView.visibility = VISIBLE
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding.floatingActionButton2.setOnClickListener {
 | 
					    private suspend fun handleTagChips(
 | 
				
			||||||
            (activity as HomeActivity).getElementsAccordingToTab()
 | 
					        context: Context,
 | 
				
			||||||
            (activity as HomeActivity).fetchOnEmptyList()
 | 
					    ) {
 | 
				
			||||||
            dismiss()
 | 
					        val tagGroup = binding.tagsGroup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val tags = repository.getTags()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tags.forEach { tag ->
 | 
				
			||||||
 | 
					            val c = Chip(context)
 | 
				
			||||||
 | 
					            c.ellipsize = TextUtils.TruncateAt.END
 | 
				
			||||||
 | 
					            c.text = tag.tag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                val gd = GradientDrawable()
 | 
				
			||||||
 | 
					                val gdColor = try {
 | 
				
			||||||
 | 
					                    Color.parseColor(tag.color)
 | 
				
			||||||
 | 
					                } catch (e: IllegalArgumentException) {
 | 
				
			||||||
 | 
					                    e.sendSilentlyWithAcraWithName("color issue " + tag.color)
 | 
				
			||||||
 | 
					                    resources.getColor(R.color.colorPrimary)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                gd.setColor(gdColor)
 | 
				
			||||||
 | 
					                gd.shape = GradientDrawable.RECTANGLE
 | 
				
			||||||
 | 
					                gd.setSize(30, 30)
 | 
				
			||||||
 | 
					                gd.cornerRadius = 30F
 | 
				
			||||||
 | 
					                c.chipIcon = gd
 | 
				
			||||||
 | 
					            } catch (e: Exception) {
 | 
				
			||||||
 | 
					                e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            c.setOnCloseIconClickListener {
 | 
				
			||||||
 | 
					                (it as Chip).isCloseIconVisible = false
 | 
				
			||||||
 | 
					                selectedChip = null
 | 
				
			||||||
 | 
					                repository.setTagFilter(null)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            c.setOnClickListener {
 | 
				
			||||||
 | 
					                if (selectedChip != null) {
 | 
				
			||||||
 | 
					                    selectedChip!!.isCloseIconVisible = false
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                (it as Chip).isCloseIconVisible = true
 | 
				
			||||||
 | 
					                selectedChip = it
 | 
				
			||||||
 | 
					                repository.setTagFilter(tag)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                repository.setSourceFilter(null)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (repository.tagFilter.value?.equals(tag) == true) {
 | 
				
			||||||
 | 
					                c.isCloseIconVisible = true
 | 
				
			||||||
 | 
					                selectedChip = c
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            tagGroup.addView(c)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return binding.root
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,6 @@ import bou.amine.apps.readerforselfossv2.utils.getImages
 | 
				
			|||||||
import com.bumptech.glide.Glide
 | 
					import com.bumptech.glide.Glide
 | 
				
			||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
 | 
					import com.bumptech.glide.load.engine.DiskCacheStrategy
 | 
				
			||||||
import com.bumptech.glide.request.RequestOptions
 | 
					import com.bumptech.glide.request.RequestOptions
 | 
				
			||||||
import org.acra.ktx.sendSilentlyWithAcra
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
 | 
					fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
 | 
				
			||||||
    val imageUrls = this.getImages()
 | 
					    val imageUrls = this.getImages()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,6 @@ import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
				
			|||||||
import com.mikepenz.aboutlibraries.LibsBuilder
 | 
					import com.mikepenz.aboutlibraries.LibsBuilder
 | 
				
			||||||
import org.kodein.di.DIAware
 | 
					import org.kodein.di.DIAware
 | 
				
			||||||
import org.kodein.di.android.closestDI
 | 
					import org.kodein.di.android.closestDI
 | 
				
			||||||
import org.kodein.di.instance
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
private const val TITLE_TAG = "settingsActivityTitle"
 | 
					private const val TITLE_TAG = "settingsActivityTitle"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -146,8 +145,12 @@ class SettingsActivity : AppCompatActivity(),
 | 
				
			|||||||
            fontSize?.setOnBindEditTextListener { editText ->
 | 
					            fontSize?.setOnBindEditTextListener { editText ->
 | 
				
			||||||
                editText.inputType = InputType.TYPE_CLASS_NUMBER
 | 
					                editText.inputType = InputType.TYPE_CLASS_NUMBER
 | 
				
			||||||
                editText.addTextChangedListener { object : TextWatcher {
 | 
					                editText.addTextChangedListener { object : TextWatcher {
 | 
				
			||||||
                    override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
 | 
					                    override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
 | 
				
			||||||
                    override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
 | 
					                        // We do nothing
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
 | 
				
			||||||
 | 
					                        // We do nothing
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                    override fun afterTextChanged(editable: Editable) {
 | 
					                    override fun afterTextChanged(editable: Editable) {
 | 
				
			||||||
                        try {
 | 
					                        try {
 | 
				
			||||||
                            editText.textSize = editable.toString().toInt().toFloat()
 | 
					                            editText.textSize = editable.toString().toInt().toFloat()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,12 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
<androidx.drawerlayout.widget.DrawerLayout
 | 
					<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
					    xmlns:tools="http://schemas.android.com/tools"
 | 
				
			||||||
    android:id="@+id/drawerContainer"
 | 
					    android:id="@+id/drawerContainer"
 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
    android:layout_height="match_parent"
 | 
					    android:layout_height="match_parent"
 | 
				
			||||||
    tools:context="bou.amine.apps.readerforselfossv2.android.HomeActivity"
 | 
					 | 
				
			||||||
    android:fitsSystemWindows="true"
 | 
					    android:fitsSystemWindows="true"
 | 
				
			||||||
    xmlns:app="http://schemas.android.com/apk/res-auto">
 | 
					    tools:context="bou.amine.apps.readerforselfossv2.android.HomeActivity">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <androidx.coordinatorlayout.widget.CoordinatorLayout
 | 
					    <androidx.coordinatorlayout.widget.CoordinatorLayout
 | 
				
			||||||
        android:id="@+id/coordLayout"
 | 
					        android:id="@+id/coordLayout"
 | 
				
			||||||
@@ -28,10 +27,12 @@
 | 
				
			|||||||
                    android:layout_width="match_parent"
 | 
					                    android:layout_width="match_parent"
 | 
				
			||||||
                    android:layout_height="wrap_content">
 | 
					                    android:layout_height="wrap_content">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <androidx.appcompat.widget.Toolbar app:popupTheme="?attr/toolbarPopupTheme" android:theme="@style/ToolBarStyle"
 | 
					                    <androidx.appcompat.widget.Toolbar
 | 
				
			||||||
                        android:id="@+id/toolBar"
 | 
					                        android:id="@+id/toolBar"
 | 
				
			||||||
                        android:layout_width="match_parent"
 | 
					                        android:layout_width="match_parent"
 | 
				
			||||||
                        android:layout_height="?attr/actionBarSize"
 | 
					                        android:layout_height="?attr/actionBarSize"
 | 
				
			||||||
 | 
					                        android:theme="@style/ToolBarStyle"
 | 
				
			||||||
 | 
					                        app:popupTheme="?attr/toolbarPopupTheme"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        />
 | 
					                        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,19 +46,19 @@
 | 
				
			|||||||
                    <LinearLayout
 | 
					                    <LinearLayout
 | 
				
			||||||
                        android:layout_width="match_parent"
 | 
					                        android:layout_width="match_parent"
 | 
				
			||||||
                        android:layout_height="match_parent"
 | 
					                        android:layout_height="match_parent"
 | 
				
			||||||
                        android:orientation="vertical"
 | 
					                        android:background="?android:attr/windowBackground"
 | 
				
			||||||
                        android:background="?android:attr/windowBackground">
 | 
					                        android:orientation="vertical">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <TextView
 | 
					                        <TextView
 | 
				
			||||||
                            android:id="@+id/emptyText"
 | 
					                            android:id="@+id/emptyText"
 | 
				
			||||||
                            android:layout_width="match_parent"
 | 
					                            android:layout_width="match_parent"
 | 
				
			||||||
                            android:layout_height="wrap_content"
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:background="@android:color/transparent"
 | 
				
			||||||
                            android:gravity="center_horizontal"
 | 
					                            android:gravity="center_horizontal"
 | 
				
			||||||
                            android:paddingTop="100dp"
 | 
					                            android:paddingTop="100dp"
 | 
				
			||||||
                            android:text="@string/nothing_here"
 | 
					                            android:text="@string/nothing_here"
 | 
				
			||||||
                            android:textAlignment="center"
 | 
					                            android:textAlignment="center"
 | 
				
			||||||
                            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
 | 
					                            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
 | 
				
			||||||
                            android:background="@android:color/transparent"
 | 
					 | 
				
			||||||
                            android:visibility="gone" />
 | 
					                            android:visibility="gone" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <androidx.recyclerview.widget.RecyclerView
 | 
					                        <androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
@@ -77,6 +78,7 @@
 | 
				
			|||||||
            </LinearLayout>
 | 
					            </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        </androidx.coordinatorlayout.widget.CoordinatorLayout>
 | 
					        </androidx.coordinatorlayout.widget.CoordinatorLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <com.ashokvarma.bottomnavigation.BottomNavigationBar
 | 
					        <com.ashokvarma.bottomnavigation.BottomNavigationBar
 | 
				
			||||||
            android:id="@+id/bottomBar"
 | 
					            android:id="@+id/bottomBar"
 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					            android:layout_width="match_parent"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,17 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
 | 
					    android:id="@+id/container"
 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
    android:layout_height="match_parent">
 | 
					    android:layout_height="match_parent"
 | 
				
			||||||
 | 
					    app:layoutDescription="@xml/image_close_scene">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <com.google.android.material.appbar.AppBarLayout
 | 
					    <com.google.android.material.appbar.AppBarLayout
 | 
				
			||||||
        android:id="@+id/appBarLayout"
 | 
					        android:id="@+id/appBarLayout"
 | 
				
			||||||
        android:layout_width="match_parent"
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content">
 | 
				
			||||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
					 | 
				
			||||||
        app:layout_constraintStart_toStartOf="parent"
 | 
					 | 
				
			||||||
        app:layout_constraintTop_toTopOf="parent">
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <androidx.appcompat.widget.Toolbar
 | 
					        <androidx.appcompat.widget.Toolbar android:theme="@style/ToolBarStyle"
 | 
				
			||||||
            android:id="@+id/toolBar"
 | 
					            android:id="@+id/toolBar"
 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					            android:layout_width="match_parent"
 | 
				
			||||||
            android:layout_height="?attr/actionBarSize"
 | 
					            android:layout_height="?attr/actionBarSize"
 | 
				
			||||||
@@ -21,13 +20,21 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    </com.google.android.material.appbar.AppBarLayout>
 | 
					    </com.google.android.material.appbar.AppBarLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <androidx.core.widget.NestedScrollView
 | 
				
			||||||
 | 
					        android:id="@+id/scrollView"
 | 
				
			||||||
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
 | 
					        android:layout_height="match_parent"
 | 
				
			||||||
 | 
					        android:fillViewport="true"
 | 
				
			||||||
 | 
					        app:layout_constraintTop_toBottomOf="@+id/appBarLayout">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <androidx.viewpager2.widget.ViewPager2
 | 
					        <androidx.viewpager2.widget.ViewPager2
 | 
				
			||||||
            android:id="@+id/pager"
 | 
					            android:id="@+id/pager"
 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					            android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="0dp"
 | 
					            android:layout_height="match_parent"
 | 
				
			||||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
					            app:layout_constraintBottom_toBottomOf="parent"
 | 
				
			||||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
					            app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
        app:layout_constraintStart_toStartOf="parent"
 | 
					            app:layout_constraintStart_toStartOf="parent" />
 | 
				
			||||||
        app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
 | 
					    </androidx.core.widget.NestedScrollView>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
					
 | 
				
			||||||
 | 
					</androidx.constraintlayout.motion.widget.MotionLayout>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,31 +1,30 @@
 | 
				
			|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
					    xmlns:tools="http://schemas.android.com/tools"
 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
    android:layout_height="match_parent"
 | 
					    android:layout_height="match_parent"
 | 
				
			||||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
					 | 
				
			||||||
    android:gravity="center_horizontal"
 | 
					    android:gravity="center_horizontal"
 | 
				
			||||||
    android:orientation="vertical"
 | 
					    android:orientation="vertical"
 | 
				
			||||||
    tools:context="bou.amine.apps.readerforselfossv2.android.LoginActivity">
 | 
					    tools:context="bou.amine.apps.readerforselfossv2.android.LoginActivity">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <com.google.android.material.appbar.AppBarLayout
 | 
					    <com.google.android.material.appbar.AppBarLayout
 | 
				
			||||||
        android:layout_width="match_parent"
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="wrap_content">
 | 
					        android:layout_height="wrap_content">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <androidx.appcompat.widget.Toolbar app:popupTheme="?attr/toolbarPopupTheme" android:theme="@style/ToolBarStyle"
 | 
					        <androidx.appcompat.widget.Toolbar
 | 
				
			||||||
            android:id="@+id/toolbar"
 | 
					            android:id="@+id/toolbar"
 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					            android:layout_width="match_parent"
 | 
				
			||||||
            android:layout_height="?attr/actionBarSize"
 | 
					            android:layout_height="?attr/actionBarSize"
 | 
				
			||||||
 | 
					            android:theme="@style/ToolBarStyle"
 | 
				
			||||||
             />
 | 
					            app:popupTheme="?attr/toolbarPopupTheme" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    </com.google.android.material.appbar.AppBarLayout>
 | 
					    </com.google.android.material.appbar.AppBarLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <LinearLayout
 | 
					    <LinearLayout
 | 
				
			||||||
        android:layout_width="match_parent"
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="match_parent"
 | 
					        android:layout_height="match_parent"
 | 
				
			||||||
        android:orientation="vertical"
 | 
					        android:orientation="vertical"
 | 
				
			||||||
        android:paddingBottom="@dimen/activity_vertical_margin"
 | 
					        android:padding="@dimen/activity_horizontal_margin">
 | 
				
			||||||
        android:paddingLeft="@dimen/activity_horizontal_margin"
 | 
					 | 
				
			||||||
        android:paddingRight="@dimen/activity_horizontal_margin"
 | 
					 | 
				
			||||||
        android:paddingTop="@dimen/activity_vertical_margin">
 | 
					 | 
				
			||||||
        <!-- Login progress -->
 | 
					        <!-- Login progress -->
 | 
				
			||||||
        <ProgressBar
 | 
					        <ProgressBar
 | 
				
			||||||
            android:id="@+id/loginProgress"
 | 
					            android:id="@+id/loginProgress"
 | 
				
			||||||
@@ -35,12 +34,8 @@
 | 
				
			|||||||
            android:layout_marginBottom="8dp"
 | 
					            android:layout_marginBottom="8dp"
 | 
				
			||||||
            android:visibility="gone" />
 | 
					            android:visibility="gone" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <ScrollView
 | 
					 | 
				
			||||||
            android:id="@+id/loginForm"
 | 
					 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					 | 
				
			||||||
            android:layout_height="match_parent">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <LinearLayout
 | 
					        <LinearLayout
 | 
				
			||||||
 | 
					            android:id="@+id/loginForm"
 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					            android:layout_width="match_parent"
 | 
				
			||||||
            android:layout_height="wrap_content"
 | 
					            android:layout_height="wrap_content"
 | 
				
			||||||
            android:orientation="vertical">
 | 
					            android:orientation="vertical">
 | 
				
			||||||
@@ -53,14 +48,15 @@
 | 
				
			|||||||
                android:imeOptions="actionUnspecified"
 | 
					                android:imeOptions="actionUnspecified"
 | 
				
			||||||
                android:importantForAutofill="no"
 | 
					                android:importantForAutofill="no"
 | 
				
			||||||
                android:inputType="textUri"
 | 
					                android:inputType="textUri"
 | 
				
			||||||
                    android:maxLines="1" />
 | 
					                android:maxLines="1"
 | 
				
			||||||
 | 
					                android:minHeight="48dp" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <com.google.android.material.switchmaterial.SwitchMaterial
 | 
					            <com.google.android.material.switchmaterial.SwitchMaterial
 | 
				
			||||||
                    android:text="@string/withLoginSwitch"
 | 
					 | 
				
			||||||
                    android:layout_width="match_parent"
 | 
					 | 
				
			||||||
                    android:layout_height="0dp"
 | 
					 | 
				
			||||||
                android:id="@+id/withLogin"
 | 
					                android:id="@+id/withLogin"
 | 
				
			||||||
                    android:layout_weight="1"/>
 | 
					                android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                android:text="@string/withLoginSwitch"
 | 
				
			||||||
 | 
					                android:textAlignment="viewStart" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <EditText
 | 
					            <EditText
 | 
				
			||||||
                android:id="@+id/loginView"
 | 
					                android:id="@+id/loginView"
 | 
				
			||||||
@@ -70,6 +66,7 @@
 | 
				
			|||||||
                android:hint="@string/prompt_login"
 | 
					                android:hint="@string/prompt_login"
 | 
				
			||||||
                android:inputType="text"
 | 
					                android:inputType="text"
 | 
				
			||||||
                android:maxLines="1"
 | 
					                android:maxLines="1"
 | 
				
			||||||
 | 
					                android:minHeight="48dp"
 | 
				
			||||||
                android:visibility="gone" />
 | 
					                android:visibility="gone" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <EditText
 | 
					            <EditText
 | 
				
			||||||
@@ -80,6 +77,7 @@
 | 
				
			|||||||
                android:hint="@string/prompt_password"
 | 
					                android:hint="@string/prompt_password"
 | 
				
			||||||
                android:inputType="textPassword"
 | 
					                android:inputType="textPassword"
 | 
				
			||||||
                android:maxLines="1"
 | 
					                android:maxLines="1"
 | 
				
			||||||
 | 
					                android:minHeight="48dp"
 | 
				
			||||||
                android:visibility="gone" />
 | 
					                android:visibility="gone" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <Button
 | 
					            <Button
 | 
				
			||||||
@@ -93,7 +91,6 @@
 | 
				
			|||||||
                android:textStyle="bold" />
 | 
					                android:textStyle="bold" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        </LinearLayout>
 | 
					        </LinearLayout>
 | 
				
			||||||
        </ScrollView>
 | 
					 | 
				
			||||||
    </LinearLayout>
 | 
					    </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</LinearLayout>
 | 
					</LinearLayout>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,8 @@
 | 
				
			|||||||
        android:layout_width="match_parent"
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="match_parent"
 | 
					        android:layout_height="match_parent"
 | 
				
			||||||
        android:scrollbars="vertical"
 | 
					        android:scrollbars="vertical"
 | 
				
			||||||
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
 | 
					        app:layout_behavior="@string/appbar_scrolling_view_behavior"
 | 
				
			||||||
 | 
					        tools:listitem="@layout/source_list_item">
 | 
				
			||||||
    </androidx.recyclerview.widget.RecyclerView>
 | 
					    </androidx.recyclerview.widget.RecyclerView>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <com.google.android.material.floatingactionbutton.FloatingActionButton
 | 
					    <com.google.android.material.floatingactionbutton.FloatingActionButton
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
					    xmlns:tools="http://schemas.android.com/tools"
 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
    android:layout_height="match_parent"
 | 
					    android:layout_height="match_parent"
 | 
				
			||||||
    tools:context="bou.amine.apps.readerforselfossv2.android.AddSourceActivity">
 | 
					    tools:context="bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity">
 | 
				
			||||||
    <LinearLayout
 | 
					    <LinearLayout
 | 
				
			||||||
        android:layout_width="match_parent"
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
@@ -17,116 +17,83 @@
 | 
				
			|||||||
            <androidx.appcompat.widget.Toolbar app:popupTheme="?attr/toolbarPopupTheme" android:theme="@style/ToolBarStyle"
 | 
					            <androidx.appcompat.widget.Toolbar app:popupTheme="?attr/toolbarPopupTheme" android:theme="@style/ToolBarStyle"
 | 
				
			||||||
                android:id="@+id/toolbar"
 | 
					                android:id="@+id/toolbar"
 | 
				
			||||||
                android:layout_width="match_parent"
 | 
					                android:layout_width="match_parent"
 | 
				
			||||||
                android:layout_height="?attr/actionBarSize"
 | 
					                android:layout_height="?attr/actionBarSize" />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                 />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        </com.google.android.material.appbar.AppBarLayout>
 | 
					        </com.google.android.material.appbar.AppBarLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <androidx.constraintlayout.widget.ConstraintLayout
 | 
					        <androidx.constraintlayout.widget.ConstraintLayout
 | 
				
			||||||
            android:paddingBottom="@dimen/activity_vertical_margin"
 | 
					 | 
				
			||||||
            android:paddingLeft="@dimen/activity_horizontal_margin"
 | 
					 | 
				
			||||||
            android:paddingRight="@dimen/activity_horizontal_margin"
 | 
					 | 
				
			||||||
            android:paddingTop="@dimen/activity_vertical_margin"
 | 
					 | 
				
			||||||
            android:layout_height="match_parent"
 | 
					 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					 | 
				
			||||||
            app:layout_constraintTop_toTopOf="parent"
 | 
					 | 
				
			||||||
            app:layout_constraintRight_toRightOf="parent"
 | 
					 | 
				
			||||||
            app:layout_constraintLeft_toLeftOf="parent"
 | 
					 | 
				
			||||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
					 | 
				
			||||||
            android:id="@+id/formContainer"
 | 
					            android:id="@+id/formContainer"
 | 
				
			||||||
 | 
					            android:layout_width="match_parent"
 | 
				
			||||||
 | 
					            android:layout_height="match_parent"
 | 
				
			||||||
 | 
					            android:padding="16dp"
 | 
				
			||||||
            android:visibility="gone"
 | 
					            android:visibility="gone"
 | 
				
			||||||
            app:layout_constraintHorizontal_bias="1.0"
 | 
					            app:layout_constraintHorizontal_bias="1.0"
 | 
				
			||||||
            app:layout_constraintVertical_bias="0.0">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <TextView
 | 
					 | 
				
			||||||
                android:text="@string/add_source"
 | 
					 | 
				
			||||||
                android:layout_width="wrap_content"
 | 
					 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
                android:id="@+id/textView2"
 | 
					 | 
				
			||||||
                android:textAppearance="@style/TextAppearance.AppCompat.Large"
 | 
					 | 
				
			||||||
                android:textAlignment="center"
 | 
					 | 
				
			||||||
                android:layout_marginTop="16dp"
 | 
					 | 
				
			||||||
                app:layout_constraintTop_toTopOf="parent"
 | 
					 | 
				
			||||||
                android:layout_marginEnd="16dp"
 | 
					 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					 | 
				
			||||||
                android:layout_marginRight="16dp"
 | 
					 | 
				
			||||||
                android:layout_marginStart="16dp"
 | 
					 | 
				
			||||||
            app:layout_constraintLeft_toLeftOf="parent"
 | 
					            app:layout_constraintLeft_toLeftOf="parent"
 | 
				
			||||||
                android:layout_marginLeft="16dp"
 | 
					            app:layout_constraintTop_toTopOf="parent">
 | 
				
			||||||
                android:gravity="center_horizontal" />
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <EditText
 | 
					            <EditText
 | 
				
			||||||
                android:layout_width="match_parent"
 | 
					 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
                android:ems="10"
 | 
					 | 
				
			||||||
                android:id="@+id/nameInput"
 | 
					                android:id="@+id/nameInput"
 | 
				
			||||||
                android:layout_marginTop="32dp"
 | 
					                android:layout_width="match_parent"
 | 
				
			||||||
                app:layout_constraintTop_toBottomOf="@+id/textView2"
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="parent"
 | 
					                android:minHeight="48dp"
 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					                android:layout_marginTop="16dp"
 | 
				
			||||||
                android:inputType="text"
 | 
					                android:autofillHints="false"
 | 
				
			||||||
                android:hint="@string/add_source_hint_name"
 | 
					                android:hint="@string/add_source_hint_name"
 | 
				
			||||||
                android:textColorHint="?android:textColorPrimary"
 | 
					 | 
				
			||||||
                android:autofillHints="false" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <EditText
 | 
					 | 
				
			||||||
                android:layout_width="match_parent"
 | 
					 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
                android:inputType="textUri"
 | 
					 | 
				
			||||||
                android:ems="10"
 | 
					 | 
				
			||||||
                android:id="@+id/sourceUri"
 | 
					 | 
				
			||||||
                android:hint="@string/add_source_hint_url"
 | 
					 | 
				
			||||||
                android:textColorHint="?android:textColorPrimary"
 | 
					 | 
				
			||||||
                android:layout_marginTop="16dp"
 | 
					 | 
				
			||||||
                app:layout_constraintTop_toBottomOf="@+id/nameInput"
 | 
					 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="parent"
 | 
					 | 
				
			||||||
                android:autofillHints="false" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <EditText
 | 
					 | 
				
			||||||
                android:layout_width="match_parent"
 | 
					 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
                android:ems="10"
 | 
					 | 
				
			||||||
                android:id="@+id/tags"
 | 
					 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="parent"
 | 
					 | 
				
			||||||
                android:layout_marginTop="16dp"
 | 
					 | 
				
			||||||
                app:layout_constraintTop_toBottomOf="@+id/sourceUri"
 | 
					 | 
				
			||||||
                android:hint="@string/add_source_hint_tags"
 | 
					 | 
				
			||||||
                android:textColorHint="?android:textColorPrimary"
 | 
					 | 
				
			||||||
                android:inputType="text"
 | 
					                android:inputType="text"
 | 
				
			||||||
                android:autofillHints="false" />
 | 
					                android:textColorHint="?android:textColorPrimary"
 | 
				
			||||||
 | 
					                app:layout_constraintLeft_toLeftOf="parent"
 | 
				
			||||||
 | 
					                app:layout_constraintTop_toTopOf="parent" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <EditText
 | 
				
			||||||
 | 
					                android:id="@+id/sourceUri"
 | 
				
			||||||
 | 
					                android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                android:minHeight="48dp"
 | 
				
			||||||
 | 
					                android:layout_marginTop="16dp"
 | 
				
			||||||
 | 
					                android:autofillHints="false"
 | 
				
			||||||
 | 
					                android:hint="@string/add_source_hint_url"
 | 
				
			||||||
 | 
					                android:inputType="textUri"
 | 
				
			||||||
 | 
					                android:textColorHint="?android:textColorPrimary"
 | 
				
			||||||
 | 
					                app:layout_constraintLeft_toLeftOf="parent"
 | 
				
			||||||
 | 
					                app:layout_constraintTop_toBottomOf="@+id/nameInput" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <EditText
 | 
				
			||||||
 | 
					                android:id="@+id/tags"
 | 
				
			||||||
 | 
					                android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                android:minHeight="48dp"
 | 
				
			||||||
 | 
					                android:layout_marginTop="16dp"
 | 
				
			||||||
 | 
					                android:autofillHints="false"
 | 
				
			||||||
 | 
					                android:hint="@string/add_source_hint_tags"
 | 
				
			||||||
 | 
					                android:inputType="text"
 | 
				
			||||||
 | 
					                android:textColorHint="?android:textColorPrimary"
 | 
				
			||||||
 | 
					                app:layout_constraintLeft_toLeftOf="parent"
 | 
				
			||||||
 | 
					                app:layout_constraintTop_toBottomOf="@+id/sourceUri" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <Spinner
 | 
					            <Spinner
 | 
				
			||||||
                android:layout_width="match_parent"
 | 
					 | 
				
			||||||
                android:id="@+id/spoutsSpinner"
 | 
					                android:id="@+id/spoutsSpinner"
 | 
				
			||||||
 | 
					                android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                android:minHeight="48dp"
 | 
				
			||||||
                android:layout_marginTop="16dp"
 | 
					                android:layout_marginTop="16dp"
 | 
				
			||||||
                app:layout_constraintTop_toBottomOf="@+id/tags"
 | 
					 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="parent"
 | 
					                app:layout_constraintLeft_toLeftOf="parent"
 | 
				
			||||||
                android:layout_height="40dp"/>
 | 
					                app:layout_constraintTop_toBottomOf="@+id/tags" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <Button
 | 
					            <Button
 | 
				
			||||||
                android:text="@string/add_source_save"
 | 
					                android:id="@+id/saveBtn"
 | 
				
			||||||
                android:layout_width="wrap_content"
 | 
					                android:layout_width="wrap_content"
 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
                android:id="@+id/saveBtn"
 | 
					 | 
				
			||||||
                android:elevation="5dp"
 | 
					 | 
				
			||||||
                android:textColor="?android:textColorPrimary"
 | 
					 | 
				
			||||||
                android:layout_marginEnd="16dp"
 | 
					 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					 | 
				
			||||||
                android:layout_marginRight="16dp"
 | 
					 | 
				
			||||||
                android:layout_marginStart="16dp"
 | 
					 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="parent"
 | 
					 | 
				
			||||||
                android:layout_marginLeft="16dp"
 | 
					 | 
				
			||||||
                android:layout_marginTop="16dp"
 | 
					                android:layout_marginTop="16dp"
 | 
				
			||||||
                app:layout_constraintTop_toBottomOf="@+id/spoutsSpinner"
 | 
					                android:elevation="5dp"
 | 
				
			||||||
 | 
					                android:text="@string/add_source_save"
 | 
				
			||||||
 | 
					                android:textColor="?android:textColorPrimary"
 | 
				
			||||||
                app:layout_constraintBottom_toBottomOf="parent"
 | 
					                app:layout_constraintBottom_toBottomOf="parent"
 | 
				
			||||||
                android:layout_marginBottom="16dp"
 | 
					                app:layout_constraintLeft_toLeftOf="parent"
 | 
				
			||||||
                app:layout_constraintVertical_bias="0.0"/>
 | 
					                app:layout_constraintRight_toRightOf="parent"
 | 
				
			||||||
 | 
					                app:layout_constraintTop_toBottomOf="@+id/spoutsSpinner" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
					        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -135,8 +102,6 @@
 | 
				
			|||||||
            style="?android:attr/progressBarStyleLarge"
 | 
					            style="?android:attr/progressBarStyleLarge"
 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					            android:layout_width="match_parent"
 | 
				
			||||||
            android:layout_height="wrap_content"
 | 
					            android:layout_height="wrap_content"
 | 
				
			||||||
            android:visibility="visible"
 | 
					 | 
				
			||||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
					 | 
				
			||||||
            app:layout_constraintLeft_toLeftOf="parent"
 | 
					            app:layout_constraintLeft_toLeftOf="parent"
 | 
				
			||||||
            app:layout_constraintRight_toRightOf="parent"
 | 
					            app:layout_constraintRight_toRightOf="parent"
 | 
				
			||||||
            app:layout_constraintTop_toTopOf="parent"
 | 
					            app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
@@ -1,18 +1,14 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
<androidx.cardview.widget.CardView
 | 
					<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					 | 
				
			||||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
 | 
					    xmlns:card_view="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
					    xmlns:tools="http://schemas.android.com/tools"
 | 
				
			||||||
    android:id="@+id/card"
 | 
					    android:id="@+id/card"
 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
    android:layout_height="wrap_content"
 | 
					    android:layout_height="wrap_content"
 | 
				
			||||||
    android:layout_marginLeft="8dp"
 | 
					    android:layout_margin="8dp"
 | 
				
			||||||
    android:layout_marginRight="8dp"
 | 
					    app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
    android:layout_marginTop="8dp"
 | 
					    app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
    app:layout_constraintHorizontal_bias="0.62"
 | 
					 | 
				
			||||||
    app:layout_constraintLeft_toLeftOf="parent"
 | 
					 | 
				
			||||||
    app:layout_constraintRight_toRightOf="parent"
 | 
					 | 
				
			||||||
    app:layout_constraintTop_toTopOf="parent"
 | 
					    app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
    card_view:cardElevation="2dp"
 | 
					    card_view:cardElevation="2dp"
 | 
				
			||||||
    card_view:cardUseCompatPadding="true"
 | 
					    card_view:cardUseCompatPadding="true"
 | 
				
			||||||
@@ -28,8 +24,8 @@
 | 
				
			|||||||
            android:layout_height="wrap_content"
 | 
					            android:layout_height="wrap_content"
 | 
				
			||||||
            android:adjustViewBounds="true"
 | 
					            android:adjustViewBounds="true"
 | 
				
			||||||
            android:cropToPadding="true"
 | 
					            android:cropToPadding="true"
 | 
				
			||||||
            app:layout_constraintLeft_toLeftOf="parent"
 | 
					            app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
            app:layout_constraintRight_toRightOf="parent"
 | 
					            app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
            app:layout_constraintTop_toTopOf="parent"
 | 
					            app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
            app:srcCompat="@drawable/background_splash"
 | 
					            app:srcCompat="@drawable/background_splash"
 | 
				
			||||||
            card_view:layout_constraintBottom_toTopOf="@+id/constraintLayout" />
 | 
					            card_view:layout_constraintBottom_toTopOf="@+id/constraintLayout" />
 | 
				
			||||||
@@ -39,18 +35,17 @@
 | 
				
			|||||||
            android:layout_width="0dp"
 | 
					            android:layout_width="0dp"
 | 
				
			||||||
            android:layout_height="wrap_content"
 | 
					            android:layout_height="wrap_content"
 | 
				
			||||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
					            app:layout_constraintBottom_toBottomOf="parent"
 | 
				
			||||||
            app:layout_constraintLeft_toLeftOf="parent"
 | 
					            app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
            app:layout_constraintRight_toRightOf="parent"
 | 
					            app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
            app:layout_constraintTop_toBottomOf="@+id/itemImage">
 | 
					            app:layout_constraintTop_toBottomOf="@+id/itemImage">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <ImageView
 | 
					            <ImageView
 | 
				
			||||||
                android:id="@+id/sourceImage"
 | 
					                android:id="@+id/sourceImage"
 | 
				
			||||||
                android:layout_width="40dp"
 | 
					                android:layout_width="40dp"
 | 
				
			||||||
                android:layout_height="40dp"
 | 
					                android:layout_height="40dp"
 | 
				
			||||||
                android:layout_marginLeft="8dp"
 | 
					 | 
				
			||||||
                android:layout_marginStart="8dp"
 | 
					                android:layout_marginStart="8dp"
 | 
				
			||||||
                android:layout_marginTop="8dp"
 | 
					                android:layout_marginTop="8dp"
 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="parent"
 | 
					                app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
                app:layout_constraintTop_toTopOf="parent"
 | 
					                app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
                app:srcCompat="@drawable/background_splash" />
 | 
					                app:srcCompat="@drawable/background_splash" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,70 +53,58 @@
 | 
				
			|||||||
                android:id="@+id/title"
 | 
					                android:id="@+id/title"
 | 
				
			||||||
                android:layout_width="0dp"
 | 
					                android:layout_width="0dp"
 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
                android:layout_marginEnd="8dp"
 | 
					                android:layout_margin="8dp"
 | 
				
			||||||
                android:layout_marginLeft="8dp"
 | 
					 | 
				
			||||||
                android:layout_marginRight="8dp"
 | 
					 | 
				
			||||||
                android:layout_marginStart="8dp"
 | 
					 | 
				
			||||||
                android:gravity="start"
 | 
					 | 
				
			||||||
                android:textAlignment="viewStart"
 | 
					                android:textAlignment="viewStart"
 | 
				
			||||||
                android:textStyle="bold"
 | 
					 | 
				
			||||||
                android:textColor="?android:textColorPrimary"
 | 
					                android:textColor="?android:textColorPrimary"
 | 
				
			||||||
                app:layout_constraintHorizontal_bias="0.0"
 | 
					                android:textStyle="bold"
 | 
				
			||||||
                app:layout_constraintLeft_toRightOf="@+id/sourceImage"
 | 
					                app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					                app:layout_constraintStart_toEndOf="@+id/sourceImage"
 | 
				
			||||||
                app:layout_constraintTop_toTopOf="@+id/sourceImage"
 | 
					                app:layout_constraintTop_toTopOf="@+id/sourceImage"
 | 
				
			||||||
                tools:text="Titre" />
 | 
					                tools:text="Titre" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <TextView
 | 
					            <TextView
 | 
				
			||||||
                android:id="@+id/sourceTitleAndDate"
 | 
					                android:id="@+id/sourceTitleAndDate"
 | 
				
			||||||
                android:layout_width="wrap_content"
 | 
					                android:layout_width="0dp"
 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
                android:layout_marginTop="8dp"
 | 
					                android:layout_marginTop="8dp"
 | 
				
			||||||
                android:gravity="start"
 | 
					 | 
				
			||||||
                android:textAlignment="viewStart"
 | 
					                android:textAlignment="viewStart"
 | 
				
			||||||
                android:textSize="14sp"
 | 
					 | 
				
			||||||
                android:textColor="?android:textColorPrimary"
 | 
					                android:textColor="?android:textColorPrimary"
 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="@+id/title"
 | 
					                android:textSize="14sp"
 | 
				
			||||||
 | 
					                app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
 | 
					                app:layout_constraintStart_toStartOf="@+id/title"
 | 
				
			||||||
                app:layout_constraintTop_toBottomOf="@+id/title"
 | 
					                app:layout_constraintTop_toBottomOf="@+id/title"
 | 
				
			||||||
                tools:text="Google Actualité Il y a 5h" />
 | 
					                tools:text="Google Actualité Il y a 5h" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <RelativeLayout
 | 
					            <LinearLayout
 | 
				
			||||||
                android:layout_width="0dp"
 | 
					                android:layout_width="wrap_content"
 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
                android:layout_marginTop="16dp"
 | 
					                android:layout_marginTop="8dp"
 | 
				
			||||||
 | 
					                android:layout_marginEnd="8dp"
 | 
				
			||||||
                app:layout_constraintBottom_toBottomOf="parent"
 | 
					                app:layout_constraintBottom_toBottomOf="parent"
 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="parent"
 | 
					                app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					 | 
				
			||||||
                app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate">
 | 
					                app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <ImageButton
 | 
					                <ImageButton
 | 
				
			||||||
                    android:id="@+id/favButton"
 | 
					                    android:id="@+id/browserBtn"
 | 
				
			||||||
                    android:layout_width="35dp"
 | 
					                    android:layout_width="35dp"
 | 
				
			||||||
                    android:layout_height="35dp"
 | 
					                    android:layout_height="35dp"
 | 
				
			||||||
                    android:layout_alignParentEnd="true"
 | 
					 | 
				
			||||||
                    android:layout_alignParentRight="true"
 | 
					 | 
				
			||||||
                    android:layout_centerVertical="true"
 | 
					 | 
				
			||||||
                    android:layout_marginEnd="8dp"
 | 
					 | 
				
			||||||
                    android:layout_marginRight="8dp"
 | 
					 | 
				
			||||||
                    android:adjustViewBounds="true"
 | 
					                    android:adjustViewBounds="true"
 | 
				
			||||||
                    android:background="@android:color/transparent"
 | 
					                    android:background="@android:color/transparent"
 | 
				
			||||||
 | 
					                    android:contentDescription="@string/reader_action_open"
 | 
				
			||||||
                    android:elevation="5dp"
 | 
					                    android:elevation="5dp"
 | 
				
			||||||
                    android:padding="4dp"
 | 
					                    android:padding="4dp"
 | 
				
			||||||
                    android:scaleType="centerCrop"
 | 
					                    android:scaleType="centerCrop"
 | 
				
			||||||
                    app:srcCompat="@drawable/ic_menu_heart_60dp"
 | 
					                    app:srcCompat="@drawable/ic_open_in_browser_black_24dp"
 | 
				
			||||||
                    app:tint="@color/ic_menu_heart_color" />
 | 
					                    app:tint="?android:attr/textColorPrimary" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <ImageButton
 | 
					                <ImageButton
 | 
				
			||||||
                    android:id="@+id/shareBtn"
 | 
					                    android:id="@+id/shareBtn"
 | 
				
			||||||
                    android:layout_width="35dp"
 | 
					                    android:layout_width="35dp"
 | 
				
			||||||
                    android:layout_height="35dp"
 | 
					                    android:layout_height="35dp"
 | 
				
			||||||
                    android:layout_centerVertical="true"
 | 
					                    android:layout_marginStart="16dp"
 | 
				
			||||||
                    android:layout_marginEnd="16dp"
 | 
					 | 
				
			||||||
                    android:layout_marginRight="16dp"
 | 
					 | 
				
			||||||
                    android:layout_toLeftOf="@+id/favButton"
 | 
					 | 
				
			||||||
                    android:layout_toStartOf="@+id/favButton"
 | 
					 | 
				
			||||||
                    android:adjustViewBounds="true"
 | 
					                    android:adjustViewBounds="true"
 | 
				
			||||||
                    android:background="@android:color/transparent"
 | 
					                    android:background="@android:color/transparent"
 | 
				
			||||||
 | 
					                    android:contentDescription="@string/share"
 | 
				
			||||||
                    android:elevation="5dp"
 | 
					                    android:elevation="5dp"
 | 
				
			||||||
                    android:padding="4dp"
 | 
					                    android:padding="4dp"
 | 
				
			||||||
                    android:scaleType="centerCrop"
 | 
					                    android:scaleType="centerCrop"
 | 
				
			||||||
@@ -129,23 +112,21 @@
 | 
				
			|||||||
                    app:tint="?android:attr/textColorPrimary" />
 | 
					                    app:tint="?android:attr/textColorPrimary" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <ImageButton
 | 
					                <ImageButton
 | 
				
			||||||
                    android:id="@+id/browserBtn"
 | 
					                    android:id="@+id/favButton"
 | 
				
			||||||
                    android:layout_width="35dp"
 | 
					                    android:layout_width="35dp"
 | 
				
			||||||
                    android:layout_height="35dp"
 | 
					                    android:layout_height="35dp"
 | 
				
			||||||
                    android:layout_centerVertical="true"
 | 
					                    android:layout_marginStart="16dp"
 | 
				
			||||||
                    android:layout_marginEnd="16dp"
 | 
					 | 
				
			||||||
                    android:layout_marginRight="16dp"
 | 
					 | 
				
			||||||
                    android:layout_toLeftOf="@+id/shareBtn"
 | 
					 | 
				
			||||||
                    android:layout_toStartOf="@+id/shareBtn"
 | 
					 | 
				
			||||||
                    android:adjustViewBounds="true"
 | 
					                    android:adjustViewBounds="true"
 | 
				
			||||||
                    android:background="@android:color/transparent"
 | 
					                    android:background="@android:color/transparent"
 | 
				
			||||||
 | 
					                    android:contentDescription="@string/add_to_favs_reader"
 | 
				
			||||||
                    android:elevation="5dp"
 | 
					                    android:elevation="5dp"
 | 
				
			||||||
                    android:padding="4dp"
 | 
					                    android:padding="4dp"
 | 
				
			||||||
                    android:scaleType="centerCrop"
 | 
					                    android:scaleType="centerCrop"
 | 
				
			||||||
                    app:srcCompat="@drawable/ic_open_in_browser_black_24dp"
 | 
					                    app:srcCompat="@drawable/ic_menu_heart_60dp"
 | 
				
			||||||
                    app:tint="?android:attr/textColorPrimary" />
 | 
					                    app:tint="@color/ic_menu_heart_color" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            </RelativeLayout>
 | 
					
 | 
				
			||||||
 | 
					            </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
					        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
				
			||||||
    </androidx.constraintlayout.widget.ConstraintLayout>
 | 
					    </androidx.constraintlayout.widget.ConstraintLayout>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
<androidx.constraintlayout.widget.ConstraintLayout
 | 
					<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					 | 
				
			||||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
					    xmlns:tools="http://schemas.android.com/tools"
 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
@@ -17,27 +16,18 @@
 | 
				
			|||||||
        app:layout_constraintTop_toTopOf="parent"
 | 
					        app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
        tools:visibility="gone" />
 | 
					        tools:visibility="gone" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <androidx.core.widget.NestedScrollView
 | 
				
			||||||
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
 | 
					        android:layout_height="match_parent"
 | 
				
			||||||
 | 
					        android:fillViewport="true">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <androidx.constraintlayout.widget.ConstraintLayout
 | 
					        <androidx.constraintlayout.widget.ConstraintLayout
 | 
				
			||||||
            android:id="@+id/filterView"
 | 
					            android:id="@+id/filterView"
 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					            android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="match_parent"
 | 
					            android:layout_height="wrap_content"
 | 
				
			||||||
            android:visibility="gone"
 | 
					            android:visibility="gone"
 | 
				
			||||||
            tools:visibility="visible">
 | 
					            tools:visibility="visible">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <com.google.android.material.floatingactionbutton.FloatingActionButton
 | 
					 | 
				
			||||||
            android:id="@+id/floatingActionButton2"
 | 
					 | 
				
			||||||
            android:layout_width="wrap_content"
 | 
					 | 
				
			||||||
            android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
            android:layout_marginEnd="16dp"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            android:layout_marginTop="8dp"
 | 
					 | 
				
			||||||
            android:clickable="true"
 | 
					 | 
				
			||||||
            app:backgroundTint="@color/colorAccent"
 | 
					 | 
				
			||||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
					 | 
				
			||||||
            app:layout_constraintTop_toTopOf="parent"
 | 
					 | 
				
			||||||
            app:rippleColor="@color/colorAccentDark"
 | 
					 | 
				
			||||||
            app:srcCompat="@drawable/ic_menu_search_white_24dp" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <TextView
 | 
					            <TextView
 | 
				
			||||||
                android:id="@+id/filterTagsTitle"
 | 
					                android:id="@+id/filterTagsTitle"
 | 
				
			||||||
                style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
 | 
					                style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
 | 
				
			||||||
@@ -49,19 +39,6 @@
 | 
				
			|||||||
                app:layout_constraintStart_toStartOf="parent"
 | 
					                app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
                app:layout_constraintTop_toTopOf="parent" />
 | 
					                app:layout_constraintTop_toTopOf="parent" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        <com.google.android.material.chip.ChipGroup
 | 
					 | 
				
			||||||
            android:id="@+id/tagsGroup"
 | 
					 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					 | 
				
			||||||
            android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
            android:layout_marginStart="16dp"
 | 
					 | 
				
			||||||
            android:layout_marginTop="24dp"
 | 
					 | 
				
			||||||
            app:layout_constraintStart_toStartOf="parent"
 | 
					 | 
				
			||||||
            app:layout_constraintTop_toBottomOf="@+id/filterTagsTitle"
 | 
					 | 
				
			||||||
            app:singleSelection="true">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        </com.google.android.material.chip.ChipGroup>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <TextView
 | 
					            <TextView
 | 
				
			||||||
                android:id="@+id/filterSourcesTitle"
 | 
					                android:id="@+id/filterSourcesTitle"
 | 
				
			||||||
                style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
 | 
					                style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
 | 
				
			||||||
@@ -74,6 +51,18 @@
 | 
				
			|||||||
                app:layout_constraintTop_toBottomOf="@+id/tagsGroup" />
 | 
					                app:layout_constraintTop_toBottomOf="@+id/tagsGroup" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <com.google.android.material.chip.ChipGroup
 | 
				
			||||||
 | 
					                android:id="@+id/tagsGroup"
 | 
				
			||||||
 | 
					                android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                android:layout_marginStart="16dp"
 | 
				
			||||||
 | 
					                android:layout_marginTop="24dp"
 | 
				
			||||||
 | 
					                app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
 | 
					                app:layout_constraintTop_toBottomOf="@+id/filterTagsTitle"
 | 
				
			||||||
 | 
					                app:singleSelection="true">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            </com.google.android.material.chip.ChipGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <com.google.android.material.chip.ChipGroup
 | 
					            <com.google.android.material.chip.ChipGroup
 | 
				
			||||||
                android:id="@+id/sourcesGroup"
 | 
					                android:id="@+id/sourcesGroup"
 | 
				
			||||||
                android:layout_width="match_parent"
 | 
					                android:layout_width="match_parent"
 | 
				
			||||||
@@ -85,5 +74,23 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            </com.google.android.material.chip.ChipGroup>
 | 
					            </com.google.android.material.chip.ChipGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <com.google.android.material.floatingactionbutton.FloatingActionButton
 | 
				
			||||||
 | 
					                android:id="@+id/floatingActionButton2"
 | 
				
			||||||
 | 
					                android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                android:layout_marginTop="8dp"
 | 
				
			||||||
 | 
					                android:layout_marginEnd="16dp"
 | 
				
			||||||
 | 
					                android:clickable="true"
 | 
				
			||||||
 | 
					                android:contentDescription="@string/menu_home_search"
 | 
				
			||||||
 | 
					                android:focusable="true"
 | 
				
			||||||
 | 
					                app:backgroundTint="@color/colorAccent"
 | 
				
			||||||
 | 
					                app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
 | 
					                app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
 | 
					                app:rippleColor="@color/colorAccentDark"
 | 
				
			||||||
 | 
					                app:srcCompat="@drawable/ic_menu_search_white_24dp" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
					        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
				
			||||||
 | 
					    </androidx.core.widget.NestedScrollView>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
					</androidx.constraintlayout.widget.ConstraintLayout>
 | 
				
			||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
 | 
					<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					 | 
				
			||||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
					    xmlns:tools="http://schemas.android.com/tools"
 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
@@ -22,10 +21,22 @@
 | 
				
			|||||||
                android:layout_width="0dp"
 | 
					                android:layout_width="0dp"
 | 
				
			||||||
                android:layout_height="200dp"
 | 
					                android:layout_height="200dp"
 | 
				
			||||||
                android:scaleType="centerCrop"
 | 
					                android:scaleType="centerCrop"
 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="parent"
 | 
					                app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					                app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
                app:layout_constraintTop_toTopOf="parent"
 | 
					                app:layout_constraintTop_toTopOf="parent" />
 | 
				
			||||||
                />
 | 
					
 | 
				
			||||||
 | 
					            <TextView
 | 
				
			||||||
 | 
					                android:id="@+id/titleView"
 | 
				
			||||||
 | 
					                android:layout_width="0dp"
 | 
				
			||||||
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                android:layout_marginLeft="16dp"
 | 
				
			||||||
 | 
					                android:layout_marginTop="8dp"
 | 
				
			||||||
 | 
					                android:layout_marginRight="16dp"
 | 
				
			||||||
 | 
					                android:textAppearance="@style/TextAppearance.AppCompat.Headline"
 | 
				
			||||||
 | 
					                android:textStyle="bold"
 | 
				
			||||||
 | 
					                app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
 | 
					                app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
 | 
					                app:layout_constraintTop_toBottomOf="@+id/imageView" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <TextView
 | 
					            <TextView
 | 
				
			||||||
                android:id="@+id/source"
 | 
					                android:id="@+id/source"
 | 
				
			||||||
@@ -36,40 +47,23 @@
 | 
				
			|||||||
                android:layout_marginRight="16dp"
 | 
					                android:layout_marginRight="16dp"
 | 
				
			||||||
                android:textColor="?android:textColorSecondary"
 | 
					                android:textColor="?android:textColorSecondary"
 | 
				
			||||||
                android:textSize="12sp"
 | 
					                android:textSize="12sp"
 | 
				
			||||||
                app:layout_constraintHorizontal_bias="0.0"
 | 
					 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="parent"
 | 
					                app:layout_constraintLeft_toLeftOf="parent"
 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					                app:layout_constraintRight_toRightOf="parent"
 | 
				
			||||||
                app:layout_constraintTop_toBottomOf="@+id/titleView" />
 | 
					                app:layout_constraintTop_toBottomOf="@+id/titleView" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <TextView
 | 
					 | 
				
			||||||
                android:id="@+id/titleView"
 | 
					 | 
				
			||||||
                android:layout_width="0dp"
 | 
					 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
                android:layout_marginLeft="16dp"
 | 
					 | 
				
			||||||
                android:layout_marginRight="16dp"
 | 
					 | 
				
			||||||
                android:layout_marginTop="8dp"
 | 
					 | 
				
			||||||
                android:textAppearance="@style/TextAppearance.AppCompat.Headline"
 | 
					 | 
				
			||||||
                android:textStyle="bold"
 | 
					 | 
				
			||||||
                app:layout_constraintHorizontal_bias="0.0"
 | 
					 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="parent"
 | 
					 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					 | 
				
			||||||
                app:layout_constraintTop_toBottomOf="@+id/imageView" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <WebView
 | 
					            <WebView
 | 
				
			||||||
                android:id="@+id/webcontent"
 | 
					                android:id="@+id/webcontent"
 | 
				
			||||||
                android:layout_width="0dp"
 | 
					                android:layout_width="0dp"
 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                android:layout_marginLeft="16dp"
 | 
				
			||||||
 | 
					                android:layout_marginTop="24dp"
 | 
				
			||||||
 | 
					                android:layout_marginRight="16dp"
 | 
				
			||||||
 | 
					                android:background="?attr/webviewBackground"
 | 
				
			||||||
 | 
					                android:paddingBottom="48dp"
 | 
				
			||||||
                android:textColorLink="?attr/colorAccent"
 | 
					                android:textColorLink="?attr/colorAccent"
 | 
				
			||||||
                android:visibility="gone"
 | 
					                android:visibility="gone"
 | 
				
			||||||
                android:layout_marginLeft="16dp"
 | 
					                app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
                android:layout_marginRight="16dp"
 | 
					                app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
                android:layout_marginTop="24dp"
 | 
					 | 
				
			||||||
                android:paddingBottom="48dp"
 | 
					 | 
				
			||||||
                android:background="?attr/webviewBackground"
 | 
					 | 
				
			||||||
                app:layout_constraintHorizontal_bias="0.0"
 | 
					 | 
				
			||||||
                app:layout_constraintLeft_toLeftOf="parent"
 | 
					 | 
				
			||||||
                app:layout_constraintRight_toRightOf="parent"
 | 
					 | 
				
			||||||
                app:layout_constraintTop_toBottomOf="@+id/source"
 | 
					                app:layout_constraintTop_toBottomOf="@+id/source"
 | 
				
			||||||
                tools:visibility="visible" />
 | 
					                tools:visibility="visible" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -80,10 +74,10 @@
 | 
				
			|||||||
    <FrameLayout
 | 
					    <FrameLayout
 | 
				
			||||||
        android:layout_width="match_parent"
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_gravity="start|bottom|end"
 | 
				
			||||||
        app:layout_constraintBottom_toBottomOf="parent"
 | 
					        app:layout_constraintBottom_toBottomOf="parent"
 | 
				
			||||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
					        app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
        app:layout_constraintLeft_toLeftOf="parent"
 | 
					        app:layout_constraintStart_toStartOf="parent">
 | 
				
			||||||
        android:layout_gravity="end|bottom|right">
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <com.github.rubensousa.floatingtoolbar.FloatingToolbar
 | 
					        <com.github.rubensousa.floatingtoolbar.FloatingToolbar
 | 
				
			||||||
            android:id="@+id/floatingToolbar"
 | 
					            android:id="@+id/floatingToolbar"
 | 
				
			||||||
@@ -96,12 +90,11 @@
 | 
				
			|||||||
            android:id="@+id/fab"
 | 
					            android:id="@+id/fab"
 | 
				
			||||||
            android:layout_width="wrap_content"
 | 
					            android:layout_width="wrap_content"
 | 
				
			||||||
            android:layout_height="wrap_content"
 | 
					            android:layout_height="wrap_content"
 | 
				
			||||||
            android:layout_gravity="end|bottom|right"
 | 
					            android:layout_gravity="end|bottom"
 | 
				
			||||||
            android:layout_marginBottom="16dp"
 | 
					 | 
				
			||||||
            android:layout_marginEnd="16dp"
 | 
					            android:layout_marginEnd="16dp"
 | 
				
			||||||
            android:layout_marginRight="16dp"
 | 
					            android:layout_marginBottom="16dp"
 | 
				
			||||||
            android:paddingBottom="@dimen/activity_vertical_margin"
 | 
					 | 
				
			||||||
            android:paddingTop="@dimen/activity_vertical_margin"
 | 
					            android:paddingTop="@dimen/activity_vertical_margin"
 | 
				
			||||||
 | 
					            android:paddingBottom="@dimen/activity_vertical_margin"
 | 
				
			||||||
            android:src="@drawable/ic_add_white_24dp"
 | 
					            android:src="@drawable/ic_add_white_24dp"
 | 
				
			||||||
            app:backgroundTint="?attr/colorAccent"
 | 
					            app:backgroundTint="?attr/colorAccent"
 | 
				
			||||||
            app:fabSize="mini"
 | 
					            app:fabSize="mini"
 | 
				
			||||||
@@ -112,11 +105,11 @@
 | 
				
			|||||||
        android:id="@+id/progressBar"
 | 
					        android:id="@+id/progressBar"
 | 
				
			||||||
        android:layout_width="match_parent"
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="match_parent"
 | 
					        android:layout_height="match_parent"
 | 
				
			||||||
        android:visibility="gone"
 | 
					 | 
				
			||||||
        android:animateLayoutChanges="true"
 | 
					 | 
				
			||||||
        android:alpha="0.8"
 | 
					        android:alpha="0.8"
 | 
				
			||||||
 | 
					        android:animateLayoutChanges="true"
 | 
				
			||||||
        android:background="@color/black"
 | 
					        android:background="@color/black"
 | 
				
			||||||
        android:clickable="false">
 | 
					        android:clickable="false"
 | 
				
			||||||
 | 
					        android:visibility="gone">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <ProgressBar
 | 
					        <ProgressBar
 | 
				
			||||||
            style="?android:attr/progressBarStyleLarge"
 | 
					            style="?android:attr/progressBarStyleLarge"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
    android:layout_height="match_parent">
 | 
					    android:layout_height="match_parent">
 | 
				
			||||||
@@ -13,4 +13,4 @@
 | 
				
			|||||||
            android:background="@drawable/checkerboard"
 | 
					            android:background="@drawable/checkerboard"
 | 
				
			||||||
            app:srcCompat="@android:drawable/screen_background_dark" />
 | 
					            app:srcCompat="@android:drawable/screen_background_dark" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</RelativeLayout>
 | 
					</androidx.constraintlayout.widget.ConstraintLayout>
 | 
				
			||||||
@@ -3,17 +3,16 @@
 | 
				
			|||||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
					    xmlns:tools="http://schemas.android.com/tools"
 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
    android:layout_height="88dp">
 | 
					    android:layout_height="wrap_content">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ImageView
 | 
					    <ImageView
 | 
				
			||||||
        android:id="@+id/itemImage"
 | 
					        android:id="@+id/itemImage"
 | 
				
			||||||
        android:layout_width="46dp"
 | 
					        android:layout_width="46dp"
 | 
				
			||||||
        android:layout_height="46dp"
 | 
					        android:layout_height="46dp"
 | 
				
			||||||
        android:layout_marginStart="8dp"
 | 
					        android:layout_marginStart="8dp"
 | 
				
			||||||
        android:layout_marginTop="21dp"
 | 
					        app:layout_constraintBottom_toBottomOf="parent"
 | 
				
			||||||
        app:layout_constraintStart_toStartOf="parent"
 | 
					        app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
        app:layout_constraintTop_toTopOf="parent"
 | 
					        app:layout_constraintTop_toTopOf="parent" />
 | 
				
			||||||
        android:layout_marginLeft="8dp" />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <TextView
 | 
					    <TextView
 | 
				
			||||||
        android:id="@+id/title"
 | 
					        android:id="@+id/title"
 | 
				
			||||||
@@ -24,39 +23,30 @@
 | 
				
			|||||||
        android:layout_marginEnd="16dp"
 | 
					        android:layout_marginEnd="16dp"
 | 
				
			||||||
        android:ellipsize="end"
 | 
					        android:ellipsize="end"
 | 
				
			||||||
        android:fontFamily="sans-serif"
 | 
					        android:fontFamily="sans-serif"
 | 
				
			||||||
        android:gravity="start"
 | 
					 | 
				
			||||||
        android:maxLines="3"
 | 
					        android:maxLines="3"
 | 
				
			||||||
        android:textAlignment="viewStart"
 | 
					        android:textAlignment="viewStart"
 | 
				
			||||||
        android:textAllCaps="false"
 | 
					        android:textColor="?android:textColorPrimary"
 | 
				
			||||||
        android:textSize="16sp"
 | 
					        android:textSize="16sp"
 | 
				
			||||||
        android:textStyle="bold"
 | 
					        android:textStyle="bold"
 | 
				
			||||||
        android:textColor="?android:textColorPrimary"
 | 
					 | 
				
			||||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
					        app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
        app:layout_constraintHorizontal_bias="0.0"
 | 
					 | 
				
			||||||
        app:layout_constraintStart_toEndOf="@+id/itemImage"
 | 
					        app:layout_constraintStart_toEndOf="@+id/itemImage"
 | 
				
			||||||
        app:layout_constraintTop_toTopOf="parent"
 | 
					        app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
        tools:text="Titre"
 | 
					        tools:text="Titre" />
 | 
				
			||||||
        android:layout_marginLeft="8dp"
 | 
					 | 
				
			||||||
        android:layout_marginRight="16dp" />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <TextView
 | 
					    <TextView
 | 
				
			||||||
        android:id="@+id/sourceTitleAndDate"
 | 
					        android:id="@+id/sourceTitleAndDate"
 | 
				
			||||||
        android:layout_width="0dp"
 | 
					        android:layout_width="0dp"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
        android:layout_marginStart="8dp"
 | 
					        android:layout_marginStart="8dp"
 | 
				
			||||||
        android:layout_marginTop="66dp"
 | 
					 | 
				
			||||||
        android:layout_marginEnd="16dp"
 | 
					        android:layout_marginEnd="16dp"
 | 
				
			||||||
        android:gravity="start"
 | 
					        android:gravity="start"
 | 
				
			||||||
        android:maxLines="1"
 | 
					        android:maxLines="1"
 | 
				
			||||||
        android:textAlignment="viewStart"
 | 
					        android:textAlignment="viewStart"
 | 
				
			||||||
        android:textSize="14sp"
 | 
					 | 
				
			||||||
        android:textColor="?android:textColorPrimary"
 | 
					        android:textColor="?android:textColorPrimary"
 | 
				
			||||||
 | 
					        android:textSize="14sp"
 | 
				
			||||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
					        app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
        app:layout_constraintHorizontal_bias="0.0"
 | 
					 | 
				
			||||||
        app:layout_constraintStart_toEndOf="@+id/itemImage"
 | 
					        app:layout_constraintStart_toEndOf="@+id/itemImage"
 | 
				
			||||||
        app:layout_constraintTop_toTopOf="parent"
 | 
					        app:layout_constraintTop_toBottomOf="@+id/itemImage"
 | 
				
			||||||
        tools:text="Google Actualité Il y a 5h"
 | 
					        tools:text="Google Actualité Il y a 5h" />
 | 
				
			||||||
        android:layout_marginLeft="8dp"
 | 
					 | 
				
			||||||
        android:layout_marginRight="16dp" />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
					</androidx.constraintlayout.widget.ConstraintLayout>
 | 
				
			||||||
@@ -3,48 +3,74 @@
 | 
				
			|||||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
					    xmlns:tools="http://schemas.android.com/tools"
 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
    android:layout_height="48dp"
 | 
					    android:layout_height="wrap_content"
 | 
				
			||||||
    android:orientation="vertical">
 | 
					    android:orientation="vertical">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <Button
 | 
				
			||||||
 | 
					        android:id="@+id/deleteBtn"
 | 
				
			||||||
 | 
					        style="@style/Widget.AppCompat.Button.Borderless"
 | 
				
			||||||
 | 
					        android:layout_width="48dp"
 | 
				
			||||||
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_marginTop="8dp"
 | 
				
			||||||
 | 
					        android:layout_marginEnd="8dp"
 | 
				
			||||||
 | 
					        android:layout_marginBottom="8dp"
 | 
				
			||||||
 | 
					        android:background="@drawable/ic_remove_circle_outline_black_24dp"
 | 
				
			||||||
 | 
					        android:backgroundTint="?android:textColorSecondary"
 | 
				
			||||||
 | 
					        android:contentDescription="@string/remove_source"
 | 
				
			||||||
 | 
					        android:elevation="4dp"
 | 
				
			||||||
 | 
					        app:iconSize="34dp"
 | 
				
			||||||
 | 
					        app:layout_constraintBottom_toBottomOf="parent"
 | 
				
			||||||
 | 
					        app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
 | 
					        app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
 | 
					        app:layout_constraintVertical_bias="0.0" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ImageView
 | 
					    <ImageView
 | 
				
			||||||
        android:id="@+id/itemImage"
 | 
					        android:id="@+id/itemImage"
 | 
				
			||||||
        android:layout_width="36dp"
 | 
					        android:layout_width="36dp"
 | 
				
			||||||
        android:layout_height="36dp"
 | 
					        android:layout_height="36dp"
 | 
				
			||||||
 | 
					        android:layout_marginStart="8dp"
 | 
				
			||||||
 | 
					        android:layout_marginTop="8dp"
 | 
				
			||||||
 | 
					        android:layout_marginBottom="8dp"
 | 
				
			||||||
        android:importantForAccessibility="no"
 | 
					        android:importantForAccessibility="no"
 | 
				
			||||||
        app:layout_constraintBottom_toBottomOf="parent"
 | 
					        app:layout_constraintBottom_toBottomOf="parent"
 | 
				
			||||||
        app:layout_constraintLeft_toLeftOf="parent"
 | 
					        app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
        app:layout_constraintTop_toTopOf="parent" />
 | 
					        app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
 | 
					        app:layout_constraintVertical_bias="0.0" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <TextView
 | 
					    <TextView
 | 
				
			||||||
        android:id="@+id/sourceTitle"
 | 
					        android:id="@+id/sourceTitle"
 | 
				
			||||||
        android:layout_width="0dp"
 | 
					        android:layout_width="0dp"
 | 
				
			||||||
        android:layout_height="17dp"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
        android:layout_marginStart="8dp"
 | 
					        android:layout_marginStart="8dp"
 | 
				
			||||||
        android:ellipsize="end"
 | 
					        android:ellipsize="end"
 | 
				
			||||||
        android:gravity="start"
 | 
					 | 
				
			||||||
        android:maxLines="1"
 | 
					        android:maxLines="1"
 | 
				
			||||||
        android:textAlignment="textStart"
 | 
					        android:textAlignment="viewStart"
 | 
				
			||||||
        android:textSize="13sp"
 | 
					 | 
				
			||||||
        android:textColor="?android:textColorPrimary"
 | 
					        android:textColor="?android:textColorPrimary"
 | 
				
			||||||
        app:layout_constraintBottom_toBottomOf="parent"
 | 
					        android:textSize="20sp"
 | 
				
			||||||
 | 
					        android:textStyle="bold"
 | 
				
			||||||
 | 
					        app:layout_constraintBottom_toTopOf="@+id/errorText"
 | 
				
			||||||
        app:layout_constraintEnd_toStartOf="@+id/deleteBtn"
 | 
					        app:layout_constraintEnd_toStartOf="@+id/deleteBtn"
 | 
				
			||||||
        app:layout_constraintStart_toEndOf="@+id/itemImage"
 | 
					        app:layout_constraintStart_toEndOf="@+id/itemImage"
 | 
				
			||||||
        app:layout_constraintTop_toTopOf="parent"
 | 
					        app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
        tools:text="source title" />
 | 
					        tools:text="Source title" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Button
 | 
					    <TextView
 | 
				
			||||||
        android:id="@+id/deleteBtn"
 | 
					        android:id="@+id/errorText"
 | 
				
			||||||
        style="@style/Widget.AppCompat.Button.Borderless"
 | 
					        android:layout_width="0dp"
 | 
				
			||||||
        android:layout_width="34dp"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
        android:layout_height="34dp"
 | 
					        android:layout_marginStart="10sp"
 | 
				
			||||||
        android:layout_marginEnd="8dp"
 | 
					        android:layout_marginTop="8dp"
 | 
				
			||||||
        android:background="@drawable/ic_remove_circle_outline_black_24dp"
 | 
					        android:layout_marginBottom="8dp"
 | 
				
			||||||
        android:backgroundTint="?android:textColorSecondary"
 | 
					        android:textAppearance="@style/TextAppearance.AppCompat.Small"
 | 
				
			||||||
        android:elevation="4dp"
 | 
					        android:textColor="@color/red"
 | 
				
			||||||
        android:contentDescription="@string/remove_source"
 | 
					        android:textStyle="italic"
 | 
				
			||||||
 | 
					        android:visibility="gone"
 | 
				
			||||||
        app:layout_constraintBottom_toBottomOf="parent"
 | 
					        app:layout_constraintBottom_toBottomOf="parent"
 | 
				
			||||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
					        app:layout_constraintEnd_toStartOf="@+id/deleteBtn"
 | 
				
			||||||
        app:layout_constraintTop_toTopOf="parent" />
 | 
					        app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
 | 
					        app:layout_constraintTop_toBottomOf="@+id/itemImage"
 | 
				
			||||||
 | 
					        tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
 | 
				
			||||||
 | 
					        tools:visibility="visible" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
					</androidx.constraintlayout.widget.ConstraintLayout>
 | 
				
			||||||
@@ -20,6 +20,11 @@
 | 
				
			|||||||
          android:orderInCategory="2"
 | 
					          android:orderInCategory="2"
 | 
				
			||||||
          app:showAsAction="ifRoom"/>
 | 
					          app:showAsAction="ifRoom"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <item android:id="@+id/action_sources"
 | 
				
			||||||
 | 
					        android:title="@string/menu_home_sources"
 | 
				
			||||||
 | 
					        android:orderInCategory="97"
 | 
				
			||||||
 | 
					        app:showAsAction="never"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <item android:id="@+id/action_settings"
 | 
					    <item android:id="@+id/action_settings"
 | 
				
			||||||
        android:title="@string/title_activity_settings"
 | 
					        android:title="@string/title_activity_settings"
 | 
				
			||||||
        android:orderInCategory="98"
 | 
					        android:orderInCategory="98"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								androidApp/src/main/res/menu/reader_toolbar_no_read.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								androidApp/src/main/res/menu/reader_toolbar_no_read.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					<menu xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <item
 | 
				
			||||||
 | 
					        android:id="@+id/open_action"
 | 
				
			||||||
 | 
					        android:icon="@drawable/ic_open_in_browser_white_24dp"
 | 
				
			||||||
 | 
					        android:title="@string/reader_action_open"
 | 
				
			||||||
 | 
					        app:showAsAction="ifRoom" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <item
 | 
				
			||||||
 | 
					        android:id="@+id/share_action"
 | 
				
			||||||
 | 
					        android:icon="@drawable/ic_share_white_24dp"
 | 
				
			||||||
 | 
					        android:title="@string/reader_action_share"
 | 
				
			||||||
 | 
					        app:showAsAction="ifRoom" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</menu>
 | 
				
			||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,4 +6,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"禁用自动错误报告 "</string>
 | 
					    <string name="pref_switch_disable_acra">"禁用自动错误报告 "</string>
 | 
				
			||||||
    <string name="menu_home_filter">筛选器</string>
 | 
					    <string name="menu_home_filter">筛选器</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,4 +126,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,4 +12,5 @@
 | 
				
			|||||||
    <color name="refresh_progress_2">@color/colorAccent</color>
 | 
					    <color name="refresh_progress_2">@color/colorAccent</color>
 | 
				
			||||||
    <color name="refresh_progress_3">@color/pink</color>
 | 
					    <color name="refresh_progress_3">@color/pink</color>
 | 
				
			||||||
    <color name="dark">#FF282828</color>
 | 
					    <color name="dark">#FF282828</color>
 | 
				
			||||||
 | 
					    <color name="transparent_dark_background">#33000000</color>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -129,4 +129,6 @@
 | 
				
			|||||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
					    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
				
			||||||
    <string name="menu_home_filter">Filters</string>
 | 
					    <string name="menu_home_filter">Filters</string>
 | 
				
			||||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
					    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
				
			||||||
 | 
					    <string name="menu_home_sources">Sources</string>
 | 
				
			||||||
 | 
					    <string name="update_source">Update source</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,4 +26,10 @@
 | 
				
			|||||||
        <item name="android:textColorSecondary">@color/white</item>
 | 
					        <item name="android:textColorSecondary">@color/white</item>
 | 
				
			||||||
        <item name="actionMenuTextColor">@color/white</item>
 | 
					        <item name="actionMenuTextColor">@color/white</item>
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <style name="Theme.AppCompat.ImageActivity" parent="NoBar">
 | 
				
			||||||
 | 
					        <item name="android:windowBackground">@color/transparent_dark_background</item>
 | 
				
			||||||
 | 
					        <item name="android:colorBackgroundCacheHint">@null</item>
 | 
				
			||||||
 | 
					        <item name="android:windowIsTranslucent">true</item>
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										41
									
								
								androidApp/src/main/res/xml/image_close_scene.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								androidApp/src/main/res/xml/image_close_scene.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
 | 
					    xmlns:motion="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
 | 
					    android:id="@+id/motionLayout">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <Transition
 | 
				
			||||||
 | 
					        motion:constraintSetStart="@+id/start"
 | 
				
			||||||
 | 
					        motion:constraintSetEnd="@+id/end"
 | 
				
			||||||
 | 
					        motion:duration="500"
 | 
				
			||||||
 | 
					        motion:motionInterpolator="linear">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <OnSwipe
 | 
				
			||||||
 | 
					            motion:touchAnchorId="@+id/scrollView"
 | 
				
			||||||
 | 
					            motion:touchAnchorSide="top"
 | 
				
			||||||
 | 
					            motion:onTouchUp="autoCompleteToStart" />
 | 
				
			||||||
 | 
					    </Transition>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ConstraintSet android:id="@+id/start">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Constraint
 | 
				
			||||||
 | 
					            android:id="@id/scrollView"
 | 
				
			||||||
 | 
					            motion:layout_constraintBottom_toBottomOf="parent"
 | 
				
			||||||
 | 
					            motion:layout_constraintTop_toBottomOf="@+id/appBarLayout"
 | 
				
			||||||
 | 
					            motion:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
 | 
					            motion:layout_constraintStart_toStartOf="parent">
 | 
				
			||||||
 | 
					        </Constraint>
 | 
				
			||||||
 | 
					    </ConstraintSet>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ConstraintSet android:id="@+id/end">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Constraint
 | 
				
			||||||
 | 
					            android:id="@+id/scrollView"
 | 
				
			||||||
 | 
					            android:layout_width="100dp"
 | 
				
			||||||
 | 
					            android:layout_height="100dp"
 | 
				
			||||||
 | 
					            android:layout_marginBottom="0dp"
 | 
				
			||||||
 | 
					            motion:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
 | 
					            motion:layout_constraintBottom_toBottomOf="parent"
 | 
				
			||||||
 | 
					            motion:layout_constraintEnd_toEndOf="parent">
 | 
				
			||||||
 | 
					        </Constraint>
 | 
				
			||||||
 | 
					    </ConstraintSet>
 | 
				
			||||||
 | 
					</MotionScene>
 | 
				
			||||||
@@ -18,6 +18,20 @@ import org.junit.Assert.assertNotEquals
 | 
				
			|||||||
import org.junit.Before
 | 
					import org.junit.Before
 | 
				
			||||||
import org.junit.Test
 | 
					import org.junit.Test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private const val BASE_URL = "https://test.com/selfoss/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private const val USERNAME = "username"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private const val SPOUT = "spouts\\rss\\fulltextrss"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private const val IMAGE_URL = "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private const val IMAGE_URL_2 = "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private const val FEED_URL = "https://test.com/feed"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private const val TAGS = "Test, New"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RepositoryTest {
 | 
					class RepositoryTest {
 | 
				
			||||||
    private val db = mockk<ReaderForSelfossDB>(relaxed = true)
 | 
					    private val db = mockk<ReaderForSelfossDB>(relaxed = true)
 | 
				
			||||||
    private val appSettingsService = mockk<AppSettingsService>()
 | 
					    private val appSettingsService = mockk<AppSettingsService>()
 | 
				
			||||||
@@ -37,7 +51,7 @@ class RepositoryTest {
 | 
				
			|||||||
        repository = Repository(api, appSettingsService, isConnectionAvailable, db)
 | 
					        repository = Repository(api, appSettingsService, isConnectionAvailable, db)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            repository.updateApiVersion()
 | 
					            repository.updateApiInformation()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,13 +59,14 @@ class RepositoryTest {
 | 
				
			|||||||
    fun setup() {
 | 
					    fun setup() {
 | 
				
			||||||
        clearAllMocks()
 | 
					        clearAllMocks()
 | 
				
			||||||
        every { appSettingsService.getApiVersion() } returns 4
 | 
					        every { appSettingsService.getApiVersion() } returns 4
 | 
				
			||||||
        every { appSettingsService.getBaseUrl() } returns "https://test.com/selfoss/"
 | 
					        every { appSettingsService.getBaseUrl() } returns BASE_URL
 | 
				
			||||||
 | 
					        every { appSettingsService.getUserName() } returns USERNAME
 | 
				
			||||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
					        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
				
			||||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
					        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        coEvery { api.version() } returns StatusAndData(
 | 
					        coEvery { api.apiInformation() } returns StatusAndData(
 | 
				
			||||||
            success = true,
 | 
					            success = true,
 | 
				
			||||||
            data = SelfossModel.ApiVersion("2.19-ba1e8e3", "4.0.0")
 | 
					            data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(false, true))
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        coEvery { api.stats() } returns StatusAndData(
 | 
					        coEvery { api.stats() } returns StatusAndData(
 | 
				
			||||||
            success = true,
 | 
					            success = true,
 | 
				
			||||||
@@ -69,7 +84,7 @@ class RepositoryTest {
 | 
				
			|||||||
    fun instantiate_repository() {
 | 
					    fun instantiate_repository() {
 | 
				
			||||||
        initializeRepository()
 | 
					        initializeRepository()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        coVerify(exactly = 1) { api.version() }
 | 
					        coVerify(exactly = 1) { api.apiInformation() }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
@@ -78,7 +93,7 @@ class RepositoryTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        initializeRepository(MutableStateFlow(false))
 | 
					        initializeRepository(MutableStateFlow(false))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        coVerify(exactly = 0) { api.version() }
 | 
					        coVerify(exactly = 0) { api.apiInformation() }
 | 
				
			||||||
        coVerify(exactly = 0) { api.stats() }
 | 
					        coVerify(exactly = 0) { api.stats() }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -98,10 +113,70 @@ class RepositoryTest {
 | 
				
			|||||||
        verify(exactly = 1) { appSettingsService.updateApiVersion(4) }
 | 
					        verify(exactly = 1) { appSettingsService.updateApiVersion(4) }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun get_public_access() {
 | 
				
			||||||
 | 
					        every { appSettingsService.updatePublicAccess(any()) } returns Unit
 | 
				
			||||||
 | 
					        coEvery { api.apiInformation() } returns StatusAndData(
 | 
				
			||||||
 | 
					            success = true,
 | 
				
			||||||
 | 
					            data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, true))
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        every { appSettingsService.getUserName() } returns ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        initializeRepository()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        coVerify(exactly = 1) { api.apiInformation() }
 | 
				
			||||||
 | 
					        coVerify(exactly = 1) { appSettingsService.updatePublicAccess(true) }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun get_public_access_username_not_empty() {
 | 
				
			||||||
 | 
					        every { appSettingsService.updatePublicAccess(any()) } returns Unit
 | 
				
			||||||
 | 
					        coEvery { api.apiInformation() } returns StatusAndData(
 | 
				
			||||||
 | 
					            success = true,
 | 
				
			||||||
 | 
					            data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, true))
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        every { appSettingsService.getUserName() } returns "username"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        initializeRepository()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        coVerify(exactly = 1) { api.apiInformation() }
 | 
				
			||||||
 | 
					        coVerify(exactly = 0) { appSettingsService.updatePublicAccess(true) }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun get_public_access_no_auth() {
 | 
				
			||||||
 | 
					        every { appSettingsService.updatePublicAccess(any()) } returns Unit
 | 
				
			||||||
 | 
					        coEvery { api.apiInformation() } returns StatusAndData(
 | 
				
			||||||
 | 
					            success = true,
 | 
				
			||||||
 | 
					            data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, false))
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        every { appSettingsService.getUserName() } returns ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        initializeRepository()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        coVerify(exactly = 1) { api.apiInformation() }
 | 
				
			||||||
 | 
					        coVerify(exactly = 0) { appSettingsService.updatePublicAccess(true) }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun get_public_access_disabled() {
 | 
				
			||||||
 | 
					        every { appSettingsService.updatePublicAccess(any()) } returns Unit
 | 
				
			||||||
 | 
					        coEvery { api.apiInformation() } returns StatusAndData(
 | 
				
			||||||
 | 
					            success = true,
 | 
				
			||||||
 | 
					            data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(false, true))
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        every { appSettingsService.getUserName() } returns ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        initializeRepository()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        coVerify(exactly = 1) { api.apiInformation() }
 | 
				
			||||||
 | 
					        coVerify(exactly = 0) { appSettingsService.updatePublicAccess(true) }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    fun get_api_1_date_with_api_4_version_stored() {
 | 
					    fun get_api_1_date_with_api_4_version_stored() {
 | 
				
			||||||
        every { appSettingsService.getApiVersion() } returns 4
 | 
					        every { appSettingsService.getApiVersion() } returns 4
 | 
				
			||||||
        coEvery { api.version() } returns StatusAndData(success = false, null)
 | 
					        coEvery { api.apiInformation() } returns StatusAndData(success = false, null)
 | 
				
			||||||
        val itemParameters = FakeItemParameters()
 | 
					        val itemParameters = FakeItemParameters()
 | 
				
			||||||
        itemParameters.datetime = "2021-04-23 11:45:32"
 | 
					        itemParameters.datetime = "2021-04-23 11:45:32"
 | 
				
			||||||
        coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
 | 
					        coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
 | 
				
			||||||
@@ -229,9 +304,10 @@ class RepositoryTest {
 | 
				
			|||||||
            1,
 | 
					            1,
 | 
				
			||||||
            "Test",
 | 
					            "Test",
 | 
				
			||||||
            listOf("tags"),
 | 
					            listOf("tags"),
 | 
				
			||||||
            "spouts\\rss\\fulltextrss",
 | 
					            SPOUT,
 | 
				
			||||||
            "",
 | 
					            "",
 | 
				
			||||||
            "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
					            IMAGE_URL,
 | 
				
			||||||
 | 
					            SelfossModel.SourceParams("url")
 | 
				
			||||||
        ))
 | 
					        ))
 | 
				
			||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            repository.getNewerItems()
 | 
					            repository.getNewerItems()
 | 
				
			||||||
@@ -549,17 +625,19 @@ class RepositoryTest {
 | 
				
			|||||||
                1,
 | 
					                1,
 | 
				
			||||||
                "First source",
 | 
					                "First source",
 | 
				
			||||||
                listOf("Test", "second"),
 | 
					                listOf("Test", "second"),
 | 
				
			||||||
                "spouts\\rss\\fulltextrss",
 | 
					                SPOUT,
 | 
				
			||||||
                "",
 | 
					                "",
 | 
				
			||||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
					                IMAGE_URL_2,
 | 
				
			||||||
 | 
					                SelfossModel.SourceParams("url")
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            SelfossModel.Source(
 | 
					            SelfossModel.Source(
 | 
				
			||||||
                2,
 | 
					                2,
 | 
				
			||||||
                "Second source",
 | 
					                "Second source",
 | 
				
			||||||
                listOf("second"),
 | 
					                listOf("second"),
 | 
				
			||||||
                "spouts\\rss\\fulltextrss",
 | 
					                SPOUT,
 | 
				
			||||||
                "",
 | 
					                "",
 | 
				
			||||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
					                IMAGE_URL,
 | 
				
			||||||
 | 
					                SelfossModel.SourceParams("url")
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        val sourcesDB = listOf(
 | 
					        val sourcesDB = listOf(
 | 
				
			||||||
@@ -567,17 +645,19 @@ class RepositoryTest {
 | 
				
			|||||||
                "1",
 | 
					                "1",
 | 
				
			||||||
                "First DB source",
 | 
					                "First DB source",
 | 
				
			||||||
                "Test,second",
 | 
					                "Test,second",
 | 
				
			||||||
                "spouts\\rss\\fulltextrss",
 | 
					                SPOUT,
 | 
				
			||||||
                "",
 | 
					                "",
 | 
				
			||||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
					                IMAGE_URL_2,
 | 
				
			||||||
 | 
					                "url"
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            SOURCE(
 | 
					            SOURCE(
 | 
				
			||||||
                "2",
 | 
					                "2",
 | 
				
			||||||
                "Second source",
 | 
					                "Second source",
 | 
				
			||||||
                "second",
 | 
					                "second",
 | 
				
			||||||
                "spouts\\rss\\fulltextrss",
 | 
					                SPOUT,
 | 
				
			||||||
                "",
 | 
					                "",
 | 
				
			||||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
					                IMAGE_URL,
 | 
				
			||||||
 | 
					                "url"
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -707,7 +787,7 @@ class RepositoryTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    fun create_source() {
 | 
					    fun create_source() {
 | 
				
			||||||
        coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
 | 
					        coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
 | 
				
			||||||
                SuccessResponse(true)
 | 
					                SuccessResponse(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        initializeRepository()
 | 
					        initializeRepository()
 | 
				
			||||||
@@ -715,10 +795,9 @@ class RepositoryTest {
 | 
				
			|||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            response = repository.createSource(
 | 
					            response = repository.createSource(
 | 
				
			||||||
                "test",
 | 
					                "test",
 | 
				
			||||||
                "https://test.com/feed",
 | 
					                FEED_URL,
 | 
				
			||||||
                "spouts\\rss\\fulltextrss",
 | 
					                SPOUT,
 | 
				
			||||||
                "Test, New",
 | 
					                TAGS,
 | 
				
			||||||
                ""
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -728,7 +807,6 @@ class RepositoryTest {
 | 
				
			|||||||
                any(),
 | 
					                any(),
 | 
				
			||||||
                any(),
 | 
					                any(),
 | 
				
			||||||
                any(),
 | 
					                any(),
 | 
				
			||||||
                any(),
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        assertSame(true, response)
 | 
					        assertSame(true, response)
 | 
				
			||||||
@@ -736,7 +814,7 @@ class RepositoryTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    fun create_source_but_response_fails() {
 | 
					    fun create_source_but_response_fails() {
 | 
				
			||||||
        coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
 | 
					        coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
 | 
				
			||||||
                SuccessResponse(false)
 | 
					                SuccessResponse(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        initializeRepository()
 | 
					        initializeRepository()
 | 
				
			||||||
@@ -744,10 +822,9 @@ class RepositoryTest {
 | 
				
			|||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            response = repository.createSource(
 | 
					            response = repository.createSource(
 | 
				
			||||||
                "test",
 | 
					                "test",
 | 
				
			||||||
                "https://test.com/feed",
 | 
					                FEED_URL,
 | 
				
			||||||
                "spouts\\rss\\fulltextrss",
 | 
					                SPOUT,
 | 
				
			||||||
                "Test, New",
 | 
					                TAGS
 | 
				
			||||||
                ""
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -756,8 +833,7 @@ class RepositoryTest {
 | 
				
			|||||||
                any(),
 | 
					                any(),
 | 
				
			||||||
                any(),
 | 
					                any(),
 | 
				
			||||||
                any(),
 | 
					                any(),
 | 
				
			||||||
                any(),
 | 
					                any()
 | 
				
			||||||
                any(),
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        assertSame(false, response)
 | 
					        assertSame(false, response)
 | 
				
			||||||
@@ -765,7 +841,7 @@ class RepositoryTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    fun create_source_without_connection() {
 | 
					    fun create_source_without_connection() {
 | 
				
			||||||
        coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
 | 
					        coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
 | 
				
			||||||
                SuccessResponse(true)
 | 
					                SuccessResponse(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        initializeRepository(MutableStateFlow(false))
 | 
					        initializeRepository(MutableStateFlow(false))
 | 
				
			||||||
@@ -773,10 +849,9 @@ class RepositoryTest {
 | 
				
			|||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            response = repository.createSource(
 | 
					            response = repository.createSource(
 | 
				
			||||||
                "test",
 | 
					                "test",
 | 
				
			||||||
                "https://test.com/feed",
 | 
					                FEED_URL,
 | 
				
			||||||
                "spouts\\rss\\fulltextrss",
 | 
					                SPOUT,
 | 
				
			||||||
                "Test, New",
 | 
					                TAGS
 | 
				
			||||||
                ""
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -786,7 +861,6 @@ class RepositoryTest {
 | 
				
			|||||||
                any(),
 | 
					                any(),
 | 
				
			||||||
                any(),
 | 
					                any(),
 | 
				
			||||||
                any(),
 | 
					                any(),
 | 
				
			||||||
                any()
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        assertSame(false, response)
 | 
					        assertSame(false, response)
 | 
				
			||||||
@@ -953,12 +1027,12 @@ class RepositoryTest {
 | 
				
			|||||||
        coEvery { appSettingsService.refreshLoginInformation(any(), any(), any()) } returns Unit
 | 
					        coEvery { appSettingsService.refreshLoginInformation(any(), any(), any()) } returns Unit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        initializeRepository()
 | 
					        initializeRepository()
 | 
				
			||||||
        repository.refreshLoginInformation("https://test.com/selfoss/", "login", "password")
 | 
					        repository.refreshLoginInformation(BASE_URL, "login", "password")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        coVerify(exactly = 1) { api.refreshLoginInformation() }
 | 
					        coVerify(exactly = 1) { api.refreshLoginInformation() }
 | 
				
			||||||
        coVerify(exactly = 1) {
 | 
					        coVerify(exactly = 1) {
 | 
				
			||||||
            appSettingsService.refreshLoginInformation(
 | 
					            appSettingsService.refreshLoginInformation(
 | 
				
			||||||
                "https://test.com/selfoss/",
 | 
					                BASE_URL,
 | 
				
			||||||
                "login",
 | 
					                "login",
 | 
				
			||||||
                "password"
 | 
					                "password"
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@@ -1032,9 +1106,10 @@ class RepositoryTest {
 | 
				
			|||||||
                1,
 | 
					                1,
 | 
				
			||||||
                "First source",
 | 
					                "First source",
 | 
				
			||||||
                listOf("Test", "second"),
 | 
					                listOf("Test", "second"),
 | 
				
			||||||
                "spouts\\rss\\fulltextrss",
 | 
					                SPOUT,
 | 
				
			||||||
                "",
 | 
					                "",
 | 
				
			||||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
					                IMAGE_URL_2,
 | 
				
			||||||
 | 
					                SelfossModel.SourceParams("url")
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        repository.searchFilter = "search"
 | 
					        repository.searchFilter = "search"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,8 @@ buildscript {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
plugins {
 | 
					plugins {
 | 
				
			||||||
    //trick: for the same plugin versions in all sub-modules
 | 
					    //trick: for the same plugin versions in all sub-modules
 | 
				
			||||||
    id("com.android.application").version("7.3.1").apply(false)
 | 
					    id("com.android.application").version("7.4.0").apply(false)
 | 
				
			||||||
    id("com.android.library").version("7.3.1").apply(false)
 | 
					    id("com.android.library").version("7.4.0").apply(false)
 | 
				
			||||||
    kotlin("android").version("1.7.20").apply(false)
 | 
					    kotlin("android").version("1.7.20").apply(false)
 | 
				
			||||||
    kotlin("multiplatform").version("1.7.20").apply(false)
 | 
					    kotlin("multiplatform").version("1.7.20").apply(false)
 | 
				
			||||||
    id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
 | 
					    id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
 | 
				
			||||||
@@ -17,12 +17,11 @@ plugins {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
allprojects {
 | 
					allprojects {
 | 
				
			||||||
    repositories {
 | 
					    repositories {
 | 
				
			||||||
        maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
 | 
					        // maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
 | 
				
			||||||
        // IMPORTANT : Add back when new library added
 | 
					        google()
 | 
				
			||||||
        // google()
 | 
					        mavenCentral()
 | 
				
			||||||
        // mavenCentral()
 | 
					        jcenter()
 | 
				
			||||||
        // jcenter()
 | 
					        maven { url = uri("https://www.jitpack.io") }
 | 
				
			||||||
        // maven { url = uri("https://www.jitpack.io") }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
#Wed Feb 09 17:05:19 CET 2022
 | 
					#Mon Jan 23 20:47:46 CET 2023
 | 
				
			||||||
distributionBase=GRADLE_USER_HOME
 | 
					distributionBase=GRADLE_USER_HOME
 | 
				
			||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
 | 
					distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
 | 
				
			||||||
distributionPath=wrapper/dists
 | 
					distributionPath=wrapper/dists
 | 
				
			||||||
zipStorePath=wrapper/dists
 | 
					zipStorePath=wrapper/dists
 | 
				
			||||||
zipStoreBase=GRADLE_USER_HOME
 | 
					zipStoreBase=GRADLE_USER_HOME
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,20 +2,18 @@ val pushCache: String by settings
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pluginManagement {
 | 
					pluginManagement {
 | 
				
			||||||
    repositories {
 | 
					    repositories {
 | 
				
			||||||
        maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
 | 
					        // maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
 | 
				
			||||||
        // IMPORTANT : Add back when new plugin added
 | 
					        google()
 | 
				
			||||||
        // google()
 | 
					        gradlePluginPortal()
 | 
				
			||||||
        // gradlePluginPortal()
 | 
					        mavenCentral()
 | 
				
			||||||
        // mavenCentral()
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencyResolutionManagement {
 | 
					dependencyResolutionManagement {
 | 
				
			||||||
    repositories {
 | 
					    repositories {
 | 
				
			||||||
        maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
 | 
					        // maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
 | 
				
			||||||
        // IMPORTANT : Add back when new library added
 | 
					        google()
 | 
				
			||||||
        // google()
 | 
					        mavenCentral()
 | 
				
			||||||
        // mavenCentral()
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,6 @@ import org.kodein.di.instance
 | 
				
			|||||||
import org.kodein.di.singleton
 | 
					import org.kodein.di.singleton
 | 
				
			||||||
 | 
					
 | 
				
			||||||
val networkModule by DI.Module {
 | 
					val networkModule by DI.Module {
 | 
				
			||||||
    bind<AppSettingsService>() with singleton { AppSettingsService() }
 | 
					 | 
				
			||||||
    bind<SelfossApi>() with singleton { SelfossApi(instance()) }
 | 
					    bind<SelfossApi>() with singleton { SelfossApi(instance()) }
 | 
				
			||||||
    bind<MercuryApi>() with singleton { MercuryApi() }
 | 
					    bind<MercuryApi>() with singleton { MercuryApi() }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -6,9 +6,9 @@ class MercuryModel {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Serializable
 | 
					    @Serializable
 | 
				
			||||||
    class ParsedContent(
 | 
					    class ParsedContent(
 | 
				
			||||||
        val title: String,
 | 
					        val title: String?,
 | 
				
			||||||
        val content: String?,
 | 
					        val content: String?,
 | 
				
			||||||
        val lead_image_url: String?,
 | 
					        val lead_image_url: String?, // NOSONAR
 | 
				
			||||||
        val url: String
 | 
					        val url: String
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
 | 
				
			|||||||
import kotlinx.serialization.descriptors.SerialDescriptor
 | 
					import kotlinx.serialization.descriptors.SerialDescriptor
 | 
				
			||||||
import kotlinx.serialization.encoding.Decoder
 | 
					import kotlinx.serialization.encoding.Decoder
 | 
				
			||||||
import kotlinx.serialization.encoding.Encoder
 | 
					import kotlinx.serialization.encoding.Encoder
 | 
				
			||||||
 | 
					import kotlinx.serialization.encoding.encodeCollection
 | 
				
			||||||
import kotlinx.serialization.json.*
 | 
					import kotlinx.serialization.json.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SelfossModel {
 | 
					class SelfossModel {
 | 
				
			||||||
@@ -23,8 +24,8 @@ class SelfossModel {
 | 
				
			|||||||
    @Serializable
 | 
					    @Serializable
 | 
				
			||||||
    class Stats(
 | 
					    class Stats(
 | 
				
			||||||
        val total: Int,
 | 
					        val total: Int,
 | 
				
			||||||
        val unread: Int,
 | 
					        val unread: Int?,
 | 
				
			||||||
        val starred: Int
 | 
					        val starred: Int?
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Serializable
 | 
					    @Serializable
 | 
				
			||||||
@@ -34,9 +35,10 @@ class SelfossModel {
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Serializable
 | 
					    @Serializable
 | 
				
			||||||
    data class ApiVersion(
 | 
					    data class ApiInformation(
 | 
				
			||||||
        val version: String?,
 | 
					        val version: String?,
 | 
				
			||||||
        val apiversion: String?
 | 
					        val apiversion: String?,
 | 
				
			||||||
 | 
					        val configuration: ApiConfiguration?
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        fun getApiMajorVersion(): Int {
 | 
					        fun getApiMajorVersion(): Int {
 | 
				
			||||||
            var versionNumber = 0
 | 
					            var versionNumber = 0
 | 
				
			||||||
@@ -45,6 +47,20 @@ class SelfossModel {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            return versionNumber
 | 
					            return versionNumber
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fun getApiConfiguration() = configuration ?: ApiConfiguration(null, null)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Serializable
 | 
				
			||||||
 | 
					    data class ApiConfiguration(
 | 
				
			||||||
 | 
					        @Serializable(with = BooleanSerializer::class)
 | 
				
			||||||
 | 
					        val publicMode: Boolean?,
 | 
				
			||||||
 | 
					        @Serializable(with = BooleanSerializer::class)
 | 
				
			||||||
 | 
					        val authEnabled: Boolean?
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        fun isAuthEnabled() = authEnabled ?: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fun isPublicModeEnabled() = publicMode ?: false
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Serializable
 | 
					    @Serializable
 | 
				
			||||||
@@ -55,7 +71,12 @@ class SelfossModel {
 | 
				
			|||||||
        val tags: List<String>,
 | 
					        val tags: List<String>,
 | 
				
			||||||
        val spout: String,
 | 
					        val spout: String,
 | 
				
			||||||
        val error: String,
 | 
					        val error: String,
 | 
				
			||||||
        val icon: String?
 | 
					        val icon: String?,
 | 
				
			||||||
 | 
					        val params: SourceParams?
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    @Serializable
 | 
				
			||||||
 | 
					    data class SourceParams(
 | 
				
			||||||
 | 
					        val url: String? = null
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    @Serializable
 | 
					    @Serializable
 | 
				
			||||||
    data class Item(
 | 
					    data class Item(
 | 
				
			||||||
@@ -75,19 +96,13 @@ class SelfossModel {
 | 
				
			|||||||
        val tags: List<String>,
 | 
					        val tags: List<String>,
 | 
				
			||||||
        val author: String?
 | 
					        val author: String?
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        // TODO: maybe find a better way to handle these kind of urls
 | 
					 | 
				
			||||||
        fun getLinkDecoded(): String {
 | 
					        fun getLinkDecoded(): String {
 | 
				
			||||||
            var stringUrl: String
 | 
					            var stringUrl: String
 | 
				
			||||||
            stringUrl =
 | 
					            stringUrl = if (link.contains("//news.google.com/news/") && link.contains("&url=")) {
 | 
				
			||||||
                if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
 | 
					 | 
				
			||||||
                    if (link.contains("&url=")) {
 | 
					 | 
				
			||||||
                link.substringAfter("&url=")
 | 
					                link.substringAfter("&url=")
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                this.link.replace("&", "&")
 | 
					                this.link.replace("&", "&")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    this.link.replace("&", "&")
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // handle :443 => https
 | 
					            // handle :443 => https
 | 
				
			||||||
            if (stringUrl.contains(":443")) {
 | 
					            if (stringUrl.contains(":443")) {
 | 
				
			||||||
@@ -122,7 +137,7 @@ class SelfossModel {
 | 
				
			|||||||
    object TagsListSerializer : KSerializer<List<String>> {
 | 
					    object TagsListSerializer : KSerializer<List<String>> {
 | 
				
			||||||
        override fun deserialize(decoder: Decoder): List<String> {
 | 
					        override fun deserialize(decoder: Decoder): List<String> {
 | 
				
			||||||
            return when(val json = ((decoder as JsonDecoder).decodeJsonElement())) {
 | 
					            return when(val json = ((decoder as JsonDecoder).decodeJsonElement())) {
 | 
				
			||||||
                is JsonArray -> json.toList().map { it.toString() }
 | 
					                is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") }
 | 
				
			||||||
                else -> json.toString().split(",")
 | 
					                else -> json.toString().split(",")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -132,7 +147,7 @@ class SelfossModel {
 | 
				
			|||||||
            get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
 | 
					            get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        override fun serialize(encoder: Encoder, value: List<String>) {
 | 
					        override fun serialize(encoder: Encoder, value: List<String>) {
 | 
				
			||||||
            TODO("Not yet implemented")
 | 
					            encoder.encodeCollection(PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING), value.size) { this.toString() }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,11 +48,10 @@ class Repository(
 | 
				
			|||||||
    private var fetchedTags = false
 | 
					    private var fetchedTags = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var _readerItems = ArrayList<SelfossModel.Item>()
 | 
					    private var _readerItems = ArrayList<SelfossModel.Item>()
 | 
				
			||||||
 | 
					    private var _selectedSource: SelfossModel.Source? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
 | 
					    suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
 | 
				
			||||||
        // TODO: Use the updatedSince parameter
 | 
					 | 
				
			||||||
        var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
 | 
					        var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
 | 
				
			||||||
        var fromDB = false
 | 
					 | 
				
			||||||
        if (isNetworkAvailable()) {
 | 
					        if (isNetworkAvailable()) {
 | 
				
			||||||
            fetchedItems = api.getItems(
 | 
					            fetchedItems = api.getItems(
 | 
				
			||||||
                displayedItems.type,
 | 
					                displayedItems.type,
 | 
				
			||||||
@@ -62,9 +61,7 @@ class Repository(
 | 
				
			|||||||
                searchFilter,
 | 
					                searchFilter,
 | 
				
			||||||
                null
 | 
					                null
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        } else {
 | 
					        } else if (appSettingsService.isItemCachingEnabled()) {
 | 
				
			||||||
            if (appSettingsService.isItemCachingEnabled()) {
 | 
					 | 
				
			||||||
                fromDB = true
 | 
					 | 
				
			||||||
            var dbItems = getDBItems().filter {
 | 
					            var dbItems = getDBItems().filter {
 | 
				
			||||||
                displayedItems == ItemType.ALL ||
 | 
					                displayedItems == ItemType.ALL ||
 | 
				
			||||||
                        (it.unread && displayedItems == ItemType.UNREAD) ||
 | 
					                        (it.unread && displayedItems == ItemType.UNREAD) ||
 | 
				
			||||||
@@ -76,17 +73,15 @@ class Repository(
 | 
				
			|||||||
            if (sourceFilter.value != null) {
 | 
					            if (sourceFilter.value != null) {
 | 
				
			||||||
                dbItems = dbItems.filter { it.sourcetitle == sourceFilter.value!!.title }
 | 
					                dbItems = dbItems.filter { it.sourcetitle == sourceFilter.value!!.title }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            val itemsList = ArrayList(dbItems.map { it.toView() })
 | 
				
			||||||
 | 
					            itemsList.sortByDescending { DateUtils.parseDate(it.datetime) }
 | 
				
			||||||
            fetchedItems = StatusAndData.succes(
 | 
					            fetchedItems = StatusAndData.succes(
 | 
				
			||||||
                    dbItems.map { it.toView() }
 | 
					                itemsList
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (fetchedItems.success && fetchedItems.data != null) {
 | 
					        if (fetchedItems.success && fetchedItems.data != null) {
 | 
				
			||||||
            items = ArrayList(fetchedItems.data!!)
 | 
					            items = ArrayList(fetchedItems.data!!)
 | 
				
			||||||
            if (fromDB) {
 | 
					 | 
				
			||||||
                items.sortByDescending { DateUtils.parseDate(it.datetime) }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return items
 | 
					        return items
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -137,9 +132,9 @@ class Repository(
 | 
				
			|||||||
        if (isNetworkAvailable()) {
 | 
					        if (isNetworkAvailable()) {
 | 
				
			||||||
            val response = api.stats()
 | 
					            val response = api.stats()
 | 
				
			||||||
            if (response.success && response.data != null) {
 | 
					            if (response.success && response.data != null) {
 | 
				
			||||||
                _badgeUnread.value = response.data.unread
 | 
					                _badgeUnread.value = response.data.unread ?: 0
 | 
				
			||||||
                _badgeAll.value = response.data.total
 | 
					                _badgeAll.value = response.data.total
 | 
				
			||||||
                _badgeStarred.value = response.data.starred
 | 
					                _badgeStarred.value = response.data.starred ?: 0
 | 
				
			||||||
                success = true
 | 
					                success = true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else if (appSettingsService.isItemCachingEnabled()) {
 | 
					        } else if (appSettingsService.isItemCachingEnabled()) {
 | 
				
			||||||
@@ -172,14 +167,13 @@ class Repository(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Add tests
 | 
					 | 
				
			||||||
    suspend fun getSpouts(): Map<String, SelfossModel.Spout> {
 | 
					    suspend fun getSpouts(): Map<String, SelfossModel.Spout> {
 | 
				
			||||||
        return if (isNetworkAvailable()) {
 | 
					        return if (isNetworkAvailable()) {
 | 
				
			||||||
            val spouts = api.spouts()
 | 
					            val spouts = api.spouts()
 | 
				
			||||||
            if (spouts.success && spouts.data != null) {
 | 
					            if (spouts.success && spouts.data != null) {
 | 
				
			||||||
                spouts.data
 | 
					                spouts.data
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                emptyMap() // TODO: do something here
 | 
					                emptyMap()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            throw NetworkUnavailableException()
 | 
					            throw NetworkUnavailableException()
 | 
				
			||||||
@@ -205,7 +199,6 @@ class Repository(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Add tests
 | 
					 | 
				
			||||||
    suspend fun markAsRead(item: SelfossModel.Item): Boolean {
 | 
					    suspend fun markAsRead(item: SelfossModel.Item): Boolean {
 | 
				
			||||||
        val success = markAsReadById(item.id)
 | 
					        val success = markAsReadById(item.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -224,7 +217,6 @@ class Repository(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Add tests
 | 
					 | 
				
			||||||
    suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
 | 
					    suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
 | 
				
			||||||
        val success = unmarkAsReadById(item.id)
 | 
					        val success = unmarkAsReadById(item.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -243,7 +235,6 @@ class Repository(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Add tests
 | 
					 | 
				
			||||||
    suspend fun starr(item: SelfossModel.Item): Boolean {
 | 
					    suspend fun starr(item: SelfossModel.Item): Boolean {
 | 
				
			||||||
        val success = starrById(item.id)
 | 
					        val success = starrById(item.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -262,7 +253,6 @@ class Repository(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Add tests
 | 
					 | 
				
			||||||
    suspend fun unstarr(item: SelfossModel.Item): Boolean {
 | 
					    suspend fun unstarr(item: SelfossModel.Item): Boolean {
 | 
				
			||||||
        val success = unstarrById(item.id)
 | 
					        val success = unstarrById(item.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -281,7 +271,6 @@ class Repository(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Add tests
 | 
					 | 
				
			||||||
    suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
 | 
					    suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
 | 
				
			||||||
        var success = false
 | 
					        var success = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -343,7 +332,6 @@ class Repository(
 | 
				
			|||||||
        url: String,
 | 
					        url: String,
 | 
				
			||||||
        spout: String,
 | 
					        spout: String,
 | 
				
			||||||
        tags: String,
 | 
					        tags: String,
 | 
				
			||||||
        filter: String
 | 
					 | 
				
			||||||
    ): Boolean {
 | 
					    ): Boolean {
 | 
				
			||||||
        var response = false
 | 
					        var response = false
 | 
				
			||||||
        if (isNetworkAvailable()) {
 | 
					        if (isNetworkAvailable()) {
 | 
				
			||||||
@@ -352,13 +340,27 @@ class Repository(
 | 
				
			|||||||
                url,
 | 
					                url,
 | 
				
			||||||
                spout,
 | 
					                spout,
 | 
				
			||||||
                tags,
 | 
					                tags,
 | 
				
			||||||
                filter
 | 
					 | 
				
			||||||
            ).isSuccess == true
 | 
					            ).isSuccess == true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return response
 | 
					        return response
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun updateSource(
 | 
				
			||||||
 | 
					        id: Int,
 | 
				
			||||||
 | 
					        title: String,
 | 
				
			||||||
 | 
					        url: String,
 | 
				
			||||||
 | 
					        spout: String,
 | 
				
			||||||
 | 
					        tags: String
 | 
				
			||||||
 | 
					    ): Boolean {
 | 
				
			||||||
 | 
					        var response = false
 | 
				
			||||||
 | 
					        if (isNetworkAvailable()) {
 | 
				
			||||||
 | 
					            response = api.updateSourceForVersion(id, title, url, spout, tags).isSuccess == true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return response
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun deleteSource(id: Int, title: String): Boolean {
 | 
					    suspend fun deleteSource(id: Int, title: String): Boolean {
 | 
				
			||||||
        var success = false
 | 
					        var success = false
 | 
				
			||||||
        if (isNetworkAvailable()) {
 | 
					        if (isNetworkAvailable()) {
 | 
				
			||||||
@@ -437,13 +439,23 @@ class Repository(
 | 
				
			|||||||
        api.refreshLoginInformation()
 | 
					        api.refreshLoginInformation()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun updateApiVersion() {
 | 
					    suspend fun updateApiInformation() {
 | 
				
			||||||
        val apiMajorVersion = appSettingsService.getApiVersion()
 | 
					        val apiMajorVersion = appSettingsService.getApiVersion()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (isNetworkAvailable()) {
 | 
					        if (isNetworkAvailable()) {
 | 
				
			||||||
            val fetchedVersion = api.version()
 | 
					            val fetchedInformation = api.apiInformation()
 | 
				
			||||||
            if (fetchedVersion.success && fetchedVersion.data != null && fetchedVersion.data.getApiMajorVersion() != apiMajorVersion) {
 | 
					            if (fetchedInformation.success && fetchedInformation.data != null) {
 | 
				
			||||||
                appSettingsService.updateApiVersion(fetchedVersion.data.getApiMajorVersion())
 | 
					                if (fetchedInformation.data.getApiMajorVersion() != apiMajorVersion) {
 | 
				
			||||||
 | 
					                    appSettingsService.updateApiVersion(fetchedInformation.data.getApiMajorVersion())
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                // Check if we're accessing the instance in public mode
 | 
				
			||||||
 | 
					                // This happens when auth and public mode are enabled but
 | 
				
			||||||
 | 
					                // no credentials are provided to login
 | 
				
			||||||
 | 
					                if (appSettingsService.getUserName().isEmpty()
 | 
				
			||||||
 | 
					                    && fetchedInformation.data.getApiConfiguration().isAuthEnabled()
 | 
				
			||||||
 | 
					                    && fetchedInformation.data.getApiConfiguration().isPublicModeEnabled()) {
 | 
				
			||||||
 | 
					                    appSettingsService.updatePublicAccess(true)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -528,7 +540,6 @@ class Repository(
 | 
				
			|||||||
        return emptyList()
 | 
					        return emptyList()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Add tests
 | 
					 | 
				
			||||||
    suspend fun handleDBActions() {
 | 
					    suspend fun handleDBActions() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val actions: List<ACTION> = getDBActions()
 | 
					        val actions: List<ACTION> = getDBActions()
 | 
				
			||||||
@@ -576,4 +587,20 @@ class Repository(
 | 
				
			|||||||
    fun getReaderItems(): ArrayList<SelfossModel.Item> {
 | 
					    fun getReaderItems(): ArrayList<SelfossModel.Item> {
 | 
				
			||||||
        return _readerItems
 | 
					        return _readerItems
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun migrate(driverFactory: DriverFactory) {
 | 
				
			||||||
 | 
					        ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun setSelectedSource(source: SelfossModel.Source) {
 | 
				
			||||||
 | 
					        _selectedSource = source
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun unsetSelectedSource() {
 | 
				
			||||||
 | 
					        _selectedSource = null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getSelectedSource(): SelfossModel.Source? {
 | 
				
			||||||
 | 
					        return _selectedSource
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -23,6 +23,9 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
 | 
				
			|||||||
    return if (r != null && r.status.isSuccess()) {
 | 
					    return if (r != null && r.status.isSuccess()) {
 | 
				
			||||||
        r.body()
 | 
					        r.body()
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
 | 
					        if (r != null) {
 | 
				
			||||||
 | 
					            Napier.i("Error ${r.status}", tag = "maybeResponse")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        SuccessResponse(false)
 | 
					        SuccessResponse(false)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -76,10 +76,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Api version was introduces after the POST login, so when there is a version, it should be available
 | 
					    // Api version was introduces after the POST login, so when there is a version, it should be available
 | 
				
			||||||
    private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1
 | 
					    private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1
 | 
				
			||||||
    private fun hasLoginInfo() = appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty()
 | 
					    private fun hasLoginInfo() =
 | 
				
			||||||
 | 
					        appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword()
 | 
				
			||||||
 | 
					            .isNotEmpty()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun login(): SuccessResponse =
 | 
					    suspend fun login(): SuccessResponse =
 | 
				
			||||||
        if (appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty()) {
 | 
					        if (appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword()
 | 
				
			||||||
 | 
					                .isNotEmpty()
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
            if (shouldHavePostLogin()) {
 | 
					            if (shouldHavePostLogin()) {
 | 
				
			||||||
                postLogin()
 | 
					                postLogin()
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
@@ -99,7 +103,9 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
				
			|||||||
        parameter("password", appSettingsService.getPassword())
 | 
					        parameter("password", appSettingsService.getPassword())
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
 | 
					    private fun shouldHaveNewLogout() =
 | 
				
			||||||
 | 
					        appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun logout(): SuccessResponse =
 | 
					    suspend fun logout(): SuccessResponse =
 | 
				
			||||||
        if (shouldHaveNewLogout()) {
 | 
					        if (shouldHaveNewLogout()) {
 | 
				
			||||||
            doLogout()
 | 
					            doLogout()
 | 
				
			||||||
@@ -107,7 +113,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
				
			|||||||
            maybeLogoutIfAvailable()
 | 
					            maybeLogoutIfAvailable()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private suspend fun maybeLogoutIfAvailable() = responseOrSuccessIf404(client.tryToGet(url("/logout")))
 | 
					    private suspend fun maybeLogoutIfAvailable() =
 | 
				
			||||||
 | 
					        responseOrSuccessIf404(client.tryToGet(url("/logout")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")))
 | 
					    private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -184,7 +191,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun version(): StatusAndData<SelfossModel.ApiVersion> =
 | 
					    suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> =
 | 
				
			||||||
        bodyOrFailure(client.tryToGet(url("/api/about")))
 | 
					        bodyOrFailure(client.tryToGet(url("/api/about")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun markAsRead(id: String): SuccessResponse =
 | 
					    suspend fun markAsRead(id: String): SuccessResponse =
 | 
				
			||||||
@@ -236,13 +243,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
				
			|||||||
        url: String,
 | 
					        url: String,
 | 
				
			||||||
        spout: String,
 | 
					        spout: String,
 | 
				
			||||||
        tags: String,
 | 
					        tags: String,
 | 
				
			||||||
        filter: String
 | 
					 | 
				
			||||||
    ): SuccessResponse =
 | 
					    ): SuccessResponse =
 | 
				
			||||||
        maybeResponse(
 | 
					        maybeResponse(
 | 
				
			||||||
            if (appSettingsService.getApiVersion() > 1) {
 | 
					            if (appSettingsService.getApiVersion() > 1) {
 | 
				
			||||||
                createSource("tags[]", title, url, spout, tags, filter)
 | 
					                createSource("tags[]", title, url, spout, tags)
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                createSource("tags", title, url, spout, tags, filter)
 | 
					                createSource("tags", title, url, spout, tags)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -251,8 +257,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
				
			|||||||
        title: String,
 | 
					        title: String,
 | 
				
			||||||
        url: String,
 | 
					        url: String,
 | 
				
			||||||
        spout: String,
 | 
					        spout: String,
 | 
				
			||||||
        tags: String,
 | 
					        tags: String
 | 
				
			||||||
        filter: String
 | 
					 | 
				
			||||||
    ): HttpResponse? =
 | 
					    ): HttpResponse? =
 | 
				
			||||||
        client.tryToSubmitForm(
 | 
					        client.tryToSubmitForm(
 | 
				
			||||||
            url = url("/source"),
 | 
					            url = url("/source"),
 | 
				
			||||||
@@ -265,7 +270,43 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
				
			|||||||
                append("url", url)
 | 
					                append("url", url)
 | 
				
			||||||
                append("spout", spout)
 | 
					                append("spout", spout)
 | 
				
			||||||
                append(tagsParamName, tags)
 | 
					                append(tagsParamName, tags)
 | 
				
			||||||
                append("filter", filter)
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun updateSourceForVersion(
 | 
				
			||||||
 | 
					        id: Int,
 | 
				
			||||||
 | 
					        title: String,
 | 
				
			||||||
 | 
					        url: String,
 | 
				
			||||||
 | 
					        spout: String,
 | 
				
			||||||
 | 
					        tags: String
 | 
				
			||||||
 | 
					    ): SuccessResponse =
 | 
				
			||||||
 | 
					        maybeResponse(
 | 
				
			||||||
 | 
					            if (appSettingsService.getApiVersion() > 1) {
 | 
				
			||||||
 | 
					                updateSource(id, "tags[]", title, url, spout, tags)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                updateSource(id, "tags", title, url, spout, tags)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private suspend fun updateSource(
 | 
				
			||||||
 | 
					        id: Int,
 | 
				
			||||||
 | 
					        tagsParamName: String,
 | 
				
			||||||
 | 
					        title: String,
 | 
				
			||||||
 | 
					        url: String,
 | 
				
			||||||
 | 
					        spout: String,
 | 
				
			||||||
 | 
					        tags: String,
 | 
				
			||||||
 | 
					    ): HttpResponse? =
 | 
				
			||||||
 | 
					        client.tryToSubmitForm(
 | 
				
			||||||
 | 
					            url = url("/source/$id"),
 | 
				
			||||||
 | 
					            formParameters = Parameters.build {
 | 
				
			||||||
 | 
					                if (!shouldHavePostLogin()) {
 | 
				
			||||||
 | 
					                    append("username", appSettingsService.getUserName())
 | 
				
			||||||
 | 
					                    append("password", appSettingsService.getPassword())
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                append("title", title)
 | 
				
			||||||
 | 
					                append("url", url)
 | 
				
			||||||
 | 
					                append("spout", spout)
 | 
				
			||||||
 | 
					                append(tagsParamName, tags)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					package bou.amine.apps.readerforselfossv2.service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.russhwolf.settings.Settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This will be used in ACRA process. For now, it does nothing.
 | 
				
			||||||
 | 
					// This is to fix ACRA not sending reports anymore.
 | 
				
			||||||
 | 
					// See https://www.acra.ch/docs/Troubleshooting-Guide#applicationoncreate
 | 
				
			||||||
 | 
					class ACRASettings : Settings {
 | 
				
			||||||
 | 
					    override val keys: Set<String> = emptySet()
 | 
				
			||||||
 | 
					    override val size: Int = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun clear() {
 | 
				
			||||||
 | 
					        // Nothing
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getBoolean(key: String, defaultValue: Boolean): Boolean = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getBooleanOrNull(key: String): Boolean? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getDouble(key: String, defaultValue: Double): Double = 0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getDoubleOrNull(key: String): Double? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getFloat(key: String, defaultValue: Float): Float = 0.0F
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getFloatOrNull(key: String): Float? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getInt(key: String, defaultValue: Int): Int = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getIntOrNull(key: String): Int? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getLong(key: String, defaultValue: Long): Long = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getLongOrNull(key: String): Long? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getString(key: String, defaultValue: String): String = "0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getStringOrNull(key: String): String? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun hasKey(key: String): Boolean = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun putBoolean(key: String, value: Boolean) {
 | 
				
			||||||
 | 
					        // Nothing
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun putDouble(key: String, value: Double) {
 | 
				
			||||||
 | 
					        // Nothing
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun putFloat(key: String, value: Float) {
 | 
				
			||||||
 | 
					        // Nothing
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun putInt(key: String, value: Int) {
 | 
				
			||||||
 | 
					        // Nothing
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun putLong(key: String, value: Long) {
 | 
				
			||||||
 | 
					        // Nothing
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun putString(key: String, value: String) {
 | 
				
			||||||
 | 
					        // Nothing
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun remove(key: String) {
 | 
				
			||||||
 | 
					        // Nothing
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,14 +1,13 @@
 | 
				
			|||||||
package bou.amine.apps.readerforselfossv2.service
 | 
					package bou.amine.apps.readerforselfossv2.service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.russhwolf.settings.Settings
 | 
					import com.russhwolf.settings.Settings
 | 
				
			||||||
import io.github.aakira.napier.Napier
 | 
					 | 
				
			||||||
import io.ktor.client.plugins.*
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AppSettingsService {
 | 
					class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
 | 
				
			||||||
    val settings: Settings = Settings()
 | 
					    val settings: Settings = if (acraSenderServiceProcess) { ACRASettings() } else { Settings() }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Api related
 | 
					    // Api related
 | 
				
			||||||
    private var _apiVersion: Int = -1
 | 
					    private var _apiVersion: Int = -1
 | 
				
			||||||
 | 
					    private var _publicAccess: Boolean? = null
 | 
				
			||||||
    private var _baseUrl: String = ""
 | 
					    private var _baseUrl: String = ""
 | 
				
			||||||
    private var _userName: String = ""
 | 
					    private var _userName: String = ""
 | 
				
			||||||
    private var _password: String = ""
 | 
					    private var _password: String = ""
 | 
				
			||||||
@@ -50,10 +49,32 @@ class AppSettingsService {
 | 
				
			|||||||
        return _apiVersion
 | 
					        return _apiVersion
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun updateApiVersion(apiMajorVersion: Int) {
 | 
				
			||||||
 | 
					        settings.putInt(API_VERSION_MAJOR, apiMajorVersion)
 | 
				
			||||||
 | 
					        refreshApiVersion()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun refreshApiVersion() {
 | 
					    private fun refreshApiVersion() {
 | 
				
			||||||
        _apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
 | 
					        _apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getPublicAccess(): Boolean {
 | 
				
			||||||
 | 
					        if (_publicAccess == null) {
 | 
				
			||||||
 | 
					            refreshPublicAccess()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return _publicAccess!!
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun updatePublicAccess(publicAccess: Boolean) {
 | 
				
			||||||
 | 
					        settings.putBoolean(API_PUBLIC_ACCESS, publicAccess)
 | 
				
			||||||
 | 
					        refreshPublicAccess()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun refreshPublicAccess() {
 | 
				
			||||||
 | 
					        _publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun getBaseUrl(): String {
 | 
					    fun getBaseUrl(): String {
 | 
				
			||||||
        if (_baseUrl.isEmpty()) {
 | 
					        if (_baseUrl.isEmpty()) {
 | 
				
			||||||
            refreshBaseUrl()
 | 
					            refreshBaseUrl()
 | 
				
			||||||
@@ -335,6 +356,7 @@ class AppSettingsService {
 | 
				
			|||||||
        refreshUsername()
 | 
					        refreshUsername()
 | 
				
			||||||
        refreshBaseUrl()
 | 
					        refreshBaseUrl()
 | 
				
			||||||
        refreshApiVersion()
 | 
					        refreshApiVersion()
 | 
				
			||||||
 | 
					        refreshPublicAccess()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun refreshUserSettings() {
 | 
					    fun refreshUserSettings() {
 | 
				
			||||||
@@ -378,11 +400,6 @@ class AppSettingsService {
 | 
				
			|||||||
        refreshApiSettings()
 | 
					        refreshApiSettings()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun updateApiVersion(apiMajorVersion: Int) {
 | 
					 | 
				
			||||||
        settings.putInt(API_VERSION_MAJOR, apiMajorVersion)
 | 
					 | 
				
			||||||
        refreshApiVersion()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun clearAll() {
 | 
					    fun clearAll() {
 | 
				
			||||||
        settings.clear()
 | 
					        settings.clear()
 | 
				
			||||||
        refreshApiSettings()
 | 
					        refreshApiSettings()
 | 
				
			||||||
@@ -411,6 +428,8 @@ class AppSettingsService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const val API_VERSION_MAJOR = "apiVersionMajor"
 | 
					        const val API_VERSION_MAJOR = "apiVersionMajor"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const val API_PUBLIC_ACCESS = "apiPublicAccess"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const val API_ITEMS_NUMBER = "prefer_api_items_number"
 | 
					        const val API_ITEMS_NUMBER = "prefer_api_items_number"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const val API_TIMEOUT = "api_timeout"
 | 
					        const val API_TIMEOUT = "api_timeout"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,8 @@ fun SOURCE.toView(): SelfossModel.Source =
 | 
				
			|||||||
        this.tags.split(","),
 | 
					        this.tags.split(","),
 | 
				
			||||||
        this.spout,
 | 
					        this.spout,
 | 
				
			||||||
        this.error,
 | 
					        this.error,
 | 
				
			||||||
        this.icon
 | 
					        this.icon,
 | 
				
			||||||
 | 
					        if (this.url != null) SelfossModel.SourceParams(this.url) else null
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun SelfossModel.Source.toEntity(): SOURCE =
 | 
					fun SelfossModel.Source.toEntity(): SOURCE =
 | 
				
			||||||
@@ -29,7 +30,8 @@ fun SelfossModel.Source.toEntity(): SOURCE =
 | 
				
			|||||||
        this.tags.joinToString(","),
 | 
					        this.tags.joinToString(","),
 | 
				
			||||||
        this.spout,
 | 
					        this.spout,
 | 
				
			||||||
        this.error,
 | 
					        this.error,
 | 
				
			||||||
        this.icon.orEmpty()
 | 
					        this.icon.orEmpty(),
 | 
				
			||||||
 | 
					        this.params?.url
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun SelfossModel.Tag.toEntity(): TAG =
 | 
					fun SelfossModel.Tag.toEntity(): TAG =
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1,6 @@
 | 
				
			|||||||
ALTER TABLE ITEM ADD COLUMN `author` TEXT NOT NULL;
 | 
					CREATE TABLE ITEM_BACKUP AS SELECT `id`, `datetime`, `title`, `content`,
 | 
				
			||||||
 | 
					 `unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`,
 | 
				
			||||||
 | 
					 `tags` FROM ITEM;
 | 
				
			||||||
 | 
					ALTER TABLE ITEM_BACKUP ADD COLUMN `author` TEXT;
 | 
				
			||||||
 | 
					DROP TABLE ITEM;
 | 
				
			||||||
 | 
					ALTER TABLE ITEM_BACKUP RENAME TO ITEM;
 | 
				
			||||||
@@ -1,6 +1 @@
 | 
				
			|||||||
CREATE TABLE ITEM_BACKUP AS SELECT `id`, `datetime`, `title`, `content`,
 | 
					ALTER TABLE SOURCE ADD COLUMN `url` TEXT;
 | 
				
			||||||
 `unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`,
 | 
					 | 
				
			||||||
 `tags` FROM ITEM;
 | 
					 | 
				
			||||||
ALTER TABLE ITEM_BACKUP ADD COLUMN `author` TEXT;
 | 
					 | 
				
			||||||
DROP TABLE ITEM;
 | 
					 | 
				
			||||||
ALTER TABLE ITEM_BACKUP RENAME TO ITEM;
 | 
					 | 
				
			||||||
@@ -5,6 +5,7 @@ CREATE TABLE SOURCE (
 | 
				
			|||||||
    `spout` TEXT NOT NULL,
 | 
					    `spout` TEXT NOT NULL,
 | 
				
			||||||
    `error` TEXT NOT NULL,
 | 
					    `error` TEXT NOT NULL,
 | 
				
			||||||
    `icon` TEXT NOT NULL,
 | 
					    `icon` TEXT NOT NULL,
 | 
				
			||||||
 | 
					    `url` TEXT,
 | 
				
			||||||
    PRIMARY KEY(`id`)
 | 
					    PRIMARY KEY(`id`)
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user