Compare commits

..

4 Commits

Author SHA1 Message Date
66eada7f09 fix: this fixes the workflow not stopping on error.
Some checks failed
Check PR code / Lint (pull_request) Successful in 3m21s
Check PR code / build (pull_request) Failing after 11s
2025-01-11 21:09:12 +01:00
55a6b83837 chore: code style fixes for detekt
All checks were successful
Check PR code / Lint (pull_request) Successful in 3m18s
Check PR code / build (pull_request) Successful in 11m53s
2025-01-11 20:52:53 +01:00
4fbebf2954 chore: code style fixes for ktlint
Some checks failed
Check PR code / Lint (pull_request) Failing after 2m19s
Check PR code / build (pull_request) Has been skipped
2025-01-11 15:23:02 +01:00
5035392aff chore: more debug to fix a context issue. 2025-01-11 12:52:55 +01:00
53 changed files with 553 additions and 601 deletions

View File

@ -26,10 +26,9 @@ jobs:
- uses: KengoTODA/actions-setup-docker-compose@v1 - uses: KengoTODA/actions-setup-docker-compose@v1
with: with:
version: "2.23.3" version: "2.23.3"
# TESTS ARE RUN LOCALLY - name: run selfoss
# - name: run selfoss run: |
# run: | docker compose -f .gitea/workflows/assets/docker-compose.yml up -d
# docker compose -f .gitea/workflows/assets/docker-compose.yml up -d
- name: coverage - name: coverage
run: | run: |
./gradlew :koverHtmlReport ./gradlew :koverHtmlReport
@ -40,8 +39,7 @@ jobs:
retention-days: 1 retention-days: 1
overwrite: true overwrite: true
include-hidden-files: true include-hidden-files: true
# TESTS ARE RUN LOCALLY - name: Clean
# - name: Clean if: always()
# if: always() run: |
# run: | docker compose -f .gitea/workflows/assets/docker-compose.yml stop
# docker compose -f .gitea/workflows/assets/docker-compose.yml stop

View File

@ -7,7 +7,8 @@ on:
jobs: jobs:
build: build:
uses: ./.gitea/workflows/common_build.yml steps:
- uses: ./.gitea/workflows/common_build.yml
createTagAndChangelog: createTagAndChangelog:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
@ -16,7 +17,6 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
ref: master
- name: Config git - name: Config git
run: | run: |
git config --global user.email aminecmi+giteadrone@pm.me git config --global user.email aminecmi+giteadrone@pm.me
@ -51,7 +51,7 @@ jobs:
followtags: true followtags: true
ssh_key: ${{ secrets.PRIVATE_KEY }} ssh_key: ${{ secrets.PRIVATE_KEY }}
tags: true tags: true
branch: master branch: release
- name: copy file via ssh password - name: copy file via ssh password
uses: appleboy/scp-action@v0.1.7 uses: appleboy/scp-action@v0.1.7
with: with:

View File

@ -18,11 +18,12 @@ jobs:
- name: Install klint - name: Install klint
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/ run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
- name: Install detekt - name: Install detekt
run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.7/detekt-cli-1.23.7.zip && unzip detekt-cli-1.23.7.zip run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.7/detekt-cli-1.23.7.zip && unzip detekt-cli-1.23.7.zip
- name: Linting... - name: Linting...
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build' run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
- name: Detecting... - name: Detecting...
run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt' run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt'
build: build:
needs: Lint needs: Lint
uses: ./.gitea/workflows/common_build.yml steps:
- uses: ./.gitea/workflows/common_build.yml

4
.gitignore vendored
View File

@ -323,6 +323,4 @@ fabric.properties
crowdin.properties crowdin.properties
.kotlin/ .kotlin/
build-cache/ build-cache/
act

View File

@ -1,58 +1,3 @@
**v125020471
- chore: no more docker-compose.
- bump: gradle plugin.
- Merge pull request 'fix: check index exists.' (#183) from fix-index into master
- fix: check index exists.
- Changelog for v125020411
--------------------------------------------------------------------
**v125020411
- Merge pull request 'bump' (#182) from bump into master
- chore: non transiant R classes.
- Merge pull request 'fix: One more missing context.' (#181) from fix-one-more-context into master
- bump
- fix: One more missing context.
--------------------------------------------------------------------
**v125010241
- Merge pull request 'fix: Link not opening.' (#178) from fix-open-link into master
- refactor: context fragments issues.
- logs: Context issues.
- fix: Handle empty url issue, again.
- fix: Link not opening.
- Changelog for v125010201
--------------------------------------------------------------------
**v125010201
- fix: Handle empty url issue.
- Merge pull request 'Removed the floating bar.' (#177) from floating-bar into master
- chore: changing actions in reader fragment.
- Changelog for v125010131
--------------------------------------------------------------------
**v125010131
- fix: reload the adapter when it's needed. Fixes #128. (#176)
- feat: basic auth and images loading. Fixes #172. (#175)
- Changelog for v125010111
--------------------------------------------------------------------
**v125010111
- Debug trying to fix context issues. (#174)
- Changelog for v125010031
--------------------------------------------------------------------
**v125010031 **v125010031
- Merge pull request 'Bump dependencies' (#173) from upgarde into master - Merge pull request 'Bump dependencies' (#173) from upgarde into master

View File

@ -156,7 +156,7 @@ dependencies {
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0") implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
// Themes // Themes
implementation("com.leinardi.android:speed-dial:3.3.0") implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
// Pager // Pager
implementation("me.relex:circleindicator:2.1.6") implementation("me.relex:circleindicator:2.1.6")

View File

@ -56,7 +56,7 @@ class HomeActivityTest {
fun testMenuActions() { fun testMenuActions() {
onView(withId(R.id.action_search)).perform(click()) onView(withId(R.id.action_search)).perform(click())
onView( onView(
withId(com.google.android.material.R.id.search_src_text), withId(R.id.search_src_text),
).check(matches(isFocused())) ).check(matches(isFocused()))
onView(isRoot()).perform(ViewActions.pressBack()) onView(isRoot()).perform(ViewActions.pressBack())

View File

@ -65,6 +65,19 @@ class SettingsActivityGeneralTest {
), ),
), ),
) )
onView(withSettingsCheckboxWidget(R.string.reader_static_bar_title)).check(
matches(
allOf(
isDisplayed(),
not(isChecked()),
),
),
)
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
matches(
isEnabled(),
),
)
onView(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed())) onView(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed()))
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check( onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
matches( matches(
@ -148,6 +161,19 @@ class SettingsActivityGeneralTest {
@Test @Test
fun testGeneralActionsCheckboxes() { fun testGeneralActionsCheckboxes() {
// article viewer settings
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
matches(
isEnabled(),
),
)
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).perform(click())
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
matches(
not(isEnabled()),
),
)
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled()))) onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled())))
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).perform(click()) onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).perform(click())
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(isEnabled())) onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(isEnabled()))

View File

@ -31,7 +31,7 @@ import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
import bou.amine.apps.readerforselfossv2.model.SelfossModel 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
@ -317,44 +317,50 @@ class HomeActivity :
private fun reloadLayoutManager() { private fun reloadLayoutManager() {
val currentManager = binding.recyclerView.layoutManager val currentManager = binding.recyclerView.layoutManager
val layoutManager: RecyclerView.LayoutManager
fun gridLayoutManager() { // This will only update the layout manager if settings changed
val layoutManager =
GridLayoutManager(
this,
calculateNoOfColumns(),
)
binding.recyclerView.layoutManager = layoutManager
}
fun staggererdGridLayoutManager() {
var layoutManager =
StaggeredGridLayoutManager(
calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL,
)
layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
binding.recyclerView.layoutManager = layoutManager
}
when (currentManager) { when (currentManager) {
is StaggeredGridLayoutManager -> is StaggeredGridLayoutManager ->
if (!appSettingsService.isCardViewEnabled()) { if (!appSettingsService.isCardViewEnabled()) {
gridLayoutManager() layoutManager =
GridLayoutManager(
this,
calculateNoOfColumns(),
)
binding.recyclerView.layoutManager = layoutManager
} }
is GridLayoutManager -> is GridLayoutManager ->
if (appSettingsService.isCardViewEnabled()) { if (appSettingsService.isCardViewEnabled()) {
staggererdGridLayoutManager() layoutManager =
StaggeredGridLayoutManager(
calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL,
)
layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
binding.recyclerView.layoutManager = layoutManager
} }
else -> else ->
if (currentManager == null) { if (currentManager == null) {
if (!appSettingsService.isCardViewEnabled()) { if (!appSettingsService.isCardViewEnabled()) {
gridLayoutManager() layoutManager =
GridLayoutManager(
this,
calculateNoOfColumns(),
)
binding.recyclerView.layoutManager = layoutManager
} else { } else {
staggererdGridLayoutManager() layoutManager =
StaggeredGridLayoutManager(
calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL,
)
layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
binding.recyclerView.layoutManager = layoutManager
} }
} }
} }
@ -479,8 +485,8 @@ class HomeActivity :
} }
private fun handleListResult(appendResults: Boolean = false) { private fun handleListResult(appendResults: Boolean = false) {
val oldManager = binding.recyclerView.layoutManager
if (appendResults) { if (appendResults) {
val oldManager = binding.recyclerView.layoutManager
firstVisible = firstVisible =
when (oldManager) { when (oldManager) {
is StaggeredGridLayoutManager -> is StaggeredGridLayoutManager ->
@ -493,13 +499,7 @@ class HomeActivity :
} }
} }
@Suppress("detekt:ComplexCondition") if (recyclerAdapter == null) {
if (recyclerAdapter == null ||
(
(recyclerAdapter is ItemListAdapter && appSettingsService.isCardViewEnabled()) ||
(recyclerAdapter is ItemCardAdapter && !appSettingsService.isCardViewEnabled())
)
) {
if (appSettingsService.isCardViewEnabled()) { if (appSettingsService.isCardViewEnabled()) {
recyclerAdapter = recyclerAdapter =
ItemCardAdapter( ItemCardAdapter(
@ -599,7 +599,7 @@ class HomeActivity :
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.issue_tracker -> { R.id.issue_tracker -> {
baseContext.openUrlInBrowserAsNewTask(AppSettingsService.BUG_URL) baseContext.openUrlInBrowser(AppSettingsService.BUG_URL)
return true return true
} }

View File

@ -161,14 +161,12 @@ class ReaderActivity :
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
super.onPageSelected(position) super.onPageSelected(position)
if (!allItems.isNullOrEmpty() && allItems.size >= position) { if (allItems[position].starred) {
if (allItems[position].starred) { canRemoveFromFavorite()
canRemoveFromFavorite() } else {
} else { canFavorite()
canFavorite()
}
readItem(allItems[position])
} }
readItem(allItems[position])
} }
}, },
) )

View File

@ -118,13 +118,13 @@ class ItemCardAdapter(
binding.itemImage.setImageDrawable(null) binding.itemImage.setImageDrawable(null)
} else { } else {
binding.itemImage.visibility = View.VISIBLE binding.itemImage.visibility = View.VISIBLE
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage, appSettingsService) c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
} }
if (itm.getIcon(repository.baseUrl).isEmpty()) { if (itm.getIcon(repository.baseUrl).isEmpty()) {
binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded()) binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
} else { } else {
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage, appSettingsService) c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
} }
} }
} }

View File

@ -65,10 +65,10 @@ class ItemListAdapter(
if (itm.getIcon(repository.baseUrl).isEmpty()) { if (itm.getIcon(repository.baseUrl).isEmpty()) {
binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded()) binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
} else { } else {
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService) c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
} }
} else { } else {
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage, appSettingsService) c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage)
} }
} }
} }

View File

@ -16,7 +16,6 @@ import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBindi
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
import bou.amine.apps.readerforselfossv2.model.SelfossModel 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.utils.getHtmlDecoded import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon import bou.amine.apps.readerforselfossv2.utils.getIcon
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -37,7 +36,6 @@ class SourcesListAdapter(
override val di: DI by closestDI(app) override val di: DI by closestDI(app)
private val repository: Repository by instance() private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
@ -84,7 +82,7 @@ class SourcesListAdapter(
if (itm.getIcon(repository.baseUrl).isEmpty()) { if (itm.getIcon(repository.baseUrl).isEmpty()) {
binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded()) binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
} else { } else {
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService) c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
} }
if (!itm.error.isNullOrBlank()) { if (!itm.error.isNullOrBlank()) {

View File

@ -63,7 +63,7 @@ class LoadingWorker(
handleNewItemsNotification(apiItems, notificationManager) handleNewItemsNotification(apiItems, notificationManager)
} }
} }
apiItems.map { it.preloadImages(context, appSettingsService) } apiItems.map { it.preloadImages(context) }
} }
} }
return Result.success() return Result.success()

View File

@ -2,14 +2,18 @@ package bou.amine.apps.readerforselfossv2.android.fragments
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.TypedArray import android.content.res.TypedArray
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue
import android.util.TypedValue.DATA_NULL_UNDEFINED import android.util.TypedValue.DATA_NULL_UNDEFINED
import android.view.GestureDetector import android.view.GestureDetector
import android.view.InflateException import android.view.InflateException
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -19,6 +23,7 @@ import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import bou.amine.apps.readerforselfossv2.android.ImageActivity import bou.amine.apps.readerforselfossv2.android.ImageActivity
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
@ -27,15 +32,10 @@ import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem
import bou.amine.apps.readerforselfossv2.android.model.toModel import bou.amine.apps.readerforselfossv2.android.model.toModel
import bou.amine.apps.readerforselfossv2.android.model.toParcelable import bou.amine.apps.readerforselfossv2.android.model.toParcelable
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.addHomeMadeActionItem
import bou.amine.apps.readerforselfossv2.android.utils.getColorFromAttr
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapFitCenter
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.glide.getGlideImageForResource
import bou.amine.apps.readerforselfossv2.android.utils.isUrlValid import bou.amine.apps.readerforselfossv2.android.utils.isUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.maybeIfContext
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrlInBrowserAsNewTask import bou.amine.apps.readerforselfossv2.android.utils.openItemUrlInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
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.MercuryModel
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
@ -46,7 +46,11 @@ import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getImages import bou.amine.apps.readerforselfossv2.utils.getImages
import bou.amine.apps.readerforselfossv2.utils.getThumbnail import bou.amine.apps.readerforselfossv2.utils.getThumbnail
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
import com.leinardi.android.speeddial.SpeedDialView import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -61,8 +65,6 @@ import java.util.Locale
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
private const val IMAGE_JPG = "image/jpg" private const val IMAGE_JPG = "image/jpg"
private const val IMAGE_PNG = "image/png"
private const val IMAGE_WEBP = "image/webp"
private const val WHITE_COLOR_HEX = 0xFFFFFF private const val WHITE_COLOR_HEX = 0xFFFFFF
@ -71,17 +73,15 @@ private const val DEFAULT_FONT_SIZE = 16
class ArticleFragment : class ArticleFragment :
Fragment(), Fragment(),
DIAware { DIAware {
private var colorOnSurface: Int = 0
private var colorSurface: Int = 0
private var fontSize: Int = DEFAULT_FONT_SIZE private var fontSize: Int = DEFAULT_FONT_SIZE
private lateinit var item: SelfossModel.Item private lateinit var item: SelfossModel.Item
private var url: String? = null private lateinit var url: String
private lateinit var contentText: String private lateinit var contentText: String
private lateinit var contentSource: String private lateinit var contentSource: String
private lateinit var contentImage: String private lateinit var contentImage: String
private lateinit var contentTitle: String private lateinit var contentTitle: String
private lateinit var allImages: ArrayList<String> private lateinit var allImages: ArrayList<String>
private lateinit var fab: SpeedDialView private lateinit var fab: FloatingActionButton
private lateinit var textAlignment: String private lateinit var textAlignment: String
private lateinit var binding: FragmentArticleBinding private lateinit var binding: FragmentArticleBinding
@ -92,6 +92,7 @@ class ArticleFragment :
private var typeface: Typeface? = null private var typeface: Typeface? = null
private var resId: Int = 0 private var resId: Int = 0
private var font = "" private var font = ""
private var staticBar = false
private val mercuryApi: MercuryApi by instance() private val mercuryApi: MercuryApi by instance()
@ -118,9 +119,6 @@ class ArticleFragment :
e.sendSilentlyWithAcra() e.sendSilentlyWithAcra()
} }
colorOnSurface = getColorFromAttr(com.google.android.material.R.attr.colorOnSurface)
colorSurface = getColorFromAttr(com.google.android.material.R.attr.colorSurface)
contentText = item.content contentText = item.content
contentTitle = item.title.getHtmlDecoded() contentTitle = item.title.getHtmlDecoded()
contentImage = item.getThumbnail(repository.baseUrl) contentImage = item.getThumbnail(repository.baseUrl)
@ -134,11 +132,23 @@ class ArticleFragment :
allImages = item.getImages() allImages = item.getImages()
fontSize = appSettingsService.getFontSize() fontSize = appSettingsService.getFontSize()
staticBar = appSettingsService.isStaticBarEnabled()
font = appSettingsService.getFont() font = appSettingsService.getFont()
refreshAlignment() refreshAlignment()
handleFloatingToolbar() fab = binding.fab
fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
fab.rippleColor = resources.getColor(R.color.colorAccentDark)
val floatingToolbar: FloatingToolbar = handleFloatingToolbar()
if (staticBar) {
fab.hide()
floatingToolbar.show()
}
binding.source.text = contentSource binding.source.text = contentSource
if (typeface != null) { if (typeface != null) {
@ -146,13 +156,28 @@ class ArticleFragment :
} }
handleContent() 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) { } catch (e: InflateException) {
e.sendSilentlyWithAcraWithName("webview not available") e.sendSilentlyWithAcraWithName("webview not available")
maybeIfContext { try {
AlertDialog AlertDialog
.Builder(it) .Builder(requireContext())
.setMessage(it.getString(R.string.webview_dialog_issue_message)) .setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
.setTitle(it.getString(R.string.webview_dialog_issue_title)) .setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
.setPositiveButton( .setPositiveButton(
android.R.string.ok, android.R.string.ok,
) { _, _ -> ) { _, _ ->
@ -160,6 +185,8 @@ class ArticleFragment :
requireActivity().finish() requireActivity().finish()
}.create() }.create()
.show() .show()
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
} }
} }
@ -168,8 +195,8 @@ class ArticleFragment :
private fun handleContent() { private fun handleContent() {
if (contentText.isEmptyOrNullOrNullString()) { if (contentText.isEmptyOrNullOrNullString()) {
if (repository.isNetworkAvailable() && url.isUrlValid()) { if (repository.isNetworkAvailable()) {
getContentFromMercury(url!!) getContentFromMercury()
} }
} else { } else {
binding.titleView.text = contentTitle binding.titleView.text = contentTitle
@ -181,84 +208,72 @@ class ArticleFragment :
if (!contentImage.isEmptyOrNullOrNullString() && context != null) { if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
binding.imageView.visibility = View.VISIBLE binding.imageView.visibility = View.VISIBLE
maybeIfContext { it.bitmapFitCenter(contentImage, binding.imageView, appSettingsService) } Glide
.with(requireContext())
.asBitmap()
.load(contentImage)
.apply(RequestOptions.fitCenterTransform())
.into(binding.imageView)
} else { } else {
binding.imageView.visibility = View.GONE binding.imageView.visibility = View.GONE
} }
} }
} }
private fun handleFloatingToolbar() { private fun handleFloatingToolbar(): FloatingToolbar {
fab = binding.speedDial val floatingToolbar: FloatingToolbar = binding.floatingToolbar
fab.mainFabClosedIconColor = colorOnSurface if (appSettingsService.getPublicAccess()) {
fab.mainFabOpenedIconColor = colorOnSurface floatingToolbar.setMenu(R.menu.reader_toolbar_no_read)
maybeIfContext { handleFloatingToolbarActionItems(it) }
fab.setOnActionSelectedListener { actionItem ->
when (actionItem.id) {
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
R.id.unread_action ->
if (this@ArticleFragment.item.unread) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = false
maybeIfContext {
Toast
.makeText(
it,
R.string.marked_as_read,
Toast.LENGTH_LONG,
).show()
}
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = true
maybeIfContext {
Toast
.makeText(
it,
R.string.marked_as_unread,
Toast.LENGTH_LONG,
).show()
}
}
else -> Unit
}
false
} }
} floatingToolbar.attachFab(fab)
private fun handleFloatingToolbarActionItems(c: Context) { floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
fab.addHomeMadeActionItem(
R.id.share_action, floatingToolbar.setClickListener(
resources.getDrawable(R.drawable.ic_share_white_24dp), object : FloatingToolbar.ItemClickListener {
R.string.reader_action_share, override fun onItemClick(item: MenuItem) {
colorOnSurface, when (item.itemId) {
colorSurface, R.id.share_action -> requireActivity().shareLink(url, contentTitle)
c, R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
) R.id.unread_action ->
fab.addHomeMadeActionItem( try {
R.id.open_action, if (this@ArticleFragment.item.unread) {
resources.getDrawable(R.drawable.ic_open_in_browser_white_24dp), CoroutineScope(Dispatchers.IO).launch {
R.string.reader_action_open, repository.markAsRead(this@ArticleFragment.item)
colorOnSurface, }
colorSurface, this@ArticleFragment.item.unread = false
c, Toast
) .makeText(
fab.addHomeMadeActionItem( requireContext(),
R.id.unread_action, R.string.marked_as_read,
resources.getDrawable(R.drawable.ic_baseline_white_eye_24dp), Toast.LENGTH_LONG,
R.string.unmark, ).show()
colorOnSurface, } else {
colorSurface, CoroutineScope(Dispatchers.IO).launch {
c, repository.unmarkAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = true
Toast
.makeText(
context,
R.string.marked_as_unread,
Toast.LENGTH_LONG,
).show()
}
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
}
else -> Unit
}
}
override fun onItemLongClick(item: MenuItem?) {
// We do nothing
}
},
) )
return floatingToolbar
} }
private fun refreshAlignment() { private fun refreshAlignment() {
@ -271,7 +286,7 @@ class ArticleFragment :
} }
@Suppress("detekt:SwallowedException") @Suppress("detekt:SwallowedException")
private fun getContentFromMercury(url: String) { private fun getContentFromMercury() {
binding.progressBar.visibility = View.VISIBLE binding.progressBar.visibility = View.VISIBLE
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@ -310,11 +325,15 @@ class ArticleFragment :
} }
private fun handleLeadImage(leadImageUrl: String?) { private fun handleLeadImage(leadImageUrl: String?) {
if (!leadImageUrl.isNullOrEmpty()) { if (!leadImageUrl.isNullOrEmpty() && context != null) {
maybeIfContext { binding.imageView.visibility = View.VISIBLE
binding.imageView.visibility = View.VISIBLE Glide
it.bitmapFitCenter(leadImageUrl, binding.imageView, appSettingsService) .with(requireContext())
} .asBitmap()
.load(
leadImageUrl,
).apply(RequestOptions.fitCenterTransform())
.into(binding.imageView)
} else { } else {
binding.imageView.visibility = View.GONE binding.imageView.visibility = View.GONE
} }
@ -328,79 +347,138 @@ class ArticleFragment :
view: WebView?, view: WebView?,
url: String, url: String,
): Boolean = ): Boolean =
if (url.isUrlValid() && if (context != null &&
url.isUrlValid() &&
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
) { ) {
maybeIfContext { it.openUrlInBrowserAsNewTask(url) } requireContext().openUrlInBrowser(url)
true true
} else { } else {
false false
} }
@Suppress("detekt:SwallowedException", "detekt:ReturnCount") @Suppress("detekt:LongMethod", "detekt:SwallowedException")
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
override fun shouldInterceptRequest( override fun shouldInterceptRequest(
view: WebView, view: WebView,
url: String, url: String,
): WebResourceResponse? { ): WebResourceResponse? {
val (mime: String?, compression: Bitmap.CompressFormat) = val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
if (url var glideResource: WebResourceResponse? = null
.lowercase(Locale.US) if (url.lowercase(Locale.US).contains(".jpg") ||
.contains(".jpg") || url
url.lowercase(Locale.US).contains(".jpeg") .lowercase(Locale.US)
) { .contains(".jpeg")
Pair(IMAGE_JPG, Bitmap.CompressFormat.JPEG) ) {
} else if (url.lowercase(Locale.US).contains(".png")) { try {
Pair(IMAGE_PNG, Bitmap.CompressFormat.PNG) val image =
} else if (url.lowercase(Locale.US).contains(".webp")) { Glide
Pair(IMAGE_WEBP, Bitmap.CompressFormat.WEBP) .with(view)
} else { .asBitmap()
return super.shouldInterceptRequest(view, url) .apply(glideOptions)
.load(url)
.submit()
.get()
glideResource =
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()
glideResource =
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()
glideResource =
WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP),
)
} catch (e: ExecutionException) {
// Do nothing
} }
try {
val image = view.getGlideImageForResource(url, appSettingsService)
return WebResourceResponse(
mime,
"UTF-8",
getBitmapInputStream(image, compression),
)
} catch (e: ExecutionException) {
return super.shouldInterceptRequest(view, url)
} }
return glideResource ?: super.shouldInterceptRequest(view, url)
} }
} }
} }
@Suppress("detekt:LongMethod", "detekt:ImplicitDefaultLocale") @Suppress("detekt:LongMethod", "detekt:ImplicitDefaultLocale")
private fun htmlToWebview() { private fun htmlToWebview() {
maybeIfContext { val context: Context
try {
context = requireContext()
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
return
}
val colorOnSurface = TypedValue()
val colorSurface = TypedValue()
try {
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily) val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
val a: TypedArray = it.obtainStyledAttributes(resId, attrs) val a: TypedArray = context.obtainStyledAttributes(resId, attrs)
binding.webcontent.settings.standardFontFamily = a.getString(0) binding.webcontent.settings.standardFontFamily = a.getString(0)
"" binding.webcontent.visibility = View.VISIBLE
context.theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
context.theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context issue when setting attributes, but context wasn't null before")
} }
binding.webcontent.visibility = View.VISIBLE
val colorSurfaceString = val colorSurfaceString =
String.format( String.format(
"#%06X", "#%06X",
WHITE_COLOR_HEX and (if (colorSurface != DATA_NULL_UNDEFINED) colorSurface else WHITE_COLOR_HEX), WHITE_COLOR_HEX and (if (colorSurface.data != DATA_NULL_UNDEFINED) colorSurface.data else WHITE_COLOR_HEX),
) )
val colorOnSurfaceString = val colorOnSurfaceString =
String.format( String.format(
"#%06X", "#%06X",
WHITE_COLOR_HEX and (if (colorOnSurface != DATA_NULL_UNDEFINED) colorOnSurface else 0), WHITE_COLOR_HEX and (if (colorOnSurface.data != DATA_NULL_UNDEFINED) colorOnSurface.data else 0),
) )
binding.webcontent.settings.useWideViewPort = true
binding.webcontent.settings.loadWithOverviewMode = true
binding.webcontent.settings.javaScriptEnabled = false
handleImageLoading()
try { try {
binding.webcontent.settings.useWideViewPort = true
binding.webcontent.settings.loadWithOverviewMode = true
binding.webcontent.settings.javaScriptEnabled = false
handleImageLoading()
val gestureDetector = val gestureDetector =
GestureDetector( GestureDetector(
activity, activity,
@ -414,50 +492,49 @@ class ArticleFragment :
event, event,
) )
} }
binding.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Gesture detector issue ?") e.sendSilentlyWithAcraWithName("Context is null but wasn't, and that's causing issues with webview config")
return return
} }
binding.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
var baseUrl: String? = null
try { try {
val itemUrl = URL(url.orEmpty()) var baseUrl: String? = null
baseUrl = itemUrl.protocol + "://" + itemUrl.host try {
} catch (e: MalformedURLException) { val itemUrl = URL(url)
e.sendSilentlyWithAcraWithName("htmlToWebview > ${url.orEmpty()}") baseUrl = itemUrl.protocol + "://" + itemUrl.host
} } catch (e: MalformedURLException) {
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
}
val fontName: String = val fontName =
maybeIfContext {
when (font) { when (font) {
it.getString(R.string.open_sans_font_id) -> "Open Sans" getString(R.string.open_sans_font_id) -> "Open Sans"
it.getString(R.string.roboto_font_id) -> "Roboto" getString(R.string.roboto_font_id) -> "Roboto"
it.getString(R.string.source_code_pro_font_id) -> "Source Code Pro" getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
else -> "" else -> ""
} }
}?.toString().orEmpty()
val fontLinkAndStyle = val fontLinkAndStyle =
if (fontName.isNotEmpty()) { if (font.isNotEmpty()) {
"""<link href="https://fonts.googleapis.com/css?family=${ """<link href="https://fonts.googleapis.com/css?family=${
fontName.replace( fontName.replace(
" ", " ",
"+", "+",
) )
}" rel="stylesheet"> }" rel="stylesheet">
|<style> |<style>
| * { | * {
| font-family: '$fontName'; | font-family: '$fontName';
| } | }
|</style> |</style>
""".trimMargin() """.trimMargin()
} else { } else {
"" ""
} }
try {
binding.webcontent.loadDataWithBaseURL( binding.webcontent.loadDataWithBaseURL(
baseUrl, baseUrl,
"""<html> """<html>
@ -474,7 +551,7 @@ class ArticleFragment :
| color: ${ | color: ${
String.format( String.format(
"#%06X", "#%06X",
WHITE_COLOR_HEX and (maybeIfContext { it.resources.getColor(R.color.colorAccent) } as Int), WHITE_COLOR_HEX and context.resources.getColor(R.color.colorAccent),
) )
} !important; } !important;
| } | }
@ -531,8 +608,10 @@ class ArticleFragment :
private fun openInBrowserAfterFailing() { private fun openInBrowserAfterFailing() {
binding.progressBar.visibility = View.GONE binding.progressBar.visibility = View.GONE
maybeIfContext { try {
it.openItemUrlInBrowserAsNewTask(this@ArticleFragment.item) requireContext().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
} }
} }

View File

@ -1,5 +1,6 @@
package bou.amine.apps.readerforselfossv2.android.fragments package bou.amine.apps.readerforselfossv2.android.fragments
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
@ -15,13 +16,11 @@ 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.databinding.FilterFragmentBinding
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.glide.imageIntoViewTarget
import bou.amine.apps.readerforselfossv2.android.utils.maybeIfContext
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.utils.getColorHexCode import bou.amine.apps.readerforselfossv2.utils.getColorHexCode
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.request.target.ViewTarget import com.bumptech.glide.request.target.ViewTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@ -42,7 +41,6 @@ class FilterSheetFragment :
private lateinit var binding: FilterFragmentBinding 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()
private val appSettingsService: AppSettingsService by instance()
private var selectedChip: Chip? = null private var selectedChip: Chip? = null
@ -60,8 +58,8 @@ class FilterSheetFragment :
try { try {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
handleTagChips() handleTagChips(requireContext())
handleSourceChips() handleSourceChips(requireContext())
binding.progressBar2.visibility = GONE binding.progressBar2.visibility = GONE
binding.filterView.visibility = VISIBLE binding.filterView.visibility = VISIBLE
@ -79,24 +77,17 @@ class FilterSheetFragment :
return binding.root return binding.root
} }
private suspend fun handleSourceChips() { private suspend fun handleSourceChips(context: Context) {
val sourceGroup = binding.sourcesGroup val sourceGroup = binding.sourcesGroup
repository.getSourcesDetailsOrStats().forEachIndexed { _, source -> repository.getSourcesDetailsOrStats().forEachIndexed { _, source ->
val c: Chip? = val c = Chip(context)
maybeIfContext {
Chip(it)
} as Chip?
if (c == null) {
return
}
c.ellipsize = TextUtils.TruncateAt.END c.ellipsize = TextUtils.TruncateAt.END
maybeIfContext { Glide
it.imageIntoViewTarget( .with(context)
source.getIcon(repository.baseUrl), .load(source.getIcon(repository.baseUrl))
.into(
object : ViewTarget<Chip?, Drawable?>(c) { object : ViewTarget<Chip?, Drawable?>(c) {
override fun onResourceReady( override fun onResourceReady(
resource: Drawable, resource: Drawable,
@ -109,9 +100,7 @@ class FilterSheetFragment :
} }
} }
}, },
appSettingsService,
) )
}
c.text = source.title.getHtmlDecoded() c.text = source.title.getHtmlDecoded()
@ -147,17 +136,13 @@ class FilterSheetFragment :
} }
} }
private suspend fun handleTagChips() { private suspend fun handleTagChips(context: Context) {
val tagGroup = binding.tagsGroup val tagGroup = binding.tagsGroup
val tags = repository.getTags() val tags = repository.getTags()
tags.forEachIndexed { _, tag -> tags.forEachIndexed { _, tag ->
val c: Chip? = maybeIfContext { Chip(it) } as Chip? val c = Chip(context)
if (c == null) {
return
}
c.ellipsize = TextUtils.TruncateAt.END c.ellipsize = TextUtils.TruncateAt.END
c.text = tag.tag c.text = tag.tag

View File

@ -6,19 +6,13 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentImageBinding import bou.amine.apps.readerforselfossv2.android.databinding.FragmentImageBinding
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapWithCache import com.bumptech.glide.Glide
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import com.bumptech.glide.load.engine.DiskCacheStrategy
import org.kodein.di.DI import com.bumptech.glide.request.RequestOptions
import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.instance
class ImageFragment : class ImageFragment : Fragment() {
Fragment(),
DIAware {
override val di: DI by closestDI()
private val appSettingsService: AppSettingsService by instance()
private lateinit var imageUrl: String private lateinit var imageUrl: String
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
private var _binding: FragmentImageBinding? = null private var _binding: FragmentImageBinding? = null
val binding get() = _binding val binding get() = _binding
@ -37,7 +31,12 @@ class ImageFragment :
val view = binding?.root val view = binding?.root
binding!!.photoView.visibility = View.VISIBLE binding!!.photoView.visibility = View.VISIBLE
requireActivity().bitmapWithCache(imageUrl, binding!!.photoView, appSettingsService) Glide
.with(requireActivity())
.asBitmap()
.apply(glideOptions)
.load(imageUrl)
.into(binding!!.photoView)
return view return view
} }

View File

@ -3,21 +3,28 @@ package bou.amine.apps.readerforselfossv2.android.model
import android.content.Context import android.content.Context
import android.webkit.URLUtil import android.webkit.URLUtil
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.glide.preloadImage
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getImages import bou.amine.apps.readerforselfossv2.utils.getImages
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
fun SelfossModel.Item.preloadImages( private const val PRELOAD_IMAGE_TIMEOUT = 10000
context: Context,
appSettingsService: AppSettingsService, fun SelfossModel.Item.preloadImages(context: Context): Boolean {
): Boolean {
val imageUrls = this.getImages() val imageUrls = this.getImages()
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(PRELOAD_IMAGE_TIMEOUT)
try { try {
for (url in imageUrls) { for (url in imageUrls) {
if (URLUtil.isValidUrl(url)) { if (URLUtil.isValidUrl(url)) {
context.preloadImage(url, appSettingsService) Glide
.with(context)
.asBitmap()
.apply(glideOptions)
.load(url)
.submit()
} }
} }
} catch (e: Error) { } catch (e: Error) {

View File

@ -2,60 +2,24 @@ package bou.amine.apps.readerforselfossv2.android.utils
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.fragment.app.Fragment
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
fun Context.shareLink( fun Context.shareLink(
itemUrl: String?, itemUrl: String,
itemTitle: String, itemTitle: String,
) { ) {
if (itemUrl.isUrlValid()) { val sendIntent = Intent()
val sendIntent = Intent() sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK sendIntent.action = Intent.ACTION_SEND
sendIntent.action = Intent.ACTION_SEND sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl!!.toStringUriWithHttp()) sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle) sendIntent.type = "text/plain"
sendIntent.type = "text/plain" startActivity(
startActivity( Intent
Intent .createChooser(
.createChooser( sendIntent,
sendIntent, getString(R.string.share),
getString(R.string.share), ).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), )
)
}
}
@ColorInt
fun Fragment.getColorFromAttr(
@AttrRes attrColor: Int,
resolveRefs: Boolean = true,
): Int {
val typedValue = TypedValue()
maybeIfContextWithLog { this.requireContext().theme.resolveAttribute(attrColor, typedValue, resolveRefs) }
return typedValue.data
}
@Suppress("detekt:SwallowedException")
fun Fragment.maybeIfContext(fn: (Context) -> Any): Any? {
try {
return fn(this.requireContext())
} catch (e: Exception) {
// Do nothing
return null
}
}
fun Fragment.maybeIfContextWithLog(fn: (Context) -> Any): Any? {
try {
return fn(this.requireContext())
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("Fragment context issue...")
return null
}
} }

View File

@ -15,12 +15,12 @@ import android.widget.Toast
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.ReaderActivity import bou.amine.apps.readerforselfossv2.android.ReaderActivity
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
fun Context.openItemUrl( fun Context.openItemUrl(
currentItem: Int, currentItem: Int,
linkDecoded: String?, linkDecoded: String,
articleViewer: Boolean, articleViewer: Boolean,
app: Activity, app: Activity,
) { ) {
@ -37,13 +37,12 @@ fun Context.openItemUrl(
intent.putExtra("currentItem", currentItem) intent.putExtra("currentItem", currentItem)
app.startActivity(intent) app.startActivity(intent)
} else { } else {
this.openUrlInBrowserAsNewTask(linkDecoded!!) this.openUrlInBrowserAsNewTask(linkDecoded)
} }
} }
} }
fun String?.isUrlValid(): Boolean = fun String.isUrlValid(): Boolean = this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
!this.isEmptyOrNullOrNullString() && this!!.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
fun String.isBaseUrlInvalid(): Boolean { fun String.isBaseUrlInvalid(): Boolean {
val baseUrl = this.toHttpUrlOrNull() val baseUrl = this.toHttpUrlOrNull()
@ -57,16 +56,14 @@ fun String.isBaseUrlInvalid(): Boolean {
} }
fun Context.openItemUrlInBrowserAsNewTask(i: SelfossModel.Item) { fun Context.openItemUrlInBrowserAsNewTask(i: SelfossModel.Item) {
this.openUrlInBrowserAsNewTask(i.getLinkDecoded()) this.openUrlInBrowserAsNewTask(i.getLinkDecoded().toStringUriWithHttp())
} }
fun Context.openUrlInBrowserAsNewTask(url: String?) { fun Context.openUrlInBrowserAsNewTask(url: String) {
if (url.isUrlValid()) { val intent = Intent(Intent.ACTION_VIEW)
val intent = Intent(Intent.ACTION_VIEW) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.data = Uri.parse(url)
intent.data = Uri.parse(url) this.mayBeStartActivity(intent)
this.mayBeStartActivity(intent)
}
} }
fun Context.openUrlInBrowser(url: String) { fun Context.openUrlInBrowser(url: String) {

View File

@ -1,13 +1,6 @@
package bou.amine.apps.readerforselfossv2.android.utils.bottombar package bou.amine.apps.readerforselfossv2.android.utils.bottombar
import android.content.Context
import android.graphics.drawable.Drawable
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import bou.amine.apps.readerforselfossv2.android.R
import com.ashokvarma.bottomnavigation.TextBadgeItem import com.ashokvarma.bottomnavigation.TextBadgeItem
import com.leinardi.android.speeddial.SpeedDialActionItem
import com.leinardi.android.speeddial.SpeedDialView
fun TextBadgeItem.removeBadge(): TextBadgeItem { fun TextBadgeItem.removeBadge(): TextBadgeItem {
this.setText("") this.setText("")
@ -16,25 +9,3 @@ fun TextBadgeItem.removeBadge(): TextBadgeItem {
} }
fun TextBadgeItem.maybeShow(): TextBadgeItem = if (this.isHidden) this.show() else this fun TextBadgeItem.maybeShow(): TextBadgeItem = if (this.isHidden) this.show() else this
@Suppress("detekt:LongParameterList")
fun SpeedDialView.addHomeMadeActionItem(
@IdRes actionId: Int,
actionIcon: Drawable,
@StringRes labelId: Int,
colorOnSurface: Int,
colorSurface: Int,
context: Context,
) {
this.addActionItem(
SpeedDialActionItem
.Builder(actionId, actionIcon)
.setFabBackgroundColor(context.resources.getColor(R.color.colorAccent))
.setFabImageTintColor(colorOnSurface)
.setLabel(context.getString(labelId))
.setLabelClickable(false)
.setLabelBackgroundColor(colorOnSurface)
.setLabelColor(colorSurface)
.create(),
)
}

View File

@ -2,124 +2,33 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.webkit.WebView
import android.widget.ImageView import android.widget.ImageView
import bou.amine.apps.readerforselfossv2.android.utils.CircleImageView import bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.ViewTarget
import com.google.android.material.chip.Chip
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
private const val PRELOAD_IMAGE_TIMEOUT = 10000
@Suppress("detekt:ReturnCount")
@OptIn(ExperimentalEncodingApi::class)
fun String.toGlideUrl(appSettingsService: AppSettingsService): Any { // GlideUrl Or String
if (this.isEmptyOrNullOrNullString()) {
return ""
}
if (appSettingsService.getBasicUserName().isNotEmpty()) {
val authString = "${appSettingsService.getBasicUserName()}:${appSettingsService.getBasicPassword()}"
val authBuf = Base64.encode(authString.toByteArray(Charsets.UTF_8))
return GlideUrl(
this,
LazyHeaders
.Builder()
.addHeader("Authorization", "Basic $authBuf")
.build(),
)
} else {
return GlideUrl(
this,
)
}
}
fun WebView.getGlideImageForResource(
url: String,
appSettingsService: AppSettingsService,
) = Glide
.with(this)
.asBitmap()
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
.load(url.toGlideUrl(appSettingsService))
.submit()
.get()
fun Context.preloadImage(
url: String,
appSettingsService: AppSettingsService,
) = Glide
.with(this)
.asBitmap()
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(PRELOAD_IMAGE_TIMEOUT))
.load(url.toGlideUrl(appSettingsService))
.submit()
fun Context.imageIntoViewTarget(
url: String,
target: ViewTarget<Chip?, Drawable?>,
appSettingsService: AppSettingsService,
) = Glide
.with(this)
.load(url.toGlideUrl(appSettingsService))
.into(target)
fun Context.bitmapWithCache(
url: String,
iv: ImageView,
appSettingsService: AppSettingsService,
) = Glide
.with(this)
.asBitmap()
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
.load(url.toGlideUrl(appSettingsService))
.into(iv)
fun Context.bitmapCenterCrop( fun Context.bitmapCenterCrop(
url: String, url: String,
iv: ImageView, iv: ImageView,
appSettingsService: AppSettingsService,
) = Glide ) = Glide
.with(this) .with(this)
.asBitmap() .asBitmap()
.load(url.toGlideUrl(appSettingsService)) .load(url)
.apply(RequestOptions.centerCropTransform()) .apply(RequestOptions.centerCropTransform())
.into(iv) .into(iv)
fun Context.bitmapFitCenter(
url: String,
iv: ImageView,
appSettingsService: AppSettingsService,
) = Glide
.with(this)
.asBitmap()
.load(url.toGlideUrl(appSettingsService))
.apply(RequestOptions.fitCenterTransform())
.into(iv)
fun Context.circularDrawable( fun Context.circularDrawable(
url: String, url: String,
view: CircleImageView, view: CircleImageView,
appSettingsService: AppSettingsService,
) { ) {
view.textView.text = "" view.textView.text = ""
Glide Glide
.with(this) .with(this)
.load(url.toGlideUrl(appSettingsService)) .load(url)
.into(view.imageView) .into(view.imageView)
} }

View File

@ -71,13 +71,35 @@
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
<com.leinardi.android.speeddial.SpeedDialView <FrameLayout
android:id="@+id/speedDial" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="start|bottom|end"
app:layout_behavior="@string/speeddial_scrolling_view_snackbar_behavior" app:layout_constraintBottom_toBottomOf="parent"
app:sdMainFabClosedSrc="@drawable/ic_add_white_24dp" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.github.rubensousa.floatingtoolbar.FloatingToolbar
android:id="@+id/floatingToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
app:floatingMenu="@menu/reader_toolbar" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:src="@drawable/ic_add_white_24dp"
app:backgroundTint="?attr/colorAccent"
app:fabSize="mini"
app:rippleColor="?attr/colorAccentDark" />
</FrameLayout>
<FrameLayout <FrameLayout
android:id="@+id/progressBar" android:id="@+id/progressBar"
@ -97,5 +119,4 @@
android:progressTint="?attr/colorAccent" /> android:progressTint="?attr/colorAccent" />
</FrameLayout> </FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/unread_action"
android:icon="@drawable/ic_baseline_white_eye_24dp"
android:title="@string/unmark"
app:showAsAction="ifRoom" />
<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>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string> <string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string> <string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string> <string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -129,4 +132,4 @@
<string name="action_about">"Quant a"</string> <string name="action_about">"Quant a"</string>
<string name="marked_as_read">"Element llegit"</string> <string name="marked_as_read">"Element llegit"</string>
<string name="marked_as_unread">"Item unread"</string> <string name="marked_as_unread">"Item unread"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Linksbündig</string> <string name="reader_text_align_left">Linksbündig</string>
<string name="reader_text_align_justify">Blocksatz</string> <string name="reader_text_align_justify">Blocksatz</string>
<string name="settings_reader_font">Schriftgröße im Lesemodus</string> <string name="settings_reader_font">Schriftgröße im Lesemodus</string>
<string name="reader_static_bar_title">Statische untere Leiste im Lesemodus</string>
<string name="reader_static_bar_on">Die untere Leiste wird dauerhaft angezeigt</string>
<string name="reader_static_bar_off">Die untere Leiste kann über einen schwebenden Button angezeigt werden</string>
<string name="remove_source">Quelle entfernen</string> <string name="remove_source">Quelle entfernen</string>
<string name="pref_theme_title">Heller/Dunkler Modus</string> <string name="pref_theme_title">Heller/Dunkler Modus</string>
<string name="mode_dark">Dunkler Modus</string> <string name="mode_dark">Dunkler Modus</string>
@ -129,4 +132,4 @@
<string name="action_about">"Über"</string> <string name="action_about">"Über"</string>
<string name="marked_as_read">"Artikel gelesen"</string> <string name="marked_as_read">"Artikel gelesen"</string>
<string name="marked_as_unread">"Item unread"</string> <string name="marked_as_unread">"Item unread"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Alinear a la izquierda</string> <string name="reader_text_align_left">Alinear a la izquierda</string>
<string name="reader_text_align_justify">Justificado</string> <string name="reader_text_align_justify">Justificado</string>
<string name="settings_reader_font">Modo lectura</string> <string name="settings_reader_font">Modo lectura</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -129,4 +132,4 @@
<string name="action_about">"Acerca de"</string> <string name="action_about">"Acerca de"</string>
<string name="marked_as_read">"Artículo leído"</string> <string name="marked_as_read">"Artículo leído"</string>
<string name="marked_as_unread">"Artículo no leído"</string> <string name="marked_as_unread">"Artículo no leído"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Aligner à gauche</string> <string name="reader_text_align_left">Aligner à gauche</string>
<string name="reader_text_align_justify">Justifier le texte</string> <string name="reader_text_align_justify">Justifier le texte</string>
<string name="settings_reader_font">Police du lecteur d\'articles</string> <string name="settings_reader_font">Police du lecteur d\'articles</string>
<string name="reader_static_bar_title">Barre statique pour le visionneur d\'articles</string>
<string name="reader_static_bar_on">La barre sera affichée</string>
<string name="reader_static_bar_off">La barre sera affichée grâce au bouton</string>
<string name="remove_source">Supprimer la source</string> <string name="remove_source">Supprimer la source</string>
<string name="pref_theme_title">Thème Clair/Sombre</string> <string name="pref_theme_title">Thème Clair/Sombre</string>
<string name="mode_dark">Thème sombre</string> <string name="mode_dark">Thème sombre</string>
@ -129,4 +132,4 @@
<string name="action_about">"À propos"</string> <string name="action_about">"À propos"</string>
<string name="marked_as_read">"Marqué comme lu"</string> <string name="marked_as_read">"Marqué comme lu"</string>
<string name="marked_as_unread">"Marqué comme non lu"</string> <string name="marked_as_unread">"Marqué comme non lu"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Aliñar á esquerda</string> <string name="reader_text_align_left">Aliñar á esquerda</string>
<string name="reader_text_align_justify">Xustificado</string> <string name="reader_text_align_justify">Xustificado</string>
<string name="settings_reader_font">Modo lector</string> <string name="settings_reader_font">Modo lector</string>
<string name="reader_static_bar_title">Barra inferior estática na vista de artigos</string>
<string name="reader_static_bar_on">A barra inferior mostrarase sempre</string>
<string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string>
<string name="remove_source">Eliminar fonte</string> <string name="remove_source">Eliminar fonte</string>
<string name="pref_theme_title">Modo Claro/Escuro</string> <string name="pref_theme_title">Modo Claro/Escuro</string>
<string name="mode_dark">Modo escuro</string> <string name="mode_dark">Modo escuro</string>
@ -129,4 +132,4 @@
<string name="action_about">"Acerca de"</string> <string name="action_about">"Acerca de"</string>
<string name="marked_as_read">"Elemento lido"</string> <string name="marked_as_read">"Elemento lido"</string>
<string name="marked_as_unread">"Elemento non lido"</string> <string name="marked_as_unread">"Elemento non lido"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string> <string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string> <string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string> <string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -129,4 +132,4 @@
<string name="action_about">"Tentang"</string> <string name="action_about">"Tentang"</string>
<string name="marked_as_read">"Membaca item"</string> <string name="marked_as_read">"Membaca item"</string>
<string name="marked_as_unread">"Item unread"</string> <string name="marked_as_unread">"Item unread"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string> <string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string> <string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string> <string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -129,4 +132,4 @@
<string name="action_about">"Informazioni"</string> <string name="action_about">"Informazioni"</string>
<string name="marked_as_read">"Articolo letto"</string> <string name="marked_as_read">"Articolo letto"</string>
<string name="marked_as_unread">"Item unread"</string> <string name="marked_as_unread">"Item unread"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string> <string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string> <string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string> <string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -129,4 +132,4 @@
<string name="action_about">"정보"</string> <string name="action_about">"정보"</string>
<string name="marked_as_read">"항목 읽기"</string> <string name="marked_as_read">"항목 읽기"</string>
<string name="marked_as_unread">"Item unread"</string> <string name="marked_as_unread">"Item unread"</string>
</resources> </resources>

View File

@ -23,7 +23,6 @@
<string name="wrong_infos">"Controleer de gegevens nogmaals."</string> <string name="wrong_infos">"Controleer de gegevens nogmaals."</string>
<string name="all_posts_not_read">"Fout bij markeren als gelezen"</string> <string name="all_posts_not_read">"Fout bij markeren als gelezen"</string>
<string name="all_posts_read">"Alle artikelen gemarkeerd als gelezen"</string> <string name="all_posts_read">"Alle artikelen gemarkeerd als gelezen"</string>
<string name="undo_string">"Ongedaan maken"</string>
<string name="addStringNoUrl">"Login om bronnen toe te voegen"</string> <string name="addStringNoUrl">"Login om bronnen toe te voegen"</string>
<string name="cant_get_sources">"Kan de lijst met bronnen niet ophalen"</string> <string name="cant_get_sources">"Kan de lijst met bronnen niet ophalen"</string>
<string name="cant_create_source">"Kan bron niet creëeren"</string> <string name="cant_create_source">"Kan bron niet creëeren"</string>
@ -106,6 +105,9 @@
<string name="reader_text_align_left">Align left</string> <string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string> <string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string> <string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -129,4 +131,5 @@
<string name="action_about">"Over"</string> <string name="action_about">"Over"</string>
<string name="marked_as_read">"Artikel gelezen"</string> <string name="marked_as_read">"Artikel gelezen"</string>
<string name="marked_as_unread">"Item unread"</string> <string name="marked_as_unread">"Item unread"</string>
</resources> <string name="undo_string">"Ongedaan maken"</string>
</resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string> <string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string> <string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string> <string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -129,4 +132,4 @@
<string name="action_about">"Sobre"</string> <string name="action_about">"Sobre"</string>
<string name="marked_as_read">"Item lido"</string> <string name="marked_as_read">"Item lido"</string>
<string name="marked_as_unread">"Item unread"</string> <string name="marked_as_unread">"Item unread"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string> <string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string> <string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string> <string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -129,4 +132,4 @@
<string name="action_about">"Sobre"</string> <string name="action_about">"Sobre"</string>
<string name="marked_as_read">"Item lido"</string> <string name="marked_as_read">"Item lido"</string>
<string name="marked_as_unread">"Item unread"</string> <string name="marked_as_unread">"Item unread"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string> <string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string> <string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string> <string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -129,4 +132,4 @@
<string name="action_about">"මේ ගැන"</string> <string name="action_about">"මේ ගැන"</string>
<string name="marked_as_read">"Item read"</string> <string name="marked_as_read">"Item read"</string>
<string name="marked_as_unread">"Item unread"</string> <string name="marked_as_unread">"Item unread"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string> <string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string> <string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string> <string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -129,4 +132,4 @@
<string name="action_about">"Hakkında"</string> <string name="action_about">"Hakkında"</string>
<string name="marked_as_read">"Öğeleri oku"</string> <string name="marked_as_read">"Öğeleri oku"</string>
<string name="marked_as_unread">"Item unread"</string> <string name="marked_as_unread">"Item unread"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">左对齐</string> <string name="reader_text_align_left">左对齐</string>
<string name="reader_text_align_justify">左右对齐</string> <string name="reader_text_align_justify">左右对齐</string>
<string name="settings_reader_font">阅读器字体</string> <string name="settings_reader_font">阅读器字体</string>
<string name="reader_static_bar_title">文章查看器中的静态底部栏</string>
<string name="reader_static_bar_on">底部栏将始终显示</string>
<string name="reader_static_bar_off">底部栏可以通过浮动按钮显示</string>
<string name="remove_source">删除源</string> <string name="remove_source">删除源</string>
<string name="pref_theme_title">浅色/深色模式</string> <string name="pref_theme_title">浅色/深色模式</string>
<string name="mode_dark">深色模式</string> <string name="mode_dark">深色模式</string>
@ -129,4 +132,4 @@
<string name="action_about">"关于我们"</string> <string name="action_about">"关于我们"</string>
<string name="marked_as_read">"已读"</string> <string name="marked_as_read">"已读"</string>
<string name="marked_as_unread">"未读条目"</string> <string name="marked_as_unread">"未读条目"</string>
</resources> </resources>

View File

@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string> <string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string> <string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string> <string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -129,4 +132,4 @@
<string name="action_about">"关于我们"</string> <string name="action_about">"关于我们"</string>
<string name="marked_as_read">"已读"</string> <string name="marked_as_read">"已读"</string>
<string name="marked_as_unread">"未讀項目"</string> <string name="marked_as_unread">"未讀項目"</string>
</resources> </resources>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="unread_action" type="id" />
<item name="open_action" type="id" />
<item name="share_action" type="id" />
</resources>

View File

@ -108,6 +108,9 @@
<string name="source_code_pro_font_id" translatable="false">source_code_pro_medium</string> <string name="source_code_pro_font_id" translatable="false">source_code_pro_medium</string>
<string name="open_sans_font_id" translatable="false">open_sans</string> <string name="open_sans_font_id" translatable="false">open_sans</string>
<string name="roboto_font_id" translatable="false">roboto</string> <string name="roboto_font_id" translatable="false">roboto</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
@ -131,4 +134,4 @@
<string name="action_about">"About"</string> <string name="action_about">"About"</string>
<string name="marked_as_read">"Item read"</string> <string name="marked_as_read">"Item read"</string>
<string name="marked_as_unread">"Item unread"</string> <string name="marked_as_unread">"Item unread"</string>
</resources> </resources>

View File

@ -30,6 +30,14 @@
android:summaryOn="@string/pref_article_viewer_on" android:summaryOn="@string/pref_article_viewer_on"
android:title="@string/pref_article_viewer_title" android:title="@string/pref_article_viewer_title"
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="false"
android:dependency="prefer_article_viewer"
android:key="reader_static_bar"
android:summaryOff="@string/reader_static_bar_off"
android:summaryOn="@string/reader_static_bar_on"
android:title="@string/reader_static_bar_title"
app:iconSpaceReserved="false"/>
<PreferenceCategory <PreferenceCategory
android:title="@string/pref_general_category_displaying"> android:title="@string/pref_general_category_displaying">

View File

@ -1,7 +1,7 @@
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("8.8.1").apply(false) id("com.android.application").version("8.7.3").apply(false)
id("com.android.library").version("8.8.1").apply(false) id("com.android.library").version("8.7.3").apply(false)
id("org.jetbrains.kotlin.android").version("2.1.0").apply(false) id("org.jetbrains.kotlin.android").version("2.1.0").apply(false)
kotlin("multiplatform").version("2.1.0").apply(false) kotlin("multiplatform").version("2.1.0").apply(false)
id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false) id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
@ -16,6 +16,7 @@ allprojects {
} }
} }
tasks.register("clean", Delete::class) { tasks.register("clean", Delete::class) {
delete(layout.buildDirectory) delete(layout.buildDirectory)
} }
@ -23,4 +24,4 @@ tasks.register("clean", Delete::class) {
dependencies { dependencies {
kover(project(":shared")) kover(project(":shared"))
kover(project(":androidApp")) kover(project(":androidApp"))
} }

View File

@ -1,4 +0,0 @@
**v125010111**
- Debug trying to fix context issues. (#174)
- Changelog for v125010031

View File

@ -1,5 +0,0 @@
**v125010131**
- fix: reload the adapter when it's needed. Fixes #128. (#176)
- feat: basic auth and images loading. Fixes #172. (#175)
- Changelog for v125010111

View File

@ -1,6 +0,0 @@
**v125010201**
- fix: Handle empty url issue.
- Merge pull request 'Removed the floating bar.' (#177) from floating-bar into master
- chore: changing actions in reader fragment.
- Changelog for v125010131

View File

@ -1,8 +0,0 @@
**v125010241**
- Merge pull request 'fix: Link not opening.' (#178) from fix-open-link into master
- refactor: context fragments issues.
- logs: Context issues.
- fix: Handle empty url issue, again.
- fix: Link not opening.
- Changelog for v125010201

View File

@ -1,7 +0,0 @@
**v125020411**
- Merge pull request 'bump' (#182) from bump into master
- chore: non transiant R classes.
- Merge pull request 'fix: One more missing context.' (#181) from fix-one-more-context into master
- bump
- fix: One more missing context.

View File

@ -1,7 +0,0 @@
**v125020471**
- chore: no more docker-compose.
- bump: gradle plugin.
- Merge pull request 'fix: check index exists.' (#183) from fix-index into master
- fix: check index exists.
- Changelog for v125020411

View File

@ -18,12 +18,12 @@ kotlin.code.style=official
#Android #Android
android.useAndroidX=true android.useAndroidX=true
#android.nonTransitiveRClass=true #android.nonTransitiveRClass=true
android.enableJetifier=false android.enableJetifier=true
android.nonTransitiveRClass=true android.nonTransitiveRClass=false
#MPP #MPP
kotlin.mpp.enableCInteropCommonization=true kotlin.mpp.enableCInteropCommonization=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.caching=true org.gradle.caching=true
ignoreGitVersion=false ignoreGitVersion=false
kotlin.native.cacheKind.iosX64=none kotlin.native.cacheKind.iosX64=none
org.gradle.configureondemand=true org.gradle.configureondemand=true

View File

@ -1,6 +1,6 @@
#Sun Feb 09 14:44:52 CET 2025 #Mon Nov 25 22:48:24 CET 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -127,8 +127,8 @@ class SelfossModel {
val tags: List<String>, val tags: List<String>,
val author: String? = null, val author: String? = null,
) { ) {
fun getLinkDecoded(): String? { fun getLinkDecoded(): String {
var stringUrl: String? var stringUrl: String
stringUrl = stringUrl =
if (link.contains("//news.google.com/news/") && link.contains("&amp;url=")) { if (link.contains("//news.google.com/news/") && link.contains("&amp;url=")) {
link.substringAfter("&amp;url=") link.substringAfter("&amp;url=")
@ -146,7 +146,11 @@ class SelfossModel {
stringUrl = "http:$stringUrl" stringUrl = "http:$stringUrl"
} }
return if (stringUrl.isEmptyOrNullOrNullString()) null else stringUrl if (stringUrl.isEmptyOrNullOrNullString()) {
throw ModelException("Link $link was translated to $stringUrl, but was empty. Handle this.")
}
return stringUrl
} }
fun sourceAuthorAndDate(): String { fun sourceAuthorAndDate(): String {

View File

@ -53,6 +53,7 @@ class AppSettingsService(
private var activeAlignment: Int? = null private var activeAlignment: Int? = null
private var fontSize: Int? = null private var fontSize: Int? = null
private var staticBar: Boolean? = null
private var font: String = "" private var font: String = ""
private var theme: Int? = null private var theme: Int? = null
@ -385,6 +386,17 @@ class AppSettingsService(
return fontSize ?: DEFAULT_FONT_SIZE return fontSize ?: DEFAULT_FONT_SIZE
} }
private fun refreshStaticBarEnabled() {
staticBar = settings.getBoolean(READER_STATIC_BAR, false)
}
fun isStaticBarEnabled(): Boolean {
if (staticBar != null) {
refreshStaticBarEnabled()
}
return staticBar == true
}
private fun refreshFont() { private fun refreshFont() {
font = settings.getString(READER_FONT, "") font = settings.getString(READER_FONT, "")
} }
@ -437,6 +449,7 @@ class AppSettingsService(
refreshActiveAllignment() refreshActiveAllignment()
refreshFontSize() refreshFontSize()
refreshFont() refreshFont()
refreshStaticBarEnabled()
refreshCurrentTheme() refreshCurrentTheme()
} }
@ -534,6 +547,8 @@ class AppSettingsService(
const val READER_FONT = "reader_font" const val READER_FONT = "reader_font"
const val READER_STATIC_BAR = "reader_static_bar"
const val READER_FONT_SIZE = "reader_font_size" const val READER_FONT_SIZE = "reader_font_size"
const val TEXT_ALIGN = "text_align" const val TEXT_ALIGN = "text_align"