Compare commits

...

21 Commits

Author SHA1 Message Date
a90ccec707 fix: url can be empty ?
All checks were successful
Check master code / build (push) Successful in 14m37s
Create tag / build (push) Successful in 7m25s
Create tag / createTagAndChangelog (push) Successful in 44s
Create tag / release (push) Successful in 5m12s
2025-02-27 21:40:06 +01:00
2564b19726 Changelog for v125020471 2025-02-16 14:43:28 +00:00
61c7bb20cc chore: no more docker-compose.
All checks were successful
Create tag / build (push) Successful in 21m1s
Create tag / createTagAndChangelog (push) Successful in 1m21s
Create tag / release (push) Successful in 9m3s
Check master code / build (push) Successful in 34m35s
2025-02-16 15:17:51 +01:00
6a0f5baf0a bump: gradle plugin.
Some checks failed
Check master code / build (push) Has been cancelled
2025-02-16 14:57:34 +01:00
39f9505c00 Merge pull request 'fix: check index exists.' (#183) from fix-index into master
All checks were successful
Check master code / build (push) Successful in 8m3s
Reviewed-on: #183
2025-02-16 13:37:42 +00:00
6a6d447456 fix: check index exists.
All checks were successful
Check PR code / Lint (pull_request) Successful in 3m57s
Check PR code / build (pull_request) Successful in 13m44s
2025-02-16 13:57:42 +01:00
0bb4fe6aed Changelog for v125020411 2025-02-10 20:16:56 +00:00
7df4c3368c Merge pull request 'bump' (#182) from bump into master
All checks were successful
Check master code / build (push) Successful in 11m6s
Create tag / build (push) Successful in 7m35s
Create tag / createTagAndChangelog (push) Successful in 44s
Create tag / release (push) Successful in 4m35s
Reviewed-on: #182
2025-02-10 19:35:40 +00:00
c69635b5ae chore: non transiant R classes.
All checks were successful
Check PR code / Lint (pull_request) Successful in 3m36s
Check PR code / build (pull_request) Successful in 16m13s
2025-02-09 22:27:53 +01:00
3a829df70e Merge pull request 'fix: One more missing context.' (#181) from fix-one-more-context into master
All checks were successful
Check master code / build (push) Successful in 13m26s
Reviewed-on: #181
2025-02-09 20:44:29 +00:00
7a0202689f bump
All checks were successful
Check PR code / Lint (pull_request) Successful in 4m42s
Check PR code / build (pull_request) Successful in 16m9s
2025-02-09 21:44:02 +01:00
b20f6888f5 fix: One more missing context.
All checks were successful
Check PR code / Lint (pull_request) Successful in 1m1s
Check PR code / build (pull_request) Successful in 15m1s
2025-02-09 14:42:17 +01:00
6b96eb358d Merge pull request 'chore: more context issues.' (#180) from context-again into master
All checks were successful
Check master code / build (push) Successful in 7m35s
Create tag / build (push) Successful in 6m45s
Create tag / createTagAndChangelog (push) Successful in 42s
Create tag / release (push) Successful in 5m7s
Reviewed-on: #180
2025-01-29 14:50:22 +00:00
dfc1bf9fa3 chore: more context issues.
All checks were successful
Check PR code / Lint (pull_request) Successful in 2m33s
Check PR code / build (pull_request) Successful in 11m27s
2025-01-29 13:43:35 +01:00
b173664ff0 Changelog for v125010241
All checks were successful
Check master code / build (push) Successful in 9m50s
2025-01-24 22:06:19 +00:00
bc20a421ae Merge pull request 'fix: Link not opening.' (#178) from fix-open-link into master
All checks were successful
Check master code / build (push) Successful in 7m47s
Create tag / build (push) Successful in 7m14s
Create tag / createTagAndChangelog (push) Successful in 44s
Create tag / release (push) Successful in 5m13s
Reviewed-on: #178
2025-01-24 21:48:14 +00:00
794500355a refactor: context fragments issues.
All checks were successful
Check PR code / Lint (pull_request) Successful in 3m9s
Check PR code / build (pull_request) Successful in 12m15s
2025-01-24 21:44:20 +01:00
44f9dd53d3 logs: Context issues. 2025-01-24 21:10:01 +01:00
717d6b664c fix: Handle empty url issue, again. 2025-01-24 21:04:15 +01:00
e23289a3dc fix: Link not opening.
Some checks failed
Check PR code / Lint (pull_request) Failing after 7m7s
Check PR code / build (pull_request) Has been skipped
2025-01-24 20:56:04 +01:00
2f5ebe2420 Changelog for v125010201
All checks were successful
Check master code / build (push) Successful in 13m45s
2025-01-20 07:41:09 +00:00
20 changed files with 276 additions and 181 deletions

View File

@ -26,9 +26,10 @@ 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"
- name: run selfoss # TESTS ARE RUN LOCALLY
run: | # - name: run selfoss
docker compose -f .gitea/workflows/assets/docker-compose.yml up -d # run: |
# docker compose -f .gitea/workflows/assets/docker-compose.yml up -d
- name: coverage - name: coverage
run: | run: |
./gradlew :koverHtmlReport ./gradlew :koverHtmlReport
@ -39,7 +40,8 @@ jobs:
retention-days: 1 retention-days: 1
overwrite: true overwrite: true
include-hidden-files: true include-hidden-files: true
- name: Clean # TESTS ARE RUN LOCALLY
if: always() # - name: Clean
run: | # if: always()
docker compose -f .gitea/workflows/assets/docker-compose.yml stop # run: |
# docker compose -f .gitea/workflows/assets/docker-compose.yml stop

View File

@ -16,6 +16,7 @@ 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
@ -50,7 +51,7 @@ jobs:
followtags: true followtags: true
ssh_key: ${{ secrets.PRIVATE_KEY }} ssh_key: ${{ secrets.PRIVATE_KEY }}
tags: true tags: true
branch: release branch: master
- 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:
@ -124,4 +125,4 @@ jobs:
priority: high priority: high
convert_markdown: true convert_markdown: true
body: Nouveau fichier de mapping pour la version ${{ steps.version.outputs.VERSION }} body: Nouveau fichier de mapping pour la version ${{ steps.version.outputs.VERSION }}
attachments: androidApp/build/outputs/mapping/githubConfigRelease/mapping.txt attachments: androidApp/build/outputs/mapping/githubConfigRelease/mapping.txt

4
.gitignore vendored
View File

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

View File

@ -1,3 +1,43 @@
**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 **v125010131
- fix: reload the adapter when it's needed. Fixes #128. (#176) - fix: reload the adapter when it's needed. Fixes #128. (#176)

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(R.id.search_src_text), withId(com.google.android.material.R.id.search_src_text),
).check(matches(isFocused())) ).check(matches(isFocused()))
onView(isRoot()).perform(ViewActions.pressBack()) onView(isRoot()).perform(ViewActions.pressBack())

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.openUrlInBrowser import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask
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
@ -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.openUrlInBrowser(AppSettingsService.BUG_URL) baseContext.openUrlInBrowserAsNewTask(AppSettingsService.BUG_URL)
return true return true
} }

View File

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

View File

@ -33,8 +33,9 @@ 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.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.openUrlInBrowser import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask
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
@ -74,7 +75,7 @@ class ArticleFragment :
private var colorSurface: 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 lateinit var url: String private var url: String? = null
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
@ -117,8 +118,8 @@ class ArticleFragment :
e.sendSilentlyWithAcra() e.sendSilentlyWithAcra()
} }
colorOnSurface = requireContext().getColorFromAttr(R.attr.colorOnSurface) colorOnSurface = getColorFromAttr(com.google.android.material.R.attr.colorOnSurface)
colorSurface = requireContext().getColorFromAttr(R.attr.colorSurface) colorSurface = getColorFromAttr(com.google.android.material.R.attr.colorSurface)
contentText = item.content contentText = item.content
contentTitle = item.title.getHtmlDecoded() contentTitle = item.title.getHtmlDecoded()
@ -147,11 +148,11 @@ class ArticleFragment :
handleContent() handleContent()
} catch (e: InflateException) { } catch (e: InflateException) {
e.sendSilentlyWithAcraWithName("webview not available") e.sendSilentlyWithAcraWithName("webview not available")
try { maybeIfContext {
AlertDialog AlertDialog
.Builder(requireContext()) .Builder(it)
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message)) .setMessage(it.getString(R.string.webview_dialog_issue_message))
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title)) .setTitle(it.getString(R.string.webview_dialog_issue_title))
.setPositiveButton( .setPositiveButton(
android.R.string.ok, android.R.string.ok,
) { _, _ -> ) { _, _ ->
@ -159,8 +160,6 @@ class ArticleFragment :
requireActivity().finish() requireActivity().finish()
}.create() }.create()
.show() .show()
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
} }
} }
@ -169,8 +168,8 @@ class ArticleFragment :
private fun handleContent() { private fun handleContent() {
if (contentText.isEmptyOrNullOrNullString()) { if (contentText.isEmptyOrNullOrNullString()) {
if (repository.isNetworkAvailable()) { if (repository.isNetworkAvailable() && url.isUrlValid()) {
getContentFromMercury() getContentFromMercury(url!!)
} }
} else { } else {
binding.titleView.text = contentTitle binding.titleView.text = contentTitle
@ -182,7 +181,7 @@ class ArticleFragment :
if (!contentImage.isEmptyOrNullOrNullString() && context != null) { if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
binding.imageView.visibility = View.VISIBLE binding.imageView.visibility = View.VISIBLE
requireContext().bitmapFitCenter(contentImage, binding.imageView, appSettingsService) maybeIfContext { it.bitmapFitCenter(contentImage, binding.imageView, appSettingsService) }
} else { } else {
binding.imageView.visibility = View.GONE binding.imageView.visibility = View.GONE
} }
@ -194,39 +193,39 @@ class ArticleFragment :
fab.mainFabClosedIconColor = colorOnSurface fab.mainFabClosedIconColor = colorOnSurface
fab.mainFabOpenedIconColor = colorOnSurface fab.mainFabOpenedIconColor = colorOnSurface
handleFloatingToolbarActionItems() maybeIfContext { handleFloatingToolbarActionItems(it) }
fab.setOnActionSelectedListener { actionItem -> fab.setOnActionSelectedListener { actionItem ->
when (actionItem.id) { when (actionItem.id) {
R.id.share_action -> requireActivity().shareLink(url, contentTitle) R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item) R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
R.id.unread_action -> R.id.unread_action ->
try { if (this@ArticleFragment.item.unread) {
if (this@ArticleFragment.item.unread) { CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch { repository.markAsRead(this@ArticleFragment.item)
repository.markAsRead(this@ArticleFragment.item) }
} this@ArticleFragment.item.unread = false
this@ArticleFragment.item.unread = false maybeIfContext {
Toast Toast
.makeText( .makeText(
requireContext(), it,
R.string.marked_as_read, R.string.marked_as_read,
Toast.LENGTH_LONG, Toast.LENGTH_LONG,
).show() ).show()
} else { }
CoroutineScope(Dispatchers.IO).launch { } else {
repository.unmarkAsRead(this@ArticleFragment.item) CoroutineScope(Dispatchers.IO).launch {
} repository.unmarkAsRead(this@ArticleFragment.item)
this@ArticleFragment.item.unread = true }
this@ArticleFragment.item.unread = true
maybeIfContext {
Toast Toast
.makeText( .makeText(
context, it,
R.string.marked_as_unread, R.string.marked_as_unread,
Toast.LENGTH_LONG, Toast.LENGTH_LONG,
).show() ).show()
} }
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
} }
else -> Unit else -> Unit
@ -235,14 +234,14 @@ class ArticleFragment :
} }
} }
private fun handleFloatingToolbarActionItems() { private fun handleFloatingToolbarActionItems(c: Context) {
fab.addHomeMadeActionItem( fab.addHomeMadeActionItem(
R.id.share_action, R.id.share_action,
resources.getDrawable(R.drawable.ic_share_white_24dp), resources.getDrawable(R.drawable.ic_share_white_24dp),
R.string.reader_action_share, R.string.reader_action_share,
colorOnSurface, colorOnSurface,
colorSurface, colorSurface,
requireContext(), c,
) )
fab.addHomeMadeActionItem( fab.addHomeMadeActionItem(
R.id.open_action, R.id.open_action,
@ -250,7 +249,7 @@ class ArticleFragment :
R.string.reader_action_open, R.string.reader_action_open,
colorOnSurface, colorOnSurface,
colorSurface, colorSurface,
requireContext(), c,
) )
fab.addHomeMadeActionItem( fab.addHomeMadeActionItem(
R.id.unread_action, R.id.unread_action,
@ -258,7 +257,7 @@ class ArticleFragment :
R.string.unmark, R.string.unmark,
colorOnSurface, colorOnSurface,
colorSurface, colorSurface,
requireContext(), c,
) )
} }
@ -272,7 +271,7 @@ class ArticleFragment :
} }
@Suppress("detekt:SwallowedException") @Suppress("detekt:SwallowedException")
private fun getContentFromMercury() { private fun getContentFromMercury(url: String) {
binding.progressBar.visibility = View.VISIBLE binding.progressBar.visibility = View.VISIBLE
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@ -311,9 +310,11 @@ class ArticleFragment :
} }
private fun handleLeadImage(leadImageUrl: String?) { private fun handleLeadImage(leadImageUrl: String?) {
if (!leadImageUrl.isNullOrEmpty() && context != null) { if (!leadImageUrl.isNullOrEmpty()) {
binding.imageView.visibility = View.VISIBLE maybeIfContext {
requireContext().bitmapFitCenter(leadImageUrl, binding.imageView, appSettingsService) binding.imageView.visibility = View.VISIBLE
it.bitmapFitCenter(leadImageUrl, binding.imageView, appSettingsService)
}
} else { } else {
binding.imageView.visibility = View.GONE binding.imageView.visibility = View.GONE
} }
@ -327,11 +328,10 @@ class ArticleFragment :
view: WebView?, view: WebView?,
url: String, url: String,
): Boolean = ): Boolean =
if (context != null && if (url.isUrlValid() &&
url.isUrlValid() &&
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
) { ) {
requireContext().openUrlInBrowser(url) maybeIfContext { it.openUrlInBrowserAsNewTask(url) }
true true
} else { } else {
false false
@ -374,23 +374,14 @@ class ArticleFragment :
@Suppress("detekt:LongMethod", "detekt:ImplicitDefaultLocale") @Suppress("detekt:LongMethod", "detekt:ImplicitDefaultLocale")
private fun htmlToWebview() { private fun htmlToWebview() {
val context: Context maybeIfContext {
try {
context = requireContext()
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
return
}
try {
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily) val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
val a: TypedArray = context.obtainStyledAttributes(resId, attrs) val a: TypedArray = it.obtainStyledAttributes(resId, attrs)
binding.webcontent.settings.standardFontFamily = a.getString(0) binding.webcontent.settings.standardFontFamily = a.getString(0)
binding.webcontent.visibility = View.VISIBLE ""
} 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(
@ -404,13 +395,12 @@ class ArticleFragment :
WHITE_COLOR_HEX and (if (colorOnSurface != DATA_NULL_UNDEFINED) colorOnSurface else 0), WHITE_COLOR_HEX and (if (colorOnSurface != DATA_NULL_UNDEFINED) colorOnSurface 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,
@ -424,49 +414,50 @@ class ArticleFragment :
event, event,
) )
} }
binding.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context is null but wasn't, and that's causing issues with webview config") e.sendSilentlyWithAcraWithName("Gesture detector issue ?")
return return
} }
try { binding.webcontent.settings.layoutAlgorithm =
var baseUrl: String? = null WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
try {
val itemUrl = URL(url)
baseUrl = itemUrl.protocol + "://" + itemUrl.host
} catch (e: MalformedURLException) {
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
}
val fontName = var baseUrl: String? = null
try {
val itemUrl = URL(url.orEmpty())
baseUrl = itemUrl.protocol + "://" + itemUrl.host
} catch (e: MalformedURLException) {
e.sendSilentlyWithAcraWithName("htmlToWebview > ${url.orEmpty()}")
}
val fontName: String =
maybeIfContext {
when (font) { when (font) {
getString(R.string.open_sans_font_id) -> "Open Sans" it.getString(R.string.open_sans_font_id) -> "Open Sans"
getString(R.string.roboto_font_id) -> "Roboto" it.getString(R.string.roboto_font_id) -> "Roboto"
getString(R.string.source_code_pro_font_id) -> "Source Code Pro" it.getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
else -> "" else -> ""
} }
}?.toString().orEmpty()
val fontLinkAndStyle = val fontLinkAndStyle =
if (font.isNotEmpty()) { if (fontName.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>
@ -483,7 +474,7 @@ class ArticleFragment :
| color: ${ | color: ${
String.format( String.format(
"#%06X", "#%06X",
WHITE_COLOR_HEX and context.resources.getColor(R.color.colorAccent), WHITE_COLOR_HEX and (maybeIfContext { it.resources.getColor(R.color.colorAccent) } as Int),
) )
} !important; } !important;
| } | }
@ -540,10 +531,8 @@ class ArticleFragment :
private fun openInBrowserAfterFailing() { private fun openInBrowserAfterFailing() {
binding.progressBar.visibility = View.GONE binding.progressBar.visibility = View.GONE
try { maybeIfContext {
requireContext().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item) it.openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
} }
} }

View File

@ -1,6 +1,5 @@
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
@ -17,6 +16,7 @@ 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.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.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getColorHexCode import bou.amine.apps.readerforselfossv2.utils.getColorHexCode
@ -60,8 +60,8 @@ class FilterSheetFragment :
try { try {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
handleTagChips(requireContext()) handleTagChips()
handleSourceChips(requireContext()) handleSourceChips()
binding.progressBar2.visibility = GONE binding.progressBar2.visibility = GONE
binding.filterView.visibility = VISIBLE binding.filterView.visibility = VISIBLE
@ -79,29 +79,39 @@ class FilterSheetFragment :
return binding.root return binding.root
} }
private suspend fun handleSourceChips(context: Context) { private suspend fun handleSourceChips() {
val sourceGroup = binding.sourcesGroup val sourceGroup = binding.sourcesGroup
repository.getSourcesDetailsOrStats().forEachIndexed { _, source -> repository.getSourcesDetailsOrStats().forEachIndexed { _, source ->
val c = Chip(context) val c: Chip? =
maybeIfContext {
Chip(it)
} as Chip?
if (c == null) {
return
}
c.ellipsize = TextUtils.TruncateAt.END c.ellipsize = TextUtils.TruncateAt.END
context.imageIntoViewTarget( maybeIfContext {
source.getIcon(repository.baseUrl), it.imageIntoViewTarget(
object : ViewTarget<Chip?, Drawable?>(c) { source.getIcon(repository.baseUrl),
override fun onResourceReady( object : ViewTarget<Chip?, Drawable?>(c) {
resource: Drawable, override fun onResourceReady(
transition: Transition<in Drawable?>?, resource: Drawable,
) { transition: Transition<in Drawable?>?,
try { ) {
c.chipIcon = resource try {
} catch (e: Exception) { c.chipIcon = resource
e.sendSilentlyWithAcraWithName("sources > onResourceReady") } catch (e: Exception) {
e.sendSilentlyWithAcraWithName("sources > onResourceReady")
}
} }
} },
}, appSettingsService,
appSettingsService, )
) }
c.text = source.title.getHtmlDecoded() c.text = source.title.getHtmlDecoded()
@ -137,13 +147,17 @@ class FilterSheetFragment :
} }
} }
private suspend fun handleTagChips(context: Context) { private suspend fun handleTagChips() {
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(context) val c: Chip? = maybeIfContext { Chip(it) } as Chip?
if (c == null) {
return
}
c.ellipsize = TextUtils.TruncateAt.END c.ellipsize = TextUtils.TruncateAt.END
c.text = tag.tag c.text = tag.tag

View File

@ -5,39 +5,57 @@ import android.content.Intent
import android.util.TypedValue import android.util.TypedValue
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.ColorInt 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.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,
) { ) {
val sendIntent = Intent() if (itemUrl.isUrlValid()) {
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK val sendIntent = Intent()
sendIntent.action = Intent.ACTION_SEND sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp()) sendIntent.action = Intent.ACTION_SEND
sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle) sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl!!.toStringUriWithHttp())
sendIntent.type = "text/plain" sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
startActivity( sendIntent.type = "text/plain"
Intent startActivity(
.createChooser( Intent
sendIntent, .createChooser(
getString(R.string.share), sendIntent,
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), getString(R.string.share),
) ).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
)
}
} }
@ColorInt @ColorInt
fun Context.getColorFromAttr( fun Fragment.getColorFromAttr(
@AttrRes attrColor: Int, @AttrRes attrColor: Int,
resolveRefs: Boolean = true, resolveRefs: Boolean = true,
): Int { ): Int {
val typedValue = TypedValue() val typedValue = TypedValue()
try { maybeIfContextWithLog { this.requireContext().theme.resolveAttribute(attrColor, typedValue, resolveRefs) }
this.theme.resolveAttribute(attrColor, typedValue, resolveRefs)
} catch (e: Throwable) {
e.sendSilentlyWithAcraWithName("ColorFromAttr")
}
return typedValue.data 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.toStringUriWithHttp import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
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,12 +37,13 @@ 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 = this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches() fun String?.isUrlValid(): Boolean =
!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()
@ -56,14 +57,16 @@ fun String.isBaseUrlInvalid(): Boolean {
} }
fun Context.openItemUrlInBrowserAsNewTask(i: SelfossModel.Item) { fun Context.openItemUrlInBrowserAsNewTask(i: SelfossModel.Item) {
this.openUrlInBrowserAsNewTask(i.getLinkDecoded().toStringUriWithHttp()) this.openUrlInBrowserAsNewTask(i.getLinkDecoded())
} }
fun Context.openUrlInBrowserAsNewTask(url: String) { fun Context.openUrlInBrowserAsNewTask(url: String?) {
val intent = Intent(Intent.ACTION_VIEW) if (url.isUrlValid()) {
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
this.mayBeStartActivity(intent) intent.data = Uri.parse(url)
this.mayBeStartActivity(intent)
}
} }
fun Context.openUrlInBrowser(url: String) { fun Context.openUrlInBrowser(url: String) {

View File

@ -23,10 +23,11 @@ import kotlin.io.encoding.ExperimentalEncodingApi
private const val PRELOAD_IMAGE_TIMEOUT = 10000 private const val PRELOAD_IMAGE_TIMEOUT = 10000
@Suppress("detekt:ReturnCount")
@OptIn(ExperimentalEncodingApi::class) @OptIn(ExperimentalEncodingApi::class)
fun String.toGlideUrl(appSettingsService: AppSettingsService): GlideUrl { fun String.toGlideUrl(appSettingsService: AppSettingsService): Any { // GlideUrl Or String
if (this.isEmptyOrNullOrNullString()) { if (this.isEmptyOrNullOrNullString()) {
return GlideUrl("") return ""
} }
if (appSettingsService.getBasicUserName().isNotEmpty()) { if (appSettingsService.getBasicUserName().isNotEmpty()) {
val authString = "${appSettingsService.getBasicUserName()}:${appSettingsService.getBasicPassword()}" val authString = "${appSettingsService.getBasicUserName()}:${appSettingsService.getBasicPassword()}"

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

View File

@ -0,0 +1,6 @@
**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

@ -0,0 +1,8 @@
**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

@ -0,0 +1,7 @@
**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

@ -0,0 +1,7 @@
**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

@ -19,11 +19,11 @@ kotlin.code.style=official
android.useAndroidX=true android.useAndroidX=true
#android.nonTransitiveRClass=true #android.nonTransitiveRClass=true
android.enableJetifier=false android.enableJetifier=false
android.nonTransitiveRClass=false android.nonTransitiveRClass=true
#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 @@
#Mon Nov 25 22:48:24 CET 2024 #Sun Feb 09 14:44:52 CET 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-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,11 +146,7 @@ class SelfossModel {
stringUrl = "http:$stringUrl" stringUrl = "http:$stringUrl"
} }
if (stringUrl.isEmptyOrNullOrNullString()) { return if (stringUrl.isEmptyOrNullOrNullString()) null else stringUrl
throw ModelException("Link $link was translated to $stringUrl, but was empty. Handle this.")
}
return stringUrl
} }
fun sourceAuthorAndDate(): String { fun sourceAuthorAndDate(): String {