Compare commits

..
260 Commits
Author SHA1 Message Date
AmineB a602f7adf2 fix: Connectivity toast message was causing issues.
PR / translations (pull_request) Successful in 1m47s
PR / PR (pull_request) Successful in 2m10s
PR / build (pull_request) Successful in 17m42s
PR test / integrationTests (pull_request) Successful in 43m23s
Master / build (push) Successful in 10m45s
Realease / build (push) Successful in 4m37s
Realease / createTagAndChangelog (push) Successful in 44s
Realease / release (push) Successful in 7m4s
2025-04-08 20:31:29 +02:00
giteadrone cf6ef51edf Changelog for v125030901 2025-03-31 18:44:08 +00:00
AmineB 0b7f832573 Merge pull request 'fix-reload' (#195) from fix-reload into master
Master / build (push) Successful in 11m29s
Realease / build (push) Successful in 5m52s
Realease / createTagAndChangelog (push) Successful in 45s
Realease / release (push) Successful in 7m23s
Reviewed-on: #195
2025-03-31 18:25:33 +00:00
AmineB 22c966bf16 fix: Infinite scroll needs loading stats.
PR / translations (pull_request) Successful in 31s
PR / PR (pull_request) Successful in 41s
PR / build (pull_request) Successful in 16m2s
PR test / integrationTests (pull_request) Successful in 43m44s
2025-03-30 21:57:36 +02:00
AmineB bdf2bb8b31 fix: do not reload items on resume. 2025-03-30 21:57:36 +02:00
AmineB ceae91206d Merge pull request 'tests' (#193) from tests into master
Master / build (push) Successful in 12m5s
Reviewed-on: #193
2025-03-30 19:50:50 +00:00
AmineB 11c0e744dc ci: Instrumentation tests coverage in ci.
PR / PR (pull_request) Successful in 1m1s
PR / translations (pull_request) Successful in 1m0s
PR / build (pull_request) Successful in 21m57s
PR test / integrationTests (pull_request) Successful in 50m51s
2025-03-30 20:23:15 +02:00
AmineB 7374e95b0e ci: Instrumentation tests coverage in ci.
Check PR code / translations (pull_request) Successful in 1m5s
Check PR code / Lint (pull_request) Successful in 1m8s
Check PR code / build (pull_request) Successful in 18m22s
Check PR code / RunIntegrationTests (pull_request) Has been cancelled
2025-03-27 18:44:44 +01:00
AmineB 8a7743a6fb ci: Instrumentation tests coverage in ci.
Check PR code / BuildAndTestAndCoverage (pull_request) Successful in 41m45s
2025-03-26 19:05:48 +01:00
AmineB 1b2e9edc8c chore: better handling of coroutine dispatchers.
Check PR code / BuildAndTestAndCoverage (pull_request) Successful in 30m26s
2025-03-25 12:42:43 +01:00
AmineB 7c65a63315 ci: Instrumentation tests coverage in ci.
Check PR code / BuildAndTestAndCoverage (pull_request) Successful in 34m46s
2025-03-24 23:09:51 +01:00
AmineB 02d503e03a chore: comment robolectric tests for now. 2025-03-16 15:44:02 +01:00
AmineB 24b9320d6d fix: Fixed source deletion test. 2025-03-16 14:27:30 +01:00
AmineB ceba58e98f Merge pull request 'Fix alignment changes resetting reader article position' (#190) from davidoskky/ReaderForSelfoss-multiplatform:alignment into master
Check master code / build (push) Successful in 8m27s
Reviewed-on: #190
2025-03-16 13:14:44 +00:00
davidoskky c3ee07dd85 Refactor star icon handling
Check PR code / translations (pull_request) Successful in 1m4s
Check PR code / Lint (pull_request) Successful in 1m25s
Check PR code / build (pull_request) Successful in 11m52s
Extracted all favorite handling to two functions. Makes it a little bit more readable.
2025-03-12 16:07:49 +01:00
davidoskky 93d99192b3 Don't restart activity changing alignment
When changing alignment in the reader we were restarting the reader activity to reload. Doing this led to reloading the article which was initially opened every time you changed alignment. Now, when changing the alignment we retain all existing fragments but command all of them to update their alignment setting.
2025-03-12 16:07:49 +01:00
giteadrone 359dec2ca0 Changelog for v125030711 2025-03-12 11:39:57 +00:00
AmineB 62354ec70a Merge pull request 'fix: initial status loading issues.' (#192) from connectivity into master
Check master code / build (push) Successful in 18m28s
Create tag / build (push) Successful in 18m21s
Create tag / createTagAndChangelog (push) Successful in 53s
Create tag / release (push) Successful in 4m19s
Reviewed-on: #192
2025-03-12 11:20:14 +00:00
AmineB 18a17251ac chore: check changes for translations and android.
Check PR code / translations (pull_request) Successful in 2m30s
Check PR code / Lint (pull_request) Successful in 2m36s
Check PR code / build (pull_request) Successful in 16m14s
2025-03-11 22:20:07 +01:00
AmineB 5e91724ee2 fix: initial status loading issues.
Check PR code / Lint (pull_request) Successful in 3m56s
Check PR code / translations (pull_request) Successful in 1m32s
Check PR code / build (pull_request) Has been cancelled
2025-03-11 22:04:42 +01:00
AmineB 212d259a33 Merge pull request 'chore: new connectivity dep. Closes #84.' (#189) from connectivity into master
Check master code / build (push) Successful in 14m19s
Reviewed-on: #189
2025-03-11 13:48:57 +00:00
AmineB 3bf60f1146 chore: new connectivity dep. Closes #84.
Check PR code / Lint (pull_request) Successful in 1m2s
Check PR code / translations (pull_request) Successful in 1m24s
Check PR code / build (pull_request) Successful in 14m10s
2025-03-10 21:11:39 +01:00
giteadrone ef13e300f0 Changelog for v125030681 2025-03-09 17:41:13 +00:00
AmineB f170d1157d chore: do not send reports on simulators.
Check master code / build (push) Successful in 13m14s
Create tag / build (push) Successful in 7m34s
Create tag / createTagAndChangelog (push) Successful in 43s
Create tag / release (push) Successful in 5m15s
2025-03-09 18:17:41 +01:00
AmineB af4752f0f0 Merge pull request 'chore: do not send reports on simulators.' (#188) from chore-acra-simulator into master
Check master code / build (push) Has been cancelled
Reviewed-on: #188
2025-03-09 17:11:28 +00:00
AmineB f0fa1a17b6 chore: do not send reports on simulators.
Check PR code / build (pull_request) Has been cancelled
Check PR code / Lint (pull_request) Has been cancelled
Check PR code / translations (pull_request) Has been cancelled
2025-03-09 18:07:14 +01:00
AmineB bb84d1541c Merge pull request 'fix: Url validation was not failing login. Added tests.' (#186) from fix-invalid-url into master
Check master code / build (push) Successful in 8m39s
Create tag / createTagAndChangelog (push) Has been cancelled
Create tag / release (push) Has been cancelled
Create tag / build (push) Has been cancelled
Reviewed-on: #186
2025-03-09 16:54:18 +00:00
AmineB c9227b2c1c Merge pull request 'chore: crowding ci integration.' (#187) from chore-crowdin-ci into master
Check master code / build (push) Has been cancelled
Reviewed-on: #187
2025-03-09 16:54:02 +00:00
AmineB 6eaad0c7c5 chore: we don't need to check if the url is valid in upsert screen.
Check PR code / Lint (pull_request) Successful in 4m19s
Check PR code / translations (pull_request) Successful in 2m2s
Check PR code / build (pull_request) Successful in 15m54s
2025-03-09 16:24:38 +01:00
AmineB a1c98aa7d0 fix: Url validation was not failing login. Added tests. 2025-03-09 16:24:38 +01:00
AmineB d5ec118679 chore: crowding ci integration.
Check PR code / Lint (pull_request) Successful in 3m20s
Check PR code / translations (pull_request) Successful in 3m44s
Check PR code / build (pull_request) Successful in 16m24s
2025-03-09 16:19:59 +01:00
davidoskkyandAmineB a1c0241a58 Show a confirmation dialog before deleting sources (#185)
Check master code / build (push) Successful in 15m3s
## Types of changes

- [ x ] I have read the **CONTRIBUTING** document.
- [ x ] My code follows the code style of this project.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ x ] All new and existing tests passed.
- [ x ] This is **NOT** translation related.

This is implements feature #156

I added a confirmation dialogue which pops up after tapping the delete source button. The popup displays the full name of the source to be deleted and allows the user to decide not to delete the source to prevent erroneous deletions.

I moved most of the logic into the viewholder. Can be easily reverted if you prefer.

All tests pass. I tested correct behavior in emulated versions of android API 25, 34 and 35.

Co-authored-by: Amine <amine.bouabdallaoui@pm.me>
Reviewed-on: #185
Co-authored-by: davidoskky <davidoskky@yahoo.it>
Co-committed-by: davidoskky <davidoskky@yahoo.it>
2025-03-09 13:49:32 +00:00
giteadrone f38936f9b4 Changelog for v125020581 2025-02-27 21:08:25 +00:00
AmineB a90ccec707 fix: url can be empty ?
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
giteadrone 2564b19726 Changelog for v125020471 2025-02-16 14:43:28 +00:00
AmineB 61c7bb20cc chore: no more docker-compose.
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
AmineB 6a0f5baf0a bump: gradle plugin.
Check master code / build (push) Has been cancelled
2025-02-16 14:57:34 +01:00
AmineB 39f9505c00 Merge pull request 'fix: check index exists.' (#183) from fix-index into master
Check master code / build (push) Successful in 8m3s
Reviewed-on: #183
2025-02-16 13:37:42 +00:00
AmineB 6a6d447456 fix: check index exists.
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
giteadrone 0bb4fe6aed Changelog for v125020411 2025-02-10 20:16:56 +00:00
AmineB 7df4c3368c Merge pull request 'bump' (#182) from bump into master
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
AmineB c69635b5ae chore: non transiant R classes.
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
AmineB 3a829df70e Merge pull request 'fix: One more missing context.' (#181) from fix-one-more-context into master
Check master code / build (push) Successful in 13m26s
Reviewed-on: #181
2025-02-09 20:44:29 +00:00
AmineB 7a0202689f bump
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
AmineB b20f6888f5 fix: One more missing context.
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
AmineB 6b96eb358d Merge pull request 'chore: more context issues.' (#180) from context-again into master
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
AmineB dfc1bf9fa3 chore: more context issues.
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
giteadrone b173664ff0 Changelog for v125010241
Check master code / build (push) Successful in 9m50s
2025-01-24 22:06:19 +00:00
AmineB bc20a421ae Merge pull request 'fix: Link not opening.' (#178) from fix-open-link into master
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
AmineB 794500355a refactor: context fragments issues.
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
AmineB 44f9dd53d3 logs: Context issues. 2025-01-24 21:10:01 +01:00
AmineB 717d6b664c fix: Handle empty url issue, again. 2025-01-24 21:04:15 +01:00
AmineB e23289a3dc fix: Link not opening.
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
giteadrone 2f5ebe2420 Changelog for v125010201
Check master code / build (push) Successful in 13m45s
2025-01-20 07:41:09 +00:00
AmineB 1893904135 fix: Handle empty url issue.
Check master code / build (push) Successful in 10m53s
Create tag / build (push) Successful in 7m40s
Create tag / createTagAndChangelog (push) Successful in 38s
Create tag / release (push) Successful in 5m3s
2025-01-19 14:49:37 +01:00
AmineB a4cb28ba81 Merge pull request 'Removed the floating bar.' (#177) from floating-bar into master
Check master code / build (push) Successful in 11m40s
Reviewed-on: #177
2025-01-19 13:35:18 +00:00
AmineB ae3cada1c7 chore: changing actions in reader fragment.
Check PR code / Lint (pull_request) Successful in 3m21s
Check PR code / build (pull_request) Successful in 11m38s
2025-01-19 12:54:25 +00:00
giteadrone 309500276f Changelog for v125010131
Check master code / build (push) Successful in 8m58s
2025-01-13 16:19:05 +00:00
AmineB ce255b23cd fix: reload the adapter when it's needed. Fixes #128. (#176)
Check master code / build (push) Successful in 10m27s
Create tag / build (push) Successful in 7m41s
Create tag / createTagAndChangelog (push) Successful in 48s
Create tag / release (push) Successful in 5m28s
## Types of changes

- [ ] I have read the **CONTRIBUTING** document.
- [ ] My code follows the code style of this project.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] This is **NOT** translation related.

This closes issue #XXX

This is implements feature #YYY

This finishes chore #ZZZ

Reviewed-on: #176
Co-authored-by: Amine <amine.bouabdallaoui@pm.me>
Co-committed-by: Amine <amine.bouabdallaoui@pm.me>
2025-01-13 15:08:39 +00:00
AmineB 3b3a575dae feat: basic auth and images loading. Fixes #172. (#175)
Check master code / build (push) Successful in 9m58s
Check PR code / Lint (pull_request) Successful in 3m15s
Check PR code / build (pull_request) Successful in 10m34s
## Types of changes

- [ ] I have read the **CONTRIBUTING** document.
- [ ] My code follows the code style of this project.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] This is **NOT** translation related.

This closes issue #XXX

This is implements feature #YYY

This finishes chore #ZZZ

Reviewed-on: #175
Co-authored-by: Amine <amine.bouabdallaoui@pm.me>
Co-committed-by: Amine <amine.bouabdallaoui@pm.me>
2025-01-13 07:39:13 +00:00
giteadrone 7bcf4574b4 Changelog for v125010111
Check master code / build (push) Successful in 9m15s
2025-01-11 20:54:28 +00:00
AmineB c79ab5e92b Debug trying to fix context issues. (#174)
Check master code / build (push) Successful in 7m44s
Create tag / build (push) Successful in 6m27s
Create tag / createTagAndChangelog (push) Successful in 37s
Create tag / release (push) Successful in 4m27s
Reviewed-on: #174
Co-authored-by: Amine <amine.bouabdallaoui@pm.me>
Co-committed-by: Amine <amine.bouabdallaoui@pm.me>
2025-01-11 20:35:27 +00:00
giteadrone 54dbda76ab Changelog for v125010031
Check master code / build (push) Successful in 15m56s
2025-01-03 09:09:18 +00:00
AmineB 11c39ae87c Merge pull request 'Bump dependencies' (#173) from upgarde into master
Check master code / build (push) Successful in 19m42s
Create tag / build (push) Successful in 27m4s
Create tag / createTagAndChangelog (push) Successful in 52s
Create tag / release (push) Successful in 7m41s
Reviewed-on: #173
2025-01-03 08:41:15 +00:00
AmineB 6645902ec8 chore: "faster" action.
Check PR code / Lint (pull_request) Successful in 5m59s
Check PR code / build (pull_request) Successful in 14m16s
2025-01-03 09:19:12 +01:00
AmineB 0a07a5dfad fastlane: icon change.
Check PR code / Lint (pull_request) Has been cancelled
Check PR code / build (pull_request) Has been cancelled
2025-01-03 09:18:06 +01:00
AmineB d88d38fd3b chore: ignoring a pixel issue.
Check PR code / Lint (pull_request) Successful in 9m15s
Check PR code / build (pull_request) Successful in 23m30s
2025-01-02 20:53:00 +01:00
AmineB 28fe38aa17 test: fixed an ui test issue.
Check PR code / Lint (pull_request) Successful in 11m23s
Check PR code / build (pull_request) Successful in 19m41s
2025-01-02 20:32:53 +01:00
AmineB d524c30732 fix: center the loading thing.
Check PR code / Lint (pull_request) Successful in 5m35s
Check PR code / build (pull_request) Successful in 18m15s
2024-12-31 15:31:47 +01:00
AmineB 8c00aa65da test: items displaying.
Check PR code / Lint (pull_request) Successful in 9m8s
Check PR code / build (pull_request) Successful in 36m8s
2024-12-31 15:23:26 +01:00
AmineB ae81261cb1 bump: sqldelight.
Check PR code / Lint (pull_request) Successful in 1m47s
Check PR code / build (pull_request) Successful in 15m9s
2024-12-31 12:37:36 +01:00
AmineB 03c567ee33 bump: material, desugar jdk, jsoup, kodein, settings, napier, mock.
Check PR code / Lint (pull_request) Successful in 1m37s
Check PR code / build (pull_request) Successful in 13m29s
2024-12-31 11:45:11 +01:00
AmineB d23dd82fc2 bump: androix and coroutines.
Check PR code / Lint (pull_request) Successful in 1m50s
Check PR code / build (pull_request) Successful in 13m29s
2024-12-31 11:06:36 +01:00
AmineB 2e7a168424 bump: ktor. Closes #67.
Check PR code / Lint (pull_request) Successful in 47s
Check PR code / build (pull_request) Successful in 15m22s
2024-12-31 10:03:21 +01:00
giteadrone 5bc2f614af Changelog for v124123651
Check master code / build (push) Successful in 12m31s
2024-12-30 22:54:15 +00:00
AmineB 934c112db5 Merge pull request 'Bugfixes' (#171) from bugfixes into master
Check master code / build (push) Successful in 19m51s
Create tag / build (push) Successful in 21m4s
Create tag / createTagAndChangelog (push) Successful in 50s
Create tag / release (push) Successful in 3m39s
Reviewed-on: #171
2024-12-30 22:32:11 +00:00
AmineB ad7549a89f config: crowdin
Check PR code / Lint (pull_request) Successful in 54s
Check PR code / build (pull_request) Successful in 10m35s
2024-12-30 23:20:03 +01:00
AmineB fb9ceecabd chore: can links be empty ?
Check PR code / Lint (pull_request) Successful in 1m3s
Check PR code / build (pull_request) Successful in 11m14s
2024-12-30 22:45:18 +01:00
AmineB 61b9fd30e0 fix: Context issues in article fragment. 2024-12-30 22:37:36 +01:00
AmineB 806e56e20b fix: Context issues in fragment sheet.
Check PR code / Lint (pull_request) Successful in 1m7s
Check PR code / build (pull_request) Successful in 12m15s
2024-12-30 22:12:38 +01:00
AmineB cd8b7aaf9d fix: build. 2024-12-30 22:12:21 +01:00
AmineB c25ad7621e chore: compile issue fix.
Check PR code / Lint (pull_request) Successful in 1m10s
Check PR code / build (pull_request) Successful in 9m57s
2024-12-30 15:07:35 +01:00
AmineB 63da3b9fe7 chore: filter some bugs. 2024-12-30 15:07:18 +01:00
AmineB 1d99eeb633 bugfix: catch users using something other than selfoss. 2024-12-30 13:51:45 +01:00
AmineB 162a350a8f bugfix: No browser, no link. 2024-12-30 13:51:14 +01:00
AmineB 27c1bba146 translations
Check PR code / Lint (pull_request) Successful in 54s
Check PR code / build (pull_request) Successful in 9m26s
2024-12-30 13:49:42 +01:00
AmineB b7f3a9877a chore: remove log. 2024-12-30 12:58:34 +01:00
AmineB 47f78754dc translation
Check PR code / Lint (pull_request) Successful in 46s
Check PR code / build (pull_request) Failing after 3m20s
2024-12-30 12:48:42 +01:00
giteadrone 1bdfb143ac Changelog for v124123641
Check master code / coverage (push) Successful in 11m32s
Check master code / build (push) Successful in 15m2s
2024-12-29 22:02:12 +00:00
AmineB d81ced3964 Chore: no tests on build.
Check master code / coverage (push) Successful in 9m42s
Check master code / build (push) Successful in 12m57s
Create tag / build (push) Successful in 9m8s
Create tag / createTagAndChangelog (push) Successful in 46s
Create tag / release (push) Successful in 6m34s
2024-12-29 22:37:59 +01:00
AmineB fbafece1fa Merge pull request 'testing' (#170) from testing into master
Check master code / coverage (push) Successful in 10m16s
Check master code / build (push) Successful in 12m51s
Reviewed-on: #170
2024-12-29 21:23:52 +00:00
AmineB cbed8f07cb fix: Displaying fixes. Fixes #155
Check PR code / Lint (pull_request) Successful in 1m24s
Check PR code / build (pull_request) Successful in 9m20s
2024-12-29 22:13:45 +01:00
AmineB f54fcc3ba1 test: coverage
Check PR code / Lint (pull_request) Successful in 1m13s
Check PR code / build (pull_request) Successful in 9m43s
2024-12-29 21:35:12 +01:00
aminecmi aad93ef722 chore: update and use multiplatform datetime 2024-12-21 20:32:58 +01:00
giteadrone 9e83af0302 Changelog for v124123421
Check master code / build (push) Successful in 11m23s
2024-12-07 17:41:21 +00:00
aminecmi 24b86e66b4 fix: Trying to fix the serialization issue.
Check master code / build (push) Successful in 9m40s
Create tag / build (push) Successful in 8m10s
Create tag / createTagAndChangelog (push) Successful in 32s
Create tag / release (push) Successful in 6m48s
2024-12-07 18:20:49 +01:00
giteadrone 641c444061 Changelog for v124113311 2024-11-26 20:40:47 +00:00
administrateurandaminecmi 0902c61544 chore: update versions. (#165)
Check master code / build (push) Successful in 8m8s
Create tag / build (push) Successful in 8m15s
Create tag / createTagAndChangelog (push) Successful in 1m0s
Create tag / release (push) Successful in 6m52s
## Types of changes

- [ ] I have read the **CONTRIBUTING** document.
- [ ] My code follows the code style of this project.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] This is **NOT** translation related.

This closes issue #XXX

This is implements feature #YYY

This finishes chore #ZZZ

Co-authored-by: aminecmi <aminecmi@gmail.com>
Reviewed-on: #165
Co-authored-by: administrateur <administrateur@hidden.hidden>
Co-committed-by: administrateur <administrateur@hidden.hidden>
2024-11-26 20:19:43 +00:00
aminecmi 6790152a0b chore: fastlane changelog.
Check master code / build (push) Successful in 8m3s
2024-11-26 21:06:57 +01:00
aminecmi 46d1ba418e chore: fastlane fixes.
Check master code / build (push) Has been cancelled
2024-11-26 21:03:31 +01:00
giteadrone 436373d0ad Changelog for v124113301
Check master code / build (push) Successful in 7m42s
2024-11-25 22:23:23 +01:00
aminecmi 5b9b51c02d chore: Gitea Action
Check master code / build (push) Successful in 7m51s
Create tag / build (push) Successful in 8m15s
Create tag / createTagAndChangelog (push) Successful in 46s
Create tag / release (push) Successful in 6m27s
2024-11-25 21:55:48 +01:00
administrateur b81abe384a Merge pull request 'chore: Gitea Action' (#164) from runner into master
Push/PR Steps / BuildAndTest (push) Has been cancelled
Reviewed-on: #164
2024-11-23 14:14:45 +00:00
aminecmi 851f862dbe chore: Gitea Action
Push/PR Steps / Lint (pull_request) Successful in 1m0s
Push/PR Steps / BuildAndTest (pull_request) Successful in 7m49s
2024-11-23 14:54:45 +01:00
aminecmi 8d7e302af8 chore: Readme update. 2024-11-20 20:45:06 +01:00
AmineB 236e1cca90 Fix recycleview article positions (#163)
Reviewed-on: #163
Reviewed-by: Amine Bouabdallaoui <amineb@hidden.hidden>
2024-11-20 09:32:29 +01:00
davidoskky 3a33cb4510 Provide method to update items in the home
Removed in the previous commit, the item adapter accepts a method to update the articles list in the home page.
Renamed the method to make its objective more clear.
Removed a debugging log.
Reverted change to function name.
2024-11-20 01:28:47 +01:00
davidoskky 0bf9ca9a49 Fix recycleview article positions
The articles were being opened by setting a click listener on the binding.
In card view this was being done through a function and as such it used the overall viewbind of the view rather than the binding of the item viewholder.
Now the functions are more explicit to avoid future errors.
Pulled up a few members from ItemCardAdapter and ItemListAdapter to ItemAdapter.
2024-11-19 01:50:58 +01:00
aminecmi 61e0087894 Changelog for v124041081 [CI SKIP] 2024-04-17 10:57:12 +00:00
aminecmi 1ec05d9913 chore: comment.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
continuous-integration/drone/tag Build is passing
2024-04-17 12:39:45 +02:00
aminecmi 859bd91bbb fix: Last time fixing the parsing date hack before moving it to os version. 2024-04-17 12:22:33 +02:00
aminecmi 204b736c53 Changelog for v124030731 [CI SKIP] 2024-03-13 19:51:14 +00:00
aminecmi f24609c143 fix: Basic auth and password can have non whitspace characters. Fixes 142.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-03-13 20:31:24 +01:00
aminecmi b94d7dc537 Changelog for v124020451 [CI SKIP] 2024-02-14 19:54:05 +00:00
aminecmi 41910cc4cd fix: Fixed handling of position in card adapter.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-02-13 21:31:02 +01:00
aminecmi db166ca9d4 Changelog for v124010301 [CI SKIP] 2024-01-30 19:36:37 +00:00
aminecmi db0d5a4a85 fix: This may fix the oom errors.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-01-29 20:55:29 +01:00
aminecmi 3bc0d7cf95 Changelog for v124010191 [CI SKIP] 2024-01-19 21:16:23 +00:00
aminecmi 8f464d95fd fix: moving listeners.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-01-19 21:32:43 +01:00
aminecmi 5ccd6a3368 chore: removed a useless log. 2024-01-19 21:32:43 +01:00
aminecmi cdbded246e Changelog for v124010032 [CI SKIP] 2024-01-03 22:16:35 +00:00
aminecmi 750c7758bd fix: Another date format thing.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-01-03 23:07:57 +01:00
aminecmi 22f8b14ecd Changelog for v124010031 [CI SKIP] 2024-01-03 21:46:48 +00:00
aminecmi 6e27d6d4e6 fix: Checking if selfoss instance.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-01-03 22:28:57 +01:00
aminecmi 14ff4dbd05 fix: handle three characters lenght hexcode colors. 2024-01-03 21:35:58 +01:00
aminecmi 390c2d0cf3 Changelog for v123113311 [CI SKIP] 2023-11-27 20:48:32 +00:00
aminecmi e58914ef58 chore: Source tracker url in the menu.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2023-11-27 21:31:46 +01:00
aminecmi a03f08fca1 fix: Handle kodein proguard rules. 2023-11-27 21:31:26 +01:00
aminecmi 8e9b87f00c Changelog for v123102961 [CI SKIP] 2023-10-23 21:11:15 +00:00
aminecmi f765224a86 chore: domain changes.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2023-10-21 23:41:17 +02:00
aminecmi 14d2219eb8 Changelog for v123102852 [CI SKIP] 2023-10-12 18:48:06 +00:00
aminecmi 137580ccf9 chore: lint cleaning.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2023-10-12 20:38:01 +02:00
aminecmi f101d22f54 Changelog for v123102841 [CI SKIP] 2023-10-12 20:13:17 +02:00
aminecmi 68aedb7641 chore: verbose.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is failing
2023-10-11 21:13:10 +02:00
754d526b49 chore: cleaning ci steps and upgrading dependencies.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is failing
## Types of changes

- [ ] I have read the **CONTRIBUTING** document.
- [ ] My code follows the code style of this project.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] This is **NOT** translation related.

This closes issue #XXX

This is implements feature #YYY

This finishes chore #ZZZ

Co-authored-by: davidoskky <davidoskky@yahoo.it>
Co-authored-by: aminecmi <aminecmi@gmail.com>
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/150
2023-10-10 20:52:26 +00:00
Amine Louveau c458871569 feat: Self signed ssl support.
continuous-integration/drone/push Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/141
2023-09-17 18:28:47 +00:00
davidoskky 056825aa0c Revert xmlns changes
continuous-integration/drone/pr Build is passing
2023-09-17 12:01:32 +02:00
davidoskky 16b19fc5ce Revert version upgrades 2023-09-17 11:59:08 +02:00
davidoskky 4ad4a23ed8 Revert to private functions
continuous-integration/drone/pr Build is failing
2023-09-12 00:38:37 +02:00
davidoskky d8c215eacc Reintroduce removed parameter 2023-09-12 00:36:56 +02:00
davidoskky 2b446ab22b Revert dependency version changes 2023-09-12 00:36:04 +02:00
davidoskky a029d8a7dc Move api client creation function within api class
continuous-integration/drone/pr Build is passing
2023-09-10 21:33:28 +02:00
davidoskky 4482234e1a Remove unused strings file
continuous-integration/drone/pr Build is passing
2023-09-10 21:15:04 +02:00
davidoskky b5de30f561 Add translations 2023-09-10 21:14:43 +02:00
davidoskky 70ad5f322c Handle most HTTP client creation in common code 2023-09-10 21:14:43 +02:00
davidoskky d167092c83 Add a login switch to disable SSL verification 2023-09-10 20:24:22 +02:00
davidoskky c4f4bafe85 Add a switch in the login screen to disable SSL 2023-07-13 14:55:48 +02:00
davidoskky ed06b22a77 Tentative self signed ssl support 2023-07-13 14:55:48 +02:00
aminecmi 172362b533 Changelog for v123061811 [CI SKIP] 2023-06-30 19:06:44 +00:00
aminecmi ad72cb6f56 feat: Added confirmation dialog for disconnect item menu.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-06-30 20:37:43 +02:00
aminecmi 9057ee0052 Changelog for v123061651 [CI SKIP] 2023-06-14 18:23:59 +00:00
aminecmi 50d0b44315 i18n: Translation update.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-06-13 20:52:42 +02:00
aminecmi 21b08ed384 i18n: Translation update.
continuous-integration/drone/push Build is running
2023-06-13 20:26:36 +02:00
aminecmi 993c4d2ee9 i18n: Translation update.
continuous-integration/drone/push Build is passing
2023-06-12 20:50:26 +02:00
aminecmi 57a9d51027 fix: avoid trying to open invalid image urls.
continuous-integration/drone/push Build is running
2023-06-12 20:34:35 +02:00
aminecmi 673f0edb8b Changelog for v123051471 [CI SKIP] 2023-05-27 19:25:35 +00:00
aminecmi 7f96798f13 fix: images could be null.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-05-27 21:06:56 +02:00
aminecmi 6e5704a45b fix: Check if color is not empty before parsing it. 2023-05-27 21:02:25 +02:00
aminecmi 495591159f chore: Removed unused log. 2023-05-27 21:01:54 +02:00
aminecmi 718fe7c5ee Changelog for v123051331 [CI SKIP] 2023-05-13 20:24:57 +00:00
aminecmi ecd23213f9 fix: illegal input.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-05-13 22:14:25 +02:00
aminecmi e6baed8cb4 Changelog for v123051321 [CI SKIP] 2023-05-12 19:19:35 +00:00
aminecmi c87abec0b9 debug: Debug null context.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-05-12 20:31:40 +02:00
Amine Louveau 0aba41d8bf Changelog for v123051301 [CI SKIP] 2023-05-10 19:36:31 +00:00
Amine Louveauandaminecmi 2a2d1047b4 feat: Basic auth from url. Fixes #142 (#143)
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Co-authored-by: aminecmi <aminecmi@gmail.com>
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/143
2023-05-10 19:19:11 +00:00
aminecmi 66ef1ccf32 debug: Debug index out of bound exception.
continuous-integration/drone/push Build is failing
2023-05-10 20:50:13 +02:00
aminecmi 677ede5bc7 Changelog for v123051211 [CI SKIP] 2023-05-01 18:26:12 +00:00
aminecmi 996a7ed22c fix: Sometimes url isn't even defined.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-04-30 18:41:15 +02:00
Amine Louveau 85208c4e5a Changelog for v123041021 [CI SKIP] 2023-04-12 19:01:21 +00:00
Amine Louveau 5cfec50cba fix: 'Enable Core Library Desugaring to support older Android versions' (#138) from davidoskky/ReaderForSelfoss-multiplatform:desugaring into master
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/138
2023-04-12 18:26:37 +00:00
davidoskky 76ad71e1dc Enable Core Library Desugaring to support older Android versions
continuous-integration/drone/pr Build is failing
2023-04-12 16:29:47 +02:00
davidoskky 0277fb507c Changelog for v123030851 [CI SKIP] 2023-03-26 18:21:43 +00:00
davidoskky 8d7d3174aa chore: replace textDrawable library (#136)
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
## Types of changes

- [x] I have read the **CONTRIBUTING** document.
- [x] My code follows the code style of this project.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [x] All new and existing tests passed.
- [x] This is **NOT** translation related.

This closes issue #120

Removed the dependency `com.amulyakhare.textdrawable` and slightly simplified the logic required to set circular images.

Co-authored-by: davidoskky <davidoskky@yahoo.it>
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/136
Co-authored-by: davidoskky <davidoskky@hidden.hidden>
Co-committed-by: davidoskky <davidoskky@hidden.hidden>
2023-03-26 11:12:01 +00:00
aminecmi 00eb3333fe refactor: Remove slow login check. Closes #135.
continuous-integration/drone/push Build is passing
2023-03-25 20:28:30 +01:00
aminecmi 629ca01d99 ci: send the mapping file after a release.
continuous-integration/drone/push Build is passing
2023-03-17 10:10:38 +01:00
aminecmi c2d8681ce8 Changelog for v123030751 [CI SKIP] 2023-03-16 19:41:34 +00:00
aminecmi 08f79cb148 debug: added a lot to pinpoint the url issue.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-03-16 20:26:36 +01:00
davidoskky e21906e70d feat: Use /sources/stats in the home (#133)
continuous-integration/drone/push Build is passing
## Types of changes

- [x] I have read the **CONTRIBUTING** document.
- [x] My code follows the code style of this project.
- [ ] I have updated the documentation accordingly.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.
- [x] This is **NOT** translation related.

This is implements feature #131 and it will allow implementing #132
With this, public mode functions perfectly and also has source filtering.

Co-authored-by: davidoskky <davidoskky@yahoo.it>
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/133
Co-authored-by: davidoskky <davidoskky@hidden.hidden>
Co-committed-by: davidoskky <davidoskky@hidden.hidden>
2023-03-13 16:26:54 +00:00
aminecmi 9d2cc32bc9 Changelog for v123030681 [CI SKIP] 2023-03-09 20:27:08 +00:00
aminecmi d9d057c8dc fix: Unread and starred can be null.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-03-09 20:46:09 +01:00
aminecmi 1f3fa0c4a6 Fixed version number issue.
continuous-integration/drone/push Build is failing
2023-03-05 21:02:03 +01:00
aminecmi dea3def385 Changelog for v123030621 [CI SKIP] 2023-03-03 20:07:52 +00:00
aminecmi f72ef2f5d4 fix: url required issue.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-03-03 20:39:33 +01:00
aminecmi f28cb759df fix: Canvas reused issue. 2023-03-03 20:39:20 +01:00
aminecmi b9d69c3e64 Changelog for v123020572 [CI SKIP] 2023-02-26 19:13:41 +00:00
aminecmi c2a1c9eaac fix: requirecontext issues ?
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-02-26 16:05:26 +01:00
aminecmi bf37209a15 debug: activity not found exception. 2023-02-26 15:40:58 +01:00
aminecmi 2c558fe6fd Changelog for v123020571 [CI SKIP] 2023-02-26 07:49:16 +00:00
aminecmi ad88011454 chore: remove errors logging.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-02-25 22:57:59 +01:00
aminecmi 559c17bc1d fix: quickfix for url param not provided for some sources. 2023-02-25 22:46:04 +01:00
Amine Louveau ab9c46f0eb Update 'CHANGELOG.md'
continuous-integration/drone/push Build is passing
2023-02-21 15:00:37 +00:00
Amine Louveau aa799d2ca8 Changelog for v123020523 [CI SKIP] 2023-02-21 14:49:14 +00:00
Amine Louveau 177c978474 fix: Git changelog.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-02-21 14:05:32 +00:00
Amine Louveau 39b9991413 Changelog for v123020522 [CI SKIP] 2023-02-21 13:25:11 +00:00
Amine Louveau b303f110f1 fix: still fixing changelog versions.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-02-21 12:59:38 +00:00
Amine Louveau f851941a6a Changelog for v123020521 [CI SKIP] 2023-02-21 11:31:18 +00:00
Amine Louveau a313552976 fix: change changelog version on release.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-02-21 11:20:43 +00:00
aminecmi 6ac97ed3fe Changelog for v123020491 [CI SKIP] 2023-02-20 09:39:40 +00:00
aminecmi d583b937b7 fix: Fixed acra bug reporting.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-02-20 10:29:00 +01:00
aminecmi 15b9a2d935 Changelog for v123010301 [CI SKIP] 2023-02-18 20:01:03 +00:00
aminecmi 5a8ce15961 Chore: acra config.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-02-18 20:38:06 +01:00
davidoskky e1c64cef46 Changelog for v123010281 [CI SKIP] 2023-01-30 19:55:23 +00:00
davidoskky ee064f3cb4 improvement: Improve right to left support (#130)
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Co-authored-by: davidoskky <davidoskky@hidden.hidden>
Co-committed-by: davidoskky <davidoskky@hidden.hidden>
2023-01-29 12:55:28 +00:00
davidoskky 3e46e2ff29 Changelog for v123010261 [CI SKIP] 2023-01-28 10:57:06 +00:00
davidoskky f28e702549 feat: Handle public instances (#126)
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Co-authored-by: davidoskky <davidoskky@hidden.hidden>
Co-committed-by: davidoskky <davidoskky@hidden.hidden>
2023-01-28 10:25:28 +00:00
aminecmi fc31a4399c ci: Pull request should trigger ci.
continuous-integration/drone/push Build is running
2023-01-28 11:23:00 +01:00
Amine Louveau 9b23053b66 fix: Complete the disconnection before redirecting to the login screen
continuous-integration/drone/push Build is running
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/129
2023-01-28 11:20:19 +01:00
davidoskky 389a04d250 Complete the disconnection before redirecting to the login screen 2023-01-27 14:21:33 +01:00
Amine Louveau 40e1d1478b Changelog for v123010241 [CI SKIP] 2023-01-26 10:55:43 +00:00
Amine Louveau 2154ff3c33 Merge pull request 'feat: swipe down to close images' (#122) from davidoskky/ReaderForSelfoss-multiplatform:swipe_down into master
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/122
2023-01-26 10:42:15 +00:00
davidoskky 2245565f95 Remove unnecessary definition 2023-01-25 21:25:32 +01:00
davidoskky 014858f06b Remove unused import 2023-01-25 20:40:20 +01:00
davidoskky 3f1f86a78e Adjust the image closing animation 2023-01-25 10:34:03 +01:00
davidoskky a549169a7c Add a dark hue to the underlying article when swiping to close images 2023-01-25 10:27:38 +01:00
davidoskky be7cae365a Rename activity style to avoid interferences 2023-01-25 02:09:41 +01:00
davidoskky cef3b2e593 Adapt the style of the image activity to the rest of the application 2023-01-25 01:54:35 +01:00
davidoskky ae927ebc57 Resolve issues when swiping down to close images 2023-01-25 00:46:43 +01:00
Amine Louveau 90532cf501 Changelog for v123010041 [CI SKIP] 2023-01-24 12:41:52 +00:00
Amine Louveau ab0678d61e Merge pull request 'scroll-tag-filters' (#124) from scroll-tag-filters into master
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/124
2023-01-23 21:42:42 +00:00
aminecmi a1b7d22d26 fix: added POST_NOTIFICATIONS to fix notifications issues.
continuous-integration/drone/push Build is passing
2023-01-23 22:32:52 +01:00
aminecmi 29eae4b1f6 fix: scrollable filter sheet.
continuous-integration/drone/push Build is failing
2023-01-23 21:13:10 +01:00
aminecmi f5bbc63481 enhancement: Ellipsize chips text. 2023-01-23 21:12:56 +01:00
davidoskky ddc72d85b0 Close the image fragment only if the image has been dragged down 2023-01-21 16:37:25 +01:00
davidoskky 68bbf5b2d3 Animate swipe down to close images 2023-01-20 16:36:52 +01:00
aminecmi 95e76a55da Cleaning.
continuous-integration/drone/push Build is passing
2023-01-12 22:01:36 +01:00
davidoskky 2b6659f4ec Swipe down to close images 2023-01-11 22:28:14 +01:00
aminecmi e0c118a73e Changelog for v122123641 [CI SKIP] 2023-01-04 19:27:56 +00:00
aminecmi 4e61b2aed6 feat: Disable the failing source in the filter sheet.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-01-03 21:35:21 +01:00
aminecmi ba2758c0a3 feat: Display the source error in the sources list. 2023-01-03 21:28:40 +01:00
aminecmi c718b966a1 Changelog for v122123631 [CI SKIP] 2022-12-30 19:24:48 +00:00
aminecmi 99438e142f build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153)
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-12-30 20:10:08 +01:00
aminecmi 4d8076c3cf build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153)
continuous-integration/drone/push Build is failing
2022-12-29 21:34:13 +01:00
aminecmi db75c5b74a debug: trying to resolve Canvas: trying to use a recycled bitmap.
continuous-integration/drone/push Build is passing
2022-12-29 20:40:39 +01:00
aminecmi 966a082147 fix: NPE may be caused by the binding or the title that was null.
continuous-integration/drone/push Build is running
2022-12-29 20:35:03 +01:00
aminecmi cd20a5ec29 chore: Skip drone pipeline on changelog push.
continuous-integration/drone/push Build was killed
2022-12-29 14:11:29 +01:00
aminecmi cc4c1c9201 Changelog for v122123621
continuous-integration/drone/push Build was killed
2022-12-29 13:08:23 +00:00
aminecmi ff021d572c fix: Automatic CHANGELOG generation.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-12-29 13:11:23 +01:00
Amine Louveau 89992967be Merge pull request 'Sources Upsert' (#119) from sources-edit into master
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/119
2022-12-28 21:31:49 +00:00
aminecmi 3c68bde62b Source update screen.
continuous-integration/drone/push Build is passing
2022-12-28 22:19:11 +01:00
aminecmi c38251f5b3 Sources menu. 2022-12-28 21:45:00 +01:00
aminecmi a01f6d2322 chore: Automatic CHANGELOG generation.
continuous-integration/drone/push Build is passing
2022-12-28 21:43:56 +01:00
Amine Louveau 417a33eb25 Merge pull request 'Running migrations.' (#118) from fix-migration into master
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/118
2022-12-28 14:49:11 +00:00
aminecmi 2e7f7f23b3 No duplicate builds for PRs.
continuous-integration/drone/push Build is passing
2022-12-28 15:34:22 +01:00
aminecmi e5e182761e Running migrations.
continuous-integration/drone/push Build was killed
continuous-integration/drone/pr Build was killed
2022-12-28 15:27:17 +01:00
Amine Louveau a094d88799 Merge pull request 'Make the author field nullable' (#117) from davidoskky/ReaderForSelfoss-multiplatform:author into master
continuous-integration/drone/push Build was killed
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/117
2022-12-28 14:25:36 +00:00
davidoskky e51915d1cd Include author field when updating the database
continuous-integration/drone/pr Build is passing
2022-12-28 14:25:56 +01:00
davidoskky 3a654f6ede Migrate the database table 2022-12-28 14:25:34 +01:00
davidoskky 5227751dca Make the author field nullable
continuous-integration/drone/pr Build is passing
2022-12-28 11:02:43 +01:00
aminecmi 27eafe4ff4 Delete sources from DB and reload items on source deletion.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-12-26 22:27:28 +01:00
aminecmi 8c83a9408b Drone should work better.
continuous-integration/drone/push Build was killed
2022-12-26 22:26:28 +01:00
aminecmi fe2410f719 Handling author field.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
2022-12-26 21:49:55 +01:00
aminecmi a5e86bfb77 Date format issues.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
2022-12-26 15:02:19 +01:00
aminecmi 23be633798 Add api version to the reports.
continuous-integration/drone/push Build is running
2022-12-25 22:45:12 +01:00
aminecmi 813e0707d8 Date format issue.
continuous-integration/drone/push Build is running
2022-12-25 22:41:34 +01:00
aminecmi 9ed9bf07fc Items in repository.
continuous-integration/drone/push Build is passing
2022-12-23 22:53:16 +01:00
aminecmi 47265c10d0 Trying nexus build.
continuous-integration/drone/push Build is passing
2022-12-23 14:59:58 +01:00
aminecmi 5cc633246a Debugging images issues.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-12-22 20:28:49 +01:00
aminecmi 1f40385786 Context should not be null, but handle the case for now.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-12-19 22:08:28 +01:00
aminecmi eb2876324a This seems to be needed.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-12-19 20:47:04 +01:00
aminecmi 633b817d76 Remonving matomo.
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-12-18 21:07:42 +01:00
171 changed files with 8951 additions and 3861 deletions

No files matched your search

-134
View File
@@ -1,134 +0,0 @@
kind: pipeline
type: docker
name: test
steps:
- name: BuildAndTest
image: mingc/android-build-box:latest
commands:
- echo "---------------------------------------------------------"
- echo "Configure gradle..."
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
- echo "---------------------------------------------------------"
- echo "Building..."
- ./gradlew build -x test
- echo "---------------------------------------------------------"
- echo "Testing..."
- echo "---------------------------------------------------------"
- ./gradlew koverMergedXmlReport
environment:
SONAR_HOST_URL:
from_secret: sonarScannerHostUrl
SONAR_LOGIN:
from_secret: sonarScannerLogin
- name: Analyse
image: kytay/sonar-node-plugin
settings:
sonar_host:
from_secret: sonarScannerHostUrl
sonar_token:
from_secret: sonarScannerLogin
use_node_version: 16.18.1
sonar_debug: true
sonar_project_settings: ./sonar-project.properties
trigger:
event:
- push
- pull_request
---
kind: pipeline
type: docker
name: Publish
steps:
- name: createTag
image: ubuntu:latest
commands:
- apt-get update && apt-get install -y git
- ./build.sh --publish --from-ci
- git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
- git push pushing --tags
environment:
GITEA_USR:
from_secret: giteaUsr
GITEA_PASS:
from_secret: giteaPass
- name: scpFiles
image: appleboy/drone-scp
settings:
host: amine-louveau.fr
username: ubuntu
key:
from_secret: privateKey
port: 22
target: /home/ubuntu/
source: version.txt
- name: deploy
image: appleboy/drone-ssh
settings:
host: amine-louveau.fr
user: ubuntu
key:
from_secret: privateKey
command_timeout: 2m
script:
- cd /home/ubuntu
- sudo rm -rf /var/www/amine/version.txt
- sudo chown www-data:www-data ./version.txt
- sudo mv version.txt /var/www/amine/
trigger:
event:
- promote
target:
- production
---
kind: pipeline
type: docker
name: Release
steps:
- name: build
image: mingc/android-build-box:latest
commands:
- echo "---------------------------------------------------------"
- echo "Fetch tags..."
- git fetch --tags
- echo "---------------------------------------------------------"
- echo "Configure gradle..."
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=false\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
- echo "---------------------------------------------------------"
- echo "Generate APK"
- ./gradlew :androidApp:assembleGithubConfigRelease -P pushCache=false
- echo "---------------------------------------------------------"
- echo "Get Key"
- wget https://amine-louveau.fr/key
- echo "---------------------------------------------------------"
- echo "Zipalign"
- $ANDROID_HOME/build-tools/31.0.0/zipalign -f -v 4 androidApp/build/outputs/apk/githubConfig/release/androidApp-githubConfig-release-unsigned.apk androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
- echo "---------------------------------------------------------"
- echo "Sign"
- $ANDROID_HOME/build-tools/31.0.0/apksigner sign -v --out signed.apk --ks ./key --ks-key-alias $YOUR_KEY_ALIAS --ks-pass pass:$YOUR_KEYSTORE_PASSWORD --v1-signing-enabled true --v2-signing-enabled true androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
- echo "---------------------------------------------------------"
- echo "Verify"
- $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk
environment:
YOUR_KEYSTORE_PASSWORD:
from_secret: keyPass
YOUR_KEY_ALIAS:
from_secret: keyAlias
- name: gitea_release
image: plugins/gitea-release
settings:
api_key:
from_secret: giteaAPI
base_url: https://gitea.amine-louveau.fr
files: signed.apk
trigger:
event:
- tag
+36
View File
@@ -0,0 +1,36 @@
root = true
[*]
insert_final_newline = true
[.editorconfig]
insert_final_newline = false
ij_kotlin_line_break_after_multiline_when_entry = false
[*.{kt,kts}]
# Disable wildcard imports entirely
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
end_of_line = lf
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
ij_kotlin_indent_before_arrow_on_new_line = false
ij_kotlin_line_break_after_multiline_when_entry = true
ij_kotlin_packages_to_use_import_on_demand = unset
indent_size = 4
indent_style = space
ktlint_argument_list_wrapping_ignore_when_parameter_count_greater_or_equal_than = unset
ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than = 4
ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 1
ktlint_code_style = ktlint_official
ktlint_enum_entry_name_casing = upper_or_camel_cases
ktlint_function_naming_ignore_when_annotated_with = unset
ktlint_function_signature_body_expression_wrapping = multiline
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2
ktlint_ignore_back_ticked_identifier = false
ktlint_property_naming_constant_naming = screaming_snake_case
max_line_length = 140
[**/build]
ktlint = disabled
+10
View File
@@ -0,0 +1,10 @@
project_id_env: CROWDIN_PROJECT_ID
api_token_env: CROWDIN_PERSONAL_TOKEN
base_path: "../../../"
files:
- source: /androidApp/src/main/res/values/strings.xml
translation: /androidApp/src/main/res/values-%android_code%/%original_file_name%
translate_attributes: '0'
content_segmentation: '0'
preserve_hierarchy: true
@@ -0,0 +1,10 @@
version: '3'
services:
selfoss:
container_name: selfoss
image: rsprta/selfoss
network_mode: "host"
ports:
- "8888:8888"
+50
View File
@@ -0,0 +1,50 @@
name: Build
on:
workflow_call:
jobs:
BuildAndTestAndCoverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Check android app changes"
id: check-android-changes
uses: tj-actions/changed-files@v45
with:
files: |
androidApp/src/**
shared/src/commonMain/**
shared/src/androidMain/**
shared/src/commonTest/**
- name: Fetch tags
if: steps.check-android-changes.outputs.any_modified == 'true'
run: git fetch --tags -p
- uses: actions/setup-java@v4
if: steps.check-android-changes.outputs.any_modified == 'true'
with:
distribution: 'temurin'
java-version: '17'
- uses: gradle/actions/setup-gradle@v3
if: steps.check-android-changes.outputs.any_modified == 'true'
- uses: android-actions/setup-android@v3
if: steps.check-android-changes.outputs.any_modified == 'true'
- name: Configure gradle...
if: steps.check-android-changes.outputs.any_modified == 'true'
run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true" >> ~/.gradle/gradle.properties
- name: Build and test
if: steps.check-android-changes.outputs.any_modified == 'true'
run: ./gradlew build -x testReleaseUnitTest -x testDebugUnitTest -x testGithubConfigReleaseUnitTest -x testGithubConfigDebugUnitTest
- name: coverage
if: steps.check-android-changes.outputs.any_modified == 'true'
run: |
./gradlew :koverHtmlReport
- uses: actions/upload-artifact@v3
if: steps.check-android-changes.outputs.any_modified == 'true'
with:
name: coverage
path: build/reports/kover/html
retention-days: 1
overwrite: true
include-hidden-files: true
+127
View File
@@ -0,0 +1,127 @@
name: Realease
on:
push:
branches:
- release
workflow_dispatch:
jobs:
build:
uses: ./.gitea/workflows/on_called_build.yml
createTagAndChangelog:
runs-on: ubuntu-latest
needs: build
steps:
- name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: master
- name: Config git
run: |
git config --global user.email aminecmi+giteadrone@pm.me
git config --global user.name giteadrone
- name: Creating the tag and generate changelog
run: |
git fetch --tags -p
PREV=$(git describe --tags --abbrev=0)
./build.sh --publish --from-ci
VER=$(git describe --tags --abbrev=0)
CHANGELOG=$(git log $PREV..HEAD --pretty="- %s")
echo "**$VER
$CHANGELOG
--------------------------------------------------------------------
$(cat CHANGELOG.md)" > CHANGELOG.md
git add CHANGELOG.md
touch ./fastlane/metadata/android/en\-US/changelogs/$VER.txt
echo "**$VER**
$CHANGELOG" > ./fastlane/metadata/android/en\-US/changelogs/$VER.txt
git add ./fastlane/metadata/android/en\-US/changelogs/$VER.txt
git commit -m "Changelog for $VER"
- name: Push changes
uses: appleboy/git-push-action@v1.0.0
with:
author_name: giteadrone
author_email: aminecmi+giteadrone@pm.me
remote: ${{ secrets.REMOTE_URL }}
followtags: true
ssh_key: ${{ secrets.PRIVATE_KEY }}
tags: true
branch: master
- name: copy file via ssh password
uses: appleboy/scp-action@v0.1.7
with:
host: amine-bouabdallaoui.fr
username: ubuntu
key: ${{ secrets.PRIVATE_KEY }}
source: "version.txt"
target: "/home/ubuntu/"
- name: deploy version file
uses: appleboy/ssh-action@v1.2.0
with:
host: amine-bouabdallaoui.fr
username: ubuntu
key: ${{ secrets.PRIVATE_KEY }}
script: cd /home/ubuntu && sudo rm -rf /var/www/amine/version.txt && sudo chown www-data:www-data ./version.txt && sudo mv version.txt /var/www/amine/
release:
runs-on: ubuntu-latest
needs: createTagAndChangelog
steps:
- name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch tags
id: version
run: |
git fetch --tags -p
PREV=$(git describe --tags --abbrev=0)
echo $PREV
echo "VERSION=$PREV" >> $GITHUB_OUTPUT
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Configure gradle...
run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
- name: setup go
uses: https://github.com/actions/setup-go@v4
with:
go-version: '>=1.20.1'
- name: Generate APK
run: ./gradlew :androidApp:assembleGithubConfigRelease
- name: Get Key
run: wget ${{ secrets.KEY_URL }}
- name: Zippalign
run: |
sdkmanager "build-tools;31.0.0"
ls $ANDROID_HOME/build-tools
$ANDROID_HOME/build-tools/31.0.0/zipalign -f -v 4 androidApp/build/outputs/apk/githubConfig/release/androidApp-githubConfig-release-unsigned.apk androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
- name: Sigh
run: $ANDROID_HOME/build-tools/31.0.0/apksigner sign -v --out signed.apk --ks ./key --ks-key-alias ${{ secrets.KEY_ALIAS }} --ks-pass pass:${{ secrets.KEYSTORE_PASSWORD }} --v1-signing-enabled true --v2-signing-enabled true androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
- name: Verify
run: $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk
- name: Release
uses: https://gitea.com/actions/gitea-release-action@main
with:
files: signed.apk
token: ${{ secrets.API_KEY }}
tag_name: ${{ steps.version.outputs.VERSION }}
name: ${{ steps.version.outputs.VERSION }}
- name: Send mail
uses: https://github.com/dawidd6/action-send-mail@v4
with:
connection_url: ${{ secrets.MAIL_CONNECTION }}
to: ${{ secrets.MAIL_TO }}
from: ${{ secrets.MAIL_FROM }}
subject: Mapping file
priority: high
convert_markdown: true
body: Nouveau fichier de mapping pour la version ${{ steps.version.outputs.VERSION }}
attachments: androidApp/build/outputs/mapping/githubConfigRelease/mapping.txt
+89
View File
@@ -0,0 +1,89 @@
name: PR
on:
pull_request:
branches:
- master
jobs:
PR:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- 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/
- 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
- name: Linting...
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
- name: Detecting...
run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt'
translations:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Check translations changes"
id: check-translations-changes
uses: tj-actions/changed-files@v45
with:
files: |
androidApp/src/main/res/values/strings.xml
- name: upload translation sources
if: steps.check-translations-changes.outputs.any_modified == 'true'
uses: crowdin/github-action@v2
with:
config: './.gitea/workflows/assets/crowdin.yml'
upload_sources: true
upload_translations: false
download_translations: false
create_pull_request: false
push_translations: false
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- name: wait
if: steps.check-translations-changes.outputs.any_modified == 'true'
run: sleep 10s
- name: download translations
if: steps.check-translations-changes.outputs.any_modified == 'true'
uses: crowdin/github-action@v2
with:
config: './.gitea/workflows/assets/crowdin.yml'
upload_sources: false
upload_translations: false
download_translations: true
create_pull_request: false
push_translations: false
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- name: Check for uncommitted changes
if: steps.check-translations-changes.outputs.any_modified == 'true'
id: check-changes
uses: mskri/check-uncommitted-changes-action@v1.0.1
- name: Commit Changes
if: steps.check-translations-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != ''
run: |
git config --global user.email aminecmi+giteadrone@pm.me
git config --global user.name giteadrone
git add ./androidApp/src/main/res/*
git commit -m "translation: translation files"
- name: Push changes
if: steps.check-translations-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != ''
uses: appleboy/git-push-action@v1.0.0
with:
author_name: giteadrone
author_email: aminecmi+giteadrone@pm.me
remote: ${{ secrets.REMOTE_URL }}
ssh_key: ${{ secrets.PRIVATE_KEY }}
branch: ${{ github.head_ref || github.ref_name }}
build:
needs: Lint
uses: ./.gitea/workflows/on_called_build.yml
+67
View File
@@ -0,0 +1,67 @@
name: PR test
on:
pull_request:
branches:
- master
jobs:
integrationTests:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch tags
run: git fetch --tags -p
- uses: KengoTODA/actions-setup-docker-compose@v1
with:
version: "2.23.3"
- name: run selfoss
run: |
docker compose -f .gitea/workflows/assets/docker-compose.yml up -d
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- uses: gradle/actions/setup-gradle@v3
- uses: android-actions/setup-android@v3
- name: Configure gradle...
run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true" >> ~/.gradle/gradle.properties
- name: Change url until I find a better way to do it
run: |
sed -i "s/const val DEFAULT_URL = \"http:\/\/10\.0\.2\.2\:8888\"/const val DEFAULT_URL = \"http:\/\/172\.17\.0\.1\:8888\"/g" ./androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/CommonTests.kt
- name: Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
profile: pixel_2
script: |
./gradlew androidApp:clearScreenshotsTask || true
./gradlew androidApp:createScreenshotDirectory
adb logcat -G 16M
./gradlew JacocoDebugCodeCoverage || (./gradlew androidApp:fetchScreenshots && adb logcat 'InputReader:S' 'chatty:S' 'audio_hw_generic:S' 'LogApiCalls:D' '*:I' -d > ./androidApp/build/reports/androidTests/connected/screenshots/logs.txt)
- uses: actions/upload-artifact@v3
with:
name: screenshot-espresso
path: androidApp/build/reports/androidTests/connected/screenshots
retention-days: 2
overwrite: true
include-hidden-files: true
- uses: actions/upload-artifact@v3
with:
path: androidApp/build/reports/androidTests/connected/debug/flavors/githubConfig
retention-days: 1
overwrite: true
include-hidden-files: true
- uses: actions/upload-artifact@v3
with:
name: coverage-espresso
path: androidApp/build/reports/jacoco/JacocoDebugCodeCoverage
retention-days: 1
overwrite: true
include-hidden-files: true
- name: Clean
if: always()
run: |
docker compose -f .gitea/workflows/assets/docker-compose.yml stop
+9
View File
@@ -0,0 +1,9 @@
name: Master
on:
push:
branches:
- master
jobs:
build:
uses: ./.gitea/workflows/on_called_build.yml
+1 -1
View File
@@ -10,7 +10,7 @@ Please read the guidelines before contributing, and follow them (or try to) when
There are many ways to contribute to this project, you could [translate the app](https://crowdin.com/project/readerforselfoss), report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users.
You can fork the repository, and [help me solve some issues](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues)
You can fork the repository, and [help me solve some issues](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues)
### What I can't help you with.
+5
View File
@@ -321,3 +321,8 @@ fabric.properties
crowdin.properties
.kotlin/
build-cache/
act
+461
View File
@@ -1,3 +1,464 @@
**v125030901
- Merge pull request 'fix-reload' (#195) from fix-reload into master
- fix: Infinite scroll needs loading stats.
- fix: do not reload items on resume.
- Merge pull request 'tests' (#193) from tests into master
- ci: Instrumentation tests coverage in ci.
- ci: Instrumentation tests coverage in ci.
- ci: Instrumentation tests coverage in ci.
- chore: better handling of coroutine dispatchers.
- ci: Instrumentation tests coverage in ci.
- chore: comment robolectric tests for now.
- fix: Fixed source deletion test.
- Merge pull request 'Fix alignment changes resetting reader article position' (#190) from davidoskky/ReaderForSelfoss-multiplatform:alignment into master
- Refactor star icon handling
- Don't restart activity changing alignment
- Changelog for v125030711
--------------------------------------------------------------------
**v125030711
- Merge pull request 'fix: initial status loading issues.' (#192) from connectivity into master
- chore: check changes for translations and android.
- fix: initial status loading issues.
- Merge pull request 'chore: new connectivity dep. Closes #84.' (#189) from connectivity into master
- chore: new connectivity dep. Closes #84.
- Changelog for v125030681
--------------------------------------------------------------------
**v125030681
- chore: do not send reports on simulators.
- Merge pull request 'chore: do not send reports on simulators.' (#188) from chore-acra-simulator into master
- chore: do not send reports on simulators.
- Merge pull request 'fix: Url validation was not failing login. Added tests.' (#186) from fix-invalid-url into master
- Merge pull request 'chore: crowding ci integration.' (#187) from chore-crowdin-ci into master
- chore: we don't need to check if the url is valid in upsert screen.
- fix: Url validation was not failing login. Added tests.
- chore: crowding ci integration.
- Show a confirmation dialog before deleting sources (#185)
- Changelog for v125020581
--------------------------------------------------------------------
**v125020581
- fix: url can be empty ?
- Changelog for v125020471
--------------------------------------------------------------------
**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
- Merge pull request 'Bump dependencies' (#173) from upgarde into master
- chore: "faster" action.
- fastlane: icon change.
- chore: ignoring a pixel issue.
- test: fixed an ui test issue.
- fix: center the loading thing.
- test: items displaying.
- bump: sqldelight.
- bump: material, desugar jdk, jsoup, kodein, settings, napier, mock.
- bump: androix and coroutines.
- bump: ktor. Closes #67.
- Changelog for v124123651
--------------------------------------------------------------------
**v124123651
- Merge pull request 'Bugfixes' (#171) from bugfixes into master
- config: crowdin
- chore: can links be empty ?
- fix: Context issues in article fragment.
- fix: Context issues in fragment sheet.
- fix: build.
- chore: compile issue fix.
- chore: filter some bugs.
- bugfix: catch users using something other than selfoss.
- bugfix: No browser, no link.
- translations
- chore: remove log.
- translation
- Changelog for v124123641
--------------------------------------------------------------------
**v124123641
- Chore: no tests on build.
- Merge pull request 'testing' (#170) from testing into master
- fix: Displaying fixes. Fixes #155
- test: coverage
- chore: update and use multiplatform datetime
- Changelog for v124123421
--------------------------------------------------------------------
**v124123421
- fix: Trying to fix the serialization issue.
- Changelog for v124113311
--------------------------------------------------------------------
**v124113311
- chore: update versions. (#165)
- chore: fastlane changelog.
- chore: fastlane fixes.
- Changelog for v124113301
--------------------------------------------------------------------
**v124113301**
- chore: Gitea Action
- Merge pull request 'chore: Gitea Action' (#164) from runner into master
- chore: Gitea Action
- chore: Readme update.
--------------------------------------------------------------------
**v124041081**
- chore: comment.
- fix: Last time fixing the parsing date hack before moving it to os version.
- Changelog for v124030731 [CI SKIP]
--------------------------------------------------------------------
**v124030731**
- fix: Basic auth and password can have non whitspace characters. Fixes 142.
- Changelog for v124020451 [CI SKIP]
--------------------------------------------------------------------
**v124020451**
- fix: Fixed handling of position in card adapter.
- Changelog for v124010301 [CI SKIP]
--------------------------------------------------------------------
**v124010301**
- fix: This may fix the oom errors.
- Changelog for v124010191 [CI SKIP]
--------------------------------------------------------------------
**v124010191**
- fix: moving listeners.
- chore: removed a useless log.
- Changelog for v124010032 [CI SKIP]
--------------------------------------------------------------------
**v124010032**
- fix: Another date format thing.
- Changelog for v124010031 [CI SKIP]
--------------------------------------------------------------------
**v124010031**
- fix: Checking if selfoss instance.
- fix: handle three characters lenght hexcode colors.
- Changelog for v123113311 [CI SKIP]
--------------------------------------------------------------------
**v123113311**
- chore: Source tracker url in the menu.
- fix: Handle kodein proguard rules.
- Changelog for v123102961 [CI SKIP]
--------------------------------------------------------------------
**v123102961**
- chore: domain changes.
- Changelog for v123102852 [CI SKIP]
--------------------------------------------------------------------
**v123102852**
- chore: lint cleaning.
- Changelog for v123102841 [CI SKIP]
--------------------------------------------------------------------
**v123102841**
- chore: cleaning ci steps and upgrading dependencies.
- feat: Self signed ssl support.
- Changelog for v123061811 [CI SKIP]
--------------------------------------------------------------------
**v123061811**
- feat: Added confirmation dialog for disconnect item menu.
- Changelog for v123061651 [CI SKIP]
--------------------------------------------------------------------
**v123061651**
- i18n: Translation update.
- i18n: Translation update.
- i18n: Translation update.
- fix: avoid trying to open invalid image urls.
- Changelog for v123051471 [CI SKIP]
--------------------------------------------------------------------
**v123051471**
- fix: images could be null.
- fix: Check if color is not empty before parsing it.
- chore: Removed unused log.
- Changelog for v123051331 [CI SKIP]
--------------------------------------------------------------------
**v123051331**
- fix: illegal input.
- Changelog for v123051321 [CI SKIP]
--------------------------------------------------------------------
**v123051321**
- debug: Debug null context.
- Changelog for v123051301 [CI SKIP]
--------------------------------------------------------------------
**v123051301**
- feat: Basic auth from url. Fixes #142 (#143)
- debug: Debug index out of bound exception.
- Changelog for v123051211 [CI SKIP]
--------------------------------------------------------------------
**v123051211**
- fix: Sometimes url isn't even defined.
- Changelog for v123041021 [CI SKIP]
--------------------------------------------------------------------
**v123041021**
- fix: 'Enable Core Library Desugaring to support older Android versions' (#138) from davidoskky/ReaderForSelfoss-multiplatform:desugaring into master
- Enable Core Library Desugaring to support older Android versions
- Changelog for v123030851 [CI SKIP]
--------------------------------------------------------------------
**v123030851**
- chore: replace textDrawable library (#136)
- refactor: Remove slow login check. Closes #135.
- ci: send the mapping file after a release.
- Changelog for v123030751 [CI SKIP]
--------------------------------------------------------------------
**v123030751**
- debug: added a lot to pinpoint the url issue.
- feat: Use /sources/stats in the home (#133)
- Changelog for v123030681 [CI SKIP]
--------------------------------------------------------------------
**v123030681**
- fix: Unread and starred can be null.
- Fixed version number issue.
- Changelog for v123030621 [CI SKIP]
--------------------------------------------------------------------
**v123030621**
- fix: url required issue.
- fix: Canvas reused issue.
- Changelog for v123020572 [CI SKIP]
--------------------------------------------------------------------
**v123020572**
- fix: requirecontext issues ?
- debug: activity not found exception.
- Changelog for v123020571 [CI SKIP]
--------------------------------------------------------------------
**v123020571**
- chore: remove errors logging.
- fix: quickfix for url param not provided for some sources.
- Update 'CHANGELOG.md'
- Changelog for v123020523 [CI SKIP]
--------------------------------------------------------------------
**v123020523**
- fix: Git changelog.
--------------------------------------------------------------------
**v123020491**
- fix: Fixed acra bug reporting.
--------------------------------------------------------------------
**v123010301**
- Chore: acra config.
--------------------------------------------------------------------
**v123010281**
- improvement: Improve right to left support (#130) Co-authored-by: davidoskky <davidoskky@hidden.hidden> Co-committed-by: davidoskky <davidoskky@hidden.hidden>
--------------------------------------------------------------------
**v123010261**
- feat: Handle public instances (#126) Co-authored-by: davidoskky <davidoskky@hidden.hidden> Co-committed-by: davidoskky <davidoskky@hidden.hidden>
- ci: Pull request should trigger ci.
- fix: Complete the disconnection before redirecting to the login screen
- Complete the disconnection before redirecting to the login screen
--------------------------------------------------------------------
**v123010241**
- Merge pull request 'feat: swipe down to close images' (#122) from davidoskky/ReaderForSelfoss-multiplatform:swipe_down into master
- Remove unnecessary definition
- Remove unused import
- Adjust the image closing animation
- Add a dark hue to the underlying article when swiping to close images
- Rename activity style to avoid interferences
- Adapt the style of the image activity to the rest of the application
- Resolve issues when swiping down to close images
- Close the image fragment only if the image has been dragged down
- Animate swipe down to close images
- Swipe down to close images
--------------------------------------------------------------------
**v123010041**
- Merge pull request 'scroll-tag-filters' (#124) from scroll-tag-filters into master
- fix: added POST_NOTIFICATIONS to fix notifications issues.
- fix: scrollable filter sheet.
- enhancement: Ellipsize chips text.
- Cleaning.
--------------------------------------------------------------------
**v122123641**
- feat: Disable the failing source in the filter sheet.
- feat: Display the source error in the sources list.
--------------------------------------------------------------------
**v122123631**
- build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153)
- build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153)
- debug: trying to resolve `Canvas: trying to use a recycled bitmap`.
- fix: NPE may be caused by the binding or the title that was null.
- chore: Skip drone pipeline on changelog push.
--------------------------------------------------------------------
**v122123621**
- fix: Automatic CHANGELOG generation.
- Merge pull request 'Sources Upsert' (#119) from sources-edit into master
- Source update screen.
- Sources menu.
- chore: Automatic CHANGELOG generation.
--------------------------------------------------------------------
# V2/Multiplatform rewrite
**v1**
+6 -10
View File
@@ -1,4 +1,4 @@
# ReaderForSelfoss-multiplatform [![Build Status](https://build.amine-louveau.fr/api/badges/Louvorg/ReaderForSelfoss-multiplatform/status.svg)](https://build.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform)
# ReaderForSelfoss-multiplatform [![Build Status](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/actions/workflows/on_push.yml/badge.svg)](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/actions?workflow=on_push.yml&actor=0&status=0)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss)
@@ -10,10 +10,6 @@ If you are a user, you can still create new issues. I'll fix them when I can.
<a href="https://f-droid.org/packages/bou.amine.apps.readerforselfossv2.android"><img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"></a>
## Screen captures
<img src="res//fr-card.png?raw=true" alt="card view" width="400"/> <img src="res//fr-list.png?raw=true" alt="list view" width="400"/>
## Like my app ?
<a href="https://www.buymeacoffee.com/aminecmi" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/lato-orange.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>
@@ -22,15 +18,15 @@ If you are a user, you can still create new issues. I'll fix them when I can.
1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/).
2. Check the [Contribution guide](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md).
2. Check the [Contribution guide](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md).
3. Build the project by following [these steps](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
3. Build the project by following [these steps](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
## Useful links
- [Check what changed](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/CHANGELOG.md)
- [See what I'm doing](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/projects/1)
- [Create an issue, or request a new feature](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/issues)
- [Check what changed](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/CHANGELOG.md)
- [See what I'm doing](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/projects/1)
- [Create an issue, or request a new feature](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/issues)
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
## Contributors (V1) (Alphabetical order) ❤️
+1
View File
@@ -1 +1,2 @@
/build
.kotlin/
+177 -76
View File
@@ -1,39 +1,50 @@
import java.io.ByteArrayOutputStream
val ignoreGitVersion: String by project
val acraVersion = "5.9.7"
val acraVersion = "5.12.0"
plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
id("com.mikepenz.aboutlibraries.plugin")
id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("org.jetbrains.kotlinx.kover")
id("app.cash.sqldelight") version "2.0.2"
jacoco
}
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
var result: String = ByteArrayOutputStream().use { outputStream ->
project.exec {
commandLine = cmd.split(" ")
standardOutput = outputStream
isIgnoreExitValue = ignore ?: false
fun Project.execWithOutput(
cmd: String,
ignore: Boolean = false,
): String {
val result: String =
ByteArrayOutputStream().use { outputStream ->
project.exec {
commandLine = cmd.split(" ")
standardOutput = outputStream
isIgnoreExitValue = ignore
}
outputStream.toString()
}
outputStream.toString()
}
return result
}
fun gitVersion(): String {
var process = ""
val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
process = if (maybeTagOfCurrentCommit.isEmpty()) {
println("No tag on current commit. Will take the latest one.")
execWithOutput("git -C ../ for-each-ref refs/tags --sort=-authordate --format='%(refname:short)' --count=1")
} else {
println("Tag found on current commit")
execWithOutput("git -C ../ describe --contains HEAD")
}
return process.replace("^0", "").replace("'", "").substring(1).replace("\\.", "").trim()
val process =
if (maybeTagOfCurrentCommit.isEmpty()) {
println("No tag on current commit. Will take the latest one.")
execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1")
} else {
println("Tag found on current commit")
execWithOutput("git -C ../ describe --contains HEAD")
}
return process
.replace("^0", "")
.replace("'", "")
.substring(1)
.replace("\\.", "")
.trim()
}
fun versionCodeFromGit(): Int {
@@ -54,26 +65,35 @@ fun versionNameFromGit(): String {
return gitVersion()
}
val exclusions =
listOf(
"**/R.class",
"**/R\$*.class",
"**/BuildConfig.*",
"**/Manifest*.*",
"**/*Test*.*",
)
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
// Flag to enable support for the new language APIs
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
// For Kotlin projects
kotlinOptions {
jvmTarget = "11"
jvmTarget = "17"
}
compileSdk = 33
buildToolsVersion = "31.0.0"
compileSdk = 35
buildFeatures {
viewBinding = true
}
defaultConfig {
applicationId = "bou.amine.apps.readerforselfossv2.android"
minSdk = 21
targetSdk = 33
minSdk = 25
targetSdk = 34 // 35 when edge-to-edge is handled
versionCode = versionCodeFromGit()
versionName = versionNameFromGit()
@@ -85,8 +105,9 @@ android {
// tests
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["useTestStorageService"] = "true"
}
packagingOptions {
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
@@ -98,6 +119,44 @@ android {
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
getByName("debug") {
isTestCoverageEnabled = true
enableAndroidTestCoverage = true
installation {
installOptions("-g", "-r")
}
val androidTests = "connectedAndroidTest"
tasks.register<JacocoReport>("JacocoDebugCodeCoverage") {
// Depend on unit tests and Android tests tasks
dependsOn(listOf(androidTests))
// Set task grouping and description
group = "Reporting"
description = "Execute UI and unit tests, generate and combine Jacoco coverage report"
// Configure reports to generate both XML and HTML formats
reports {
xml.required.set(true)
html.required.set(true)
}
// Set source directories to the main source directory
sourceDirectories.setFrom(layout.projectDirectory.dir("src/main"))
// Set class directories to compiled Java and Kotlin classes, excluding specified exclusions
classDirectories.setFrom(
files(
fileTree(layout.buildDirectory.dir("intermediates/javac/")) {
exclude(exclusions)
},
fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/")) {
exclude(exclusions)
},
),
)
// Collect execution data from .exec and .ec files generated during test execution
executionData.setFrom(
files(
fileTree(layout.buildDirectory) { include(listOf("**/*.exec", "**/*.ec")) },
),
)
}
}
}
flavorDimensions.add("build")
@@ -108,89 +167,96 @@ android {
}
}
namespace = "bou.amine.apps.readerforselfossv2.android"
testOptions {
animationsDisabled = true
unitTests {
isIncludeAndroidResources = true
}
}
}
dependencies {
implementation(project(":shared"))
implementation("com.google.android.material:material:1.5.0")
implementation("androidx.appcompat:appcompat:1.4.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
implementation("androidx.preference:preference-ktx:1.1.1")
implementation(project(":shared"))
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))
// Android Support
implementation("androidx.appcompat:appcompat:1.4.1")
implementation("com.google.android.material:material:1.5.0")
implementation("androidx.recyclerview:recyclerview:1.3.0-alpha01")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.recyclerview:recyclerview:1.4.0-rc01")
implementation("androidx.legacy:legacy-support-v4:1.0.0")
implementation("androidx.vectordrawable:vectordrawable:1.2.0-alpha02")
implementation("androidx.vectordrawable:vectordrawable:1.2.0")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.annotation:annotation:1.3.0")
implementation("androidx.work:work-runtime-ktx:2.7.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("org.jsoup:jsoup:1.14.3")
implementation("androidx.annotation:annotation:1.9.1")
implementation("androidx.work:work-runtime-ktx:2.10.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.0")
implementation("org.jsoup:jsoup:1.18.3")
//multidex
// multidex
implementation("androidx.multidex:multidex:2.0.1")
// About
implementation("com.mikepenz:aboutlibraries-core:10.5.1")
implementation("com.mikepenz:aboutlibraries:10.5.1")
implementation("com.mikepenz:aboutlibraries-core:11.6.3")
implementation("com.mikepenz:aboutlibraries:11.6.3")
// Material-ish things
implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
// glide
kapt("com.github.bumptech.glide:compiler:4.14.2")
implementation("com.github.bumptech.glide:okhttp3-integration:4.14.2")
kapt("com.github.bumptech.glide:compiler:4.16.0")
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
// Themes
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
implementation("com.leinardi.android:speed-dial:3.3.0")
// Pager
implementation("me.relex:circleindicator:2.1.6")
implementation("androidx.viewpager2:viewpager2:1.1.0-beta01")
implementation("androidx.viewpager2:viewpager2:1.1.0")
//Dependency Injection
implementation("org.kodein.di:kodein-di:7.14.0")
implementation("org.kodein.di:kodein-di-framework-android-x:7.14.0")
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.14.0")
// Dependency Injection
implementation("org.kodein.di:kodein-di:7.23.1")
implementation("org.kodein.di:kodein-di-framework-android-x:7.23.1")
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.23.1")
//Settings
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
// Settings
implementation("com.russhwolf:multiplatform-settings-no-arg:1.3.0")
//Logging
implementation("io.github.aakira:napier:2.6.1")
// Logging
implementation("io.github.aakira:napier:2.7.1")
//PhotoView
// PhotoView
implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation("androidx.core:core-ktx:1.8.0")
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
// Network information
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
// SQLDELIGHT
implementation("com.squareup.sqldelight:android-driver:1.5.4")
implementation("app.cash.sqldelight:android-driver:2.0.2")
//test
// test
testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.12.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
testImplementation("io.mockk:mockk:1.13.14")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
androidTestImplementation("androidx.test:runner:1.7.0-alpha01")
androidTestImplementation("androidx.test:rules:1.7.0-alpha01")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
implementation("androidx.test.espresso:espresso-idling-resource:3.6.1")
androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1")
androidTestUtil("androidx.test.services:test-services:1.6.0-alpha02")
testImplementation("org.robolectric:robolectric:4.14.1")
testImplementation("androidx.test:core-ktx:1.7.0-alpha01")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
implementation("ch.acra:acra-http:$acraVersion")
implementation("ch.acra:acra-toast:$acraVersion")
// Matomo
implementation("com.github.matomo-org:matomo-sdk-android:4.1.4")
implementation("com.google.auto.service:auto-service:1.1.1")
}
tasks.withType<Test> {
@@ -198,16 +264,24 @@ tasks.withType<Test> {
useJUnit()
testLogging {
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
events = setOf(
org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED,
org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED,
org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR
)
events =
setOf(
org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED,
org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED,
org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR,
)
showStandardStreams = true
}
if (this.name == "connectedAndroidTest") {
configure<JacocoTaskExtension> {
isIncludeNoLocationClasses = true
excludes = listOf("jdk.internal.*")
}
}
}
aboutLibraries {
excludeFields = arrayOf("generated")
offlineMode = true
fetchRemoteLicense = false
fetchRemoteFunding = false
@@ -216,3 +290,30 @@ aboutLibraries {
duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
}
val clearScreenshotsTask =
tasks.register<Exec>("clearScreenshots") {
println("AMINE : clear")
commandLine = listOf("adb", "shell", "rm", "-r", "/storage/emulated/0/Pictures/selfoss_tests/screenshots/*")
}
val createScreenshotDirectoryTask =
tasks.register<Exec>("createScreenshotDirectory") {
println("AMINE : create directory")
group = "reporting"
commandLine = listOf("adb", "shell", "mkdir", "-p", "/storage/emulated/0/Pictures/selfoss_tests/screenshots")
}
tasks.register<Exec>("fetchScreenshots") {
val reportsDirectory = file("$buildDir/reports/androidTests/connected")
println("AMINE : fetch")
group = "reporting"
executable(android.adbExecutable.toString())
commandLine = listOf("adb", "pull", "/storage/emulated/0/Pictures/selfoss_tests/screenshots", reportsDirectory.toString())
finalizedBy(clearScreenshotsTask)
doFirst {
reportsDirectory.mkdirs()
}
}
+10
View File
@@ -55,6 +55,7 @@
# maybe remove later ?
-keep class * extends androidx.fragment.app.Fragment
-dontwarn org.slf4j.impl.StaticLoggerBinder
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
@@ -85,3 +86,12 @@
-dontwarn io.mockk.**
-keep class io.mockk.** { *; }
# Kodein
-keep, allowobfuscation, allowoptimization class org.kodein.type.TypeReference
-keep, allowobfuscation, allowoptimization class org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest
-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.TypeReference
-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest
@@ -0,0 +1,98 @@
package bou.amine.apps.readerforselfossv2.android
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isClickable
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.junit.After
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@LargeTest
class `1-LoginActivityTest` : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@Before
fun registerIdlingResource() {
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
}
@After
fun unregisterIdlingResource() {
IdlingRegistry
.getInstance()
.unregister(CountingIdlingResourceSingleton.countingIdlingResource)
}
@Test
fun `1-viewIsInitialized`() {
onView(withId(R.id.urlView)).check(matches(isDisplayed()))
onView(withId(R.id.selfSigned))
.check(matches(isDisplayed()))
.check(matches(isNotChecked()))
.check(
matches(isClickable()),
)
onView(withId(R.id.withLogin))
.check(matches(isDisplayed()))
.check(matches(isNotChecked()))
.check(
matches(isClickable()),
)
}
@Test
fun `2-urlError`() {
performLogin("10.0.2.2:8888")
onView(withId(R.id.urlView)).perform(click())
onView(withId(R.id.urlView)).check(matches(withError(R.string.login_url_problem)))
}
@Test
fun `3-urlSlashError`() {
performLogin("https://google.fr/toto")
onView(withId(R.id.urlView)).perform(click())
onView(withId(R.id.urlView)).check(matches(withError(R.string.login_url_problem)))
}
@Test
fun `4-connectError`() {
performLogin("http://10.0.2.2:8889")
onView(withId(R.id.urlView)).check(matches(withError(R.string.wrong_infos)))
}
@Test
fun `5-multiError`() {
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.signInButton)).perform(click())
onView(withText(R.string.warning_wrong_url)).check(matches(isDisplayed()))
}
@Test
fun `6-connect`() {
performLogin()
onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed()))
onView(withText("OK")).perform(click())
checkHomeLoadingDone()
}
}
@@ -0,0 +1,127 @@
package bou.amine.apps.readerforselfossv2.android
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isClickable
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isFocused
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.isSelected
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.hamcrest.CoreMatchers.not
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@LargeTest
class `2-HomeActivityTest` : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java)
@Before
fun registerIdlingResource() {
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
checkHomeLoadingDone()
}
@Test
fun testMenu() {
onView(withId(R.id.action_search)).check(matches(isDisplayed())).check(
matches(
isClickable(),
),
)
onView(withId(R.id.action_filter)).check(matches(isDisplayed())).check(
matches(
isClickable(),
),
)
openMenu()
onView(withText(R.string.readAll)).check(matches(isDisplayed()))
onView(withText(R.string.menu_home_sources)).check(matches(isDisplayed()))
onView(withText(R.string.title_activity_settings)).check(matches(isDisplayed()))
onView(withText(R.string.menu_home_refresh)).check(matches(isDisplayed()))
onView(withText(R.string.issue_tracker_link)).check(matches(isDisplayed()))
onView(withText(R.string.action_disconnect)).check(matches(isDisplayed()))
}
@Test
fun testMenuActions() {
onView(withId(R.id.action_search)).perform(click())
onView(
withId(com.google.android.material.R.id.search_src_text),
).check(matches(isFocused()))
onView(isRoot()).perform(ViewActions.pressBack())
onView(withId(R.id.action_filter)).perform(click())
onView(
withText(R.string.filter_item_sources),
).check(matches(isDisplayed()))
onView(
withText(R.string.filter_item_tags),
).check(matches(isDisplayed()))
onView(
withId(R.id.floatingActionButton2),
).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack())
openMenu()
onView(withText(R.string.readAll)).perform(click())
onView(withText(R.string.markall_dialog_message)).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack())
openMenu()
onView(withText(R.string.menu_home_sources)).perform(click())
onView(withId(R.id.fab)).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack())
openMenu()
onView(withText(R.string.title_activity_settings)).perform(click())
onView(withText(R.string.pref_header_general)).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack())
openMenu()
onView(withText(R.string.menu_home_refresh)).perform(click())
onView(withText(R.string.refresh_dialog_message)).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack())
openMenu()
/*onView(withText(R.string.issue_tracker_link)).perform(click())
onView(withText(R.string.markall_dialog_message)).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack())
openMenu()*/
onView(withText(R.string.action_disconnect)).perform(click())
onView(withText(R.string.confirm_disconnect_title)).check(matches(isDisplayed()))
}
@Test
fun testEmptyView() {
onView(withId(R.id.emptyText)).check(matches(isDisplayed()))
onView(
hasBottombarItemText(R.string.tab_new),
).check(matches(isDisplayed())).check(matches(isSelected()))
onView(
hasBottombarItemText(R.string.tab_read),
).check(matches(isDisplayed())).check(matches(not(isSelected())))
onView(
hasBottombarItemText(R.string.tab_favs),
).check(matches(isDisplayed())).check(matches(not(isSelected())))
}
}
@@ -0,0 +1,103 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.isSelected
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
class `3-SettingsActivityTest` : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java)
lateinit var context: Context
@Before
fun init() {
activityRule.scenario.onActivity { activity ->
context = activity.window.context
}
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
openMenu()
onView(withText(R.string.title_activity_settings)).perform(click())
}
@Test
fun testAllSettings() {
onView(withText(R.string.pref_header_general)).check(matches(isDisplayed()))
onView(withText(R.string.pref_header_viewer)).check(matches(isDisplayed()))
onView(withText(R.string.pref_header_offline)).check(matches(isDisplayed()))
onView(withText(R.string.pref_header_theme)).check(matches(isDisplayed()))
onView(withText(R.string.pref_header_links)).check(matches(isDisplayed()))
onView(withText(R.string.pref_switch_disable_acra)).check(
matches(
allOf(
isDisplayed(),
not(isSelected()),
),
),
)
onView(withText(R.string.action_about)).check(matches(isDisplayed()))
}
@Test
fun testThemes() {
testPreferencesFromArray(context, R.array.ModeTitles) {
onView(withText(R.string.pref_header_theme)).perform(click())
}
}
@Test
fun testExperimentail() {
onView(withText(R.string.pref_header_experimental)).perform(click())
changeAndCancelSetting("", "10") {
onView(withText(R.string.pref_api_timeout)).perform(click())
}
changeAndSaveSetting("", "10") {
onView(withText(R.string.pref_api_timeout)).perform(click())
}
changeAndSaveSetting("", "60") {
onView(withText(R.string.pref_api_timeout)).perform(click())
}
}
@Test
fun testBugReports() {
onView(withText(R.string.pref_switch_disable_acra)).perform(click())
}
@Test
fun testLinks() {
onView(withText(R.string.pref_header_links)).perform(click())
onView(withText(R.string.issue_tracker_link)).check(matches(isDisplayed()))
onView(withText(R.string.issue_tracker_summary)).check(matches(isDisplayed()))
onView(withText(R.string.source_code)).check(matches(isDisplayed()))
onView(withText(R.string.translation)).check(matches(isDisplayed()))
}
@Test
fun testAbout() {
onView(withText(R.string.action_about)).perform(click())
onView(isRoot()).perform(waitUntilShown("ACRA", 30000))
onView(withText("ACRA")).check(matches(isDisplayed()))
}
}
@@ -0,0 +1,163 @@
package bou.amine.apps.readerforselfossv2.android
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.action.ViewActions.swipeUp
import androidx.test.espresso.action.ViewActions.typeTextIntoFocusedView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
import androidx.test.espresso.matcher.ViewMatchers.isFocused
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@LargeTest
class `4-SettingsActivityGeneralTest` : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java)
@Before
fun init() {
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext(),
)
onView(withText(R.string.title_activity_settings)).perform(click())
onView(withText(R.string.pref_header_general)).perform(click())
}
@Suppress("detekt:LongMethod")
@Test
fun testGeneral() {
onView(withText(R.string.pref_api_items_number_title)).check(matches(isDisplayed()))
onView(
withSettingsCheckboxWidget(R.string.pref_general_infinite_loading_title),
).check(
matches(
allOf(
isDisplayed(),
not(isChecked()),
),
),
)
onView(withText(R.string.pref_general_category_links)).check(matches(isDisplayed()))
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).check(
matches(
allOf(
isDisplayed(),
isChecked(),
),
),
)
onView(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed()))
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
matches(
allOf(
isDisplayed(),
not(isChecked()),
),
),
)
onView(withSettingsCheckboxWidget(R.string.card_height_title)).check(
matches(
allOf(
isDisplayed(),
not(isChecked()),
),
),
)
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(
matches(
not(isEnabled()),
),
)
onView(withSettingsCheckboxWidget(R.string.switch_unread_count_title)).check(
matches(
allOf(
isDisplayed(),
isChecked(),
),
),
)
onView(withId(R.id.settings)).perform(swipeUp())
onView(withSettingsCheckboxWidget(R.string.display_all_counts_title)).check(
matches(
allOf(
isDisplayed(),
not(isChecked()),
),
),
)
}
@Suppress("detekt:ForbiddenComment")
@Test
fun testGeneralActionsNumberItems() {
onView(withText(R.string.pref_api_items_number_title)).perform(click())
onView(withId(android.R.id.edit)).check(matches(isFocused()))
// Value check
onView(
withId(android.R.id.edit),
).perform(replaceText("AVC"))
.check(matches(withText("")))
// TODO: should check message error. Not working for api level 30+
onView(
withId(android.R.id.edit),
).perform(replaceText("-1"))
.check(matches(withText("")))
// TODO: should check message error. Not working for api level 30+
onView(
withId(android.R.id.edit),
).perform(replaceText("300"))
.check(matches(withText("")))
onView(
withId(android.R.id.edit),
).perform(typeTextIntoFocusedView("300"))
.check(matches(withText("30")))
onView(
withId(android.R.id.edit),
).perform(replaceText("10"))
.check(matches(withText("10")))
onView(isRoot()).perform(ViewActions.pressBack())
// Value saving
changeAndCancelSetting("20", "10") {
onView(withText(R.string.pref_api_items_number_title)).perform(click())
}
changeAndSaveSetting("20", "10") {
onView(withText(R.string.pref_api_items_number_title)).perform(click())
}
}
@Test
fun testGeneralActionsCheckboxes() {
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled())))
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).perform(click())
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(isEnabled()))
}
}
@@ -0,0 +1,93 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@LargeTest
class `5-SettingsActivityReaderTest` : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(SettingsActivity::class.java)
lateinit var context: Context
@Before
fun init() {
activityRule.scenario.onActivity { activity ->
context = activity.window.context
}
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
onView(withText(R.string.pref_header_viewer)).perform(click())
}
@After
fun back() {
onView(isRoot()).perform(ViewActions.pressBack())
}
@Test
fun testReader() {
onView(withSettingsCheckboxFrame(R.string.pref_switch_actions_pager_scroll)).check(
matches(
allOf(
isDisplayed(),
not(
isChecked(),
),
),
),
)
onView(withText(R.string.pref_content_reader_font_size)).check(matches(isDisplayed()))
onView(withText(R.string.settings_reader_font)).check(matches(isDisplayed()))
}
@Test
fun testReaderActions() {
onView(withText(R.string.pref_switch_actions_pager_scroll_off)).check(
matches(
isDisplayed(),
),
)
onView(withSettingsCheckboxFrame(R.string.pref_switch_actions_pager_scroll)).perform(click())
onView(withText(R.string.pref_switch_actions_pager_scroll_on)).check(
matches(
isDisplayed(),
),
)
onView(withText(R.string.pref_content_reader_font_size)).perform(click())
changeAndCancelSetting("16", "10") {
onView(withText(R.string.pref_content_reader_font_size)).perform(click())
}
changeAndSaveSetting("16", "10") {
onView(withText(R.string.pref_content_reader_font_size)).perform(click())
}
testPreferencesFromArray(context, R.array.preloaded_fonts_values) {
onView(withText(R.string.settings_reader_font)).perform(click())
}
}
}
@@ -0,0 +1,182 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
import androidx.test.espresso.matcher.ViewMatchers.isNotEnabled
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@LargeTest
class `6-SettingsActivityOfflineTest` : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(SettingsActivity::class.java)
lateinit var context: Context
@Before
fun init() {
activityRule.scenario.onActivity { activity ->
context = activity.window.context
}
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
onView(withText(R.string.pref_header_offline)).perform(click())
}
@After
fun back() {
onView(isRoot()).perform(ViewActions.pressBack())
}
@Suppress("detekt:LongMethod")
@Test
fun testOffline() {
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).check(
matches(
allOf(
isDisplayed(),
not(isChecked()),
),
),
)
onView(withSettingsCheckboxWidget(R.string.pref_switch_items_caching)).check(
matches(
allOf(
isDisplayed(),
not(isChecked()),
),
),
)
onView(withSettingsCheckboxFrame(R.string.pref_switch_items_caching)).check(
matches(
isEnabled(),
),
)
onView(withText(R.string.pref_periodic_refresh_minutes_title)).check(
matches(
allOf(isNotEnabled(), isDisplayed()),
),
)
onView(withSettingsCheckboxWidget(R.string.pref_switch_refresh_when_charging)).check(
matches(
allOf(
isDisplayed(),
not(isChecked()),
),
),
)
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
matches(
isNotEnabled(),
),
)
onView(withSettingsCheckboxWidget(R.string.pref_switch_notify_new_items)).check(
matches(
allOf(
isDisplayed(),
not(isChecked()),
),
),
)
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
matches(
isNotEnabled(),
),
)
onView(withSettingsCheckboxWidget(R.string.pref_switch_update_sources)).check(
matches(
allOf(
isDisplayed(),
isChecked(),
),
),
)
}
@Suppress("detekt:LongMethod")
@Test
fun testOfflineActions() {
onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed()))
onView(withText(R.string.pref_switch_items_caching)).perform(click())
onView(withText(R.string.pref_switch_items_caching_on)).check(matches(isDisplayed()))
onView(withSettingsCheckboxFrame(R.string.pref_switch_items_caching)).check(
matches(
isEnabled(),
),
)
onView(withText(R.string.pref_periodic_refresh_minutes_title)).check(
matches(
isNotEnabled(),
),
)
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
matches(
isNotEnabled(),
),
)
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
matches(
isNotEnabled(),
),
)
onView(withText(R.string.pref_switch_periodic_refresh_off)).check(
matches(
isDisplayed(),
),
)
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).perform(click())
onView(withText(R.string.pref_switch_periodic_refresh_on)).check(
matches(
isDisplayed(),
),
)
onView(withSettingsCheckboxFrame(R.string.pref_periodic_refresh_minutes_title)).check(
matches(
isEnabled(),
),
)
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
matches(
isEnabled(),
),
)
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
matches(
isEnabled(),
),
)
changeAndCancelSetting("360", "123") {
onView(withText(R.string.pref_periodic_refresh_minutes_title)).perform(click())
}
changeAndSaveSetting("360", "123") {
onView(withText(R.string.pref_periodic_refresh_minutes_title)).perform(click())
}
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).perform(click())
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).perform(click())
onView(withSettingsCheckboxWidget(R.string.pref_switch_update_sources)).perform(click())
}
}
@@ -0,0 +1,83 @@
package bou.amine.apps.readerforselfossv2.android
import androidx.test.espresso.AmbiguousViewMatcherException
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.swipeDown
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.util.UUID
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@LargeTest
class `7-SourcesActivityTest` : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java)
lateinit var sourceName: String
@Before
fun init() {
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
sourceName = UUID.randomUUID().toString().substring(0, 15)
goToSources()
}
@Test
fun addSource() {
testAddSourceWithUrl(
"https://lorem-rss.herokuapp.com/feed?unit=year&interval=1&length=10",
sourceName,
)
}
@Suppress("detekt:SwallowedException")
@Test
fun addSourceCheckContent() {
testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName)
onView(isRoot()).perform(ViewActions.pressBack())
openMenu()
onView(withText(R.string.menu_home_refresh)).perform(click())
onView(withText(R.string.refresh_dialog_message)).check(matches(isDisplayed()))
onView(
withId(android.R.id.button1),
).perform(click())
Thread.sleep(10000)
onView(withId(R.id.swipeRefreshLayout)).perform(swipeDown())
Thread.sleep(2000)
try {
onView(withId(R.id.sourceTitleAndDate)).check(matches(isDisplayed()))
} catch (e: AmbiguousViewMatcherException) {
assert(true)
}
goToSources()
}
@After
fun deleteTheCreatedSource() {
onView(withText(sourceName)).check(matches(isDisplayed()))
onView(withId(R.id.deleteBtn)).perform(click())
onView(withText(R.string.confirm_delete_title)).check(matches(isDisplayed()))
onView(withId(android.R.id.button1)).perform(click())
onView(withText(sourceName)).check(doesNotExist())
}
}
@@ -0,0 +1,232 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import android.graphics.Bitmap
import android.os.Environment.DIRECTORY_PICTURES
import android.os.Environment.getExternalStoragePublicDirectory
import android.util.Log
import androidx.annotation.ArrayRes
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.action.ViewActions.typeTextIntoFocusedView
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.base.DefaultFailureHandler
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.hamcrest.Matchers.hasToString
import org.junit.BeforeClass
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.Locale
// For now, do not move this as it is modified by the integration tests
const val DEFAULT_URL = "http://10.0.2.2:8888"
fun performLogin(someUrl: String? = null) {
Log.i("AUTOMATION", "The url used will be ${if (!someUrl.isNullOrEmpty()) someUrl else DEFAULT_URL}")
onView(withId(R.id.urlView)).perform(click()).perform(
typeTextIntoFocusedView(
if (!someUrl.isNullOrEmpty()) someUrl else DEFAULT_URL,
),
)
onView(withId(R.id.signInButton)).perform(click())
}
fun changeAndCancelSetting(
oldValue: String,
newValue: String,
openSettingItem: () -> Unit,
) {
openSettingItem()
onView(
withId(android.R.id.edit),
).perform(replaceText(newValue))
onView(
withId(android.R.id.button2),
).perform(click())
openSettingItem()
onView(
withId(android.R.id.edit),
).check(matches(withText(oldValue)))
onView(
withText(newValue),
).check(doesNotExist())
onView(
withId(android.R.id.button2),
).perform(click())
}
fun changeAndSaveSetting(
oldValue: String,
newValue: String,
openSettingItem: () -> Unit,
) {
openSettingItem()
onView(
withId(android.R.id.edit),
).perform(replaceText(newValue))
onView(
withId(android.R.id.button1),
).perform(click())
openSettingItem()
onView(
withId(android.R.id.edit),
).check(matches(withText(newValue)))
if (oldValue.isNotEmpty()) {
onView(
withText(oldValue),
).check(doesNotExist())
}
onView(
withId(android.R.id.button2),
).perform(click())
}
fun testPreferencesFromArray(
context: Context,
@ArrayRes arrayRes: Int,
openSettingItem: () -> Unit,
) {
openSettingItem()
context.resources.getStringArray(arrayRes).forEach { res ->
onView(withText(res)).check(matches(allOf(isDisplayed(), isNotChecked())))
onView(withText(res)).perform(click())
onView(withText(res)).check(doesNotExist())
openSettingItem()
onView(withText(res)).check(matches(allOf(isDisplayed(), isChecked())))
}
}
fun goToSources() {
openMenu()
onView(withText(R.string.menu_home_sources))
.perform(click())
}
fun testAddSourceWithUrl(
url: String,
sourceName: String,
) {
onView(withId(R.id.fab))
.perform(click())
onView(withId(R.id.nameInput))
.perform(click())
.perform(typeTextIntoFocusedView(sourceName))
onView(withId(R.id.sourceUri))
.perform(click())
.perform(typeTextIntoFocusedView(url))
onView(withId(R.id.tags))
.perform(click())
.perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
onView(withId(R.id.spoutsSpinner))
.perform(click())
onData(hasToString("RSS Feed")).perform(click())
onView(withId(R.id.saveBtn))
.perform(click())
onView(withText(sourceName)).check(matches(isDisplayed()))
}
fun checkHomeLoadingDone() {
onView(withId(R.id.swipeRefreshLayout)).inRoot(not(isDialog())).perform(waitForRecyclerViewToStopLoading(300000))
}
@Suppress("detekt:UtilityClassWithPublicConstructor")
open class WithANRException {
companion object {
// Running count of the number of Android Not Responding dialogues to prevent endless dismissal.
private var anrCount = 0
// `RootViewWithoutFocusException` class is private, need to match the message (instead of using type matching).
private val rootViewWithoutFocusExceptionMsg =
java.lang.String.format(
Locale.ROOT,
"Waited for the root of the view hierarchy to have " +
"window focus and not request layout for 10 seconds. If you specified a non " +
"default root matcher, it may be picking a root that never takes focus. " +
"Root:",
)
private const val OTHER_EXCEPTION = "System Ul isn't responding"
private fun handleAnrDialogue() {
val device = UiDevice.getInstance(getInstrumentation())
// If running the device in English Locale
val waitButton = device.findObject(UiSelector().textContains("wait"))
if (waitButton.exists()) waitButton.click()
}
@JvmStatic
@BeforeClass
fun setUpHandler() {
Espresso.setFailureHandler { error, viewMatcher ->
takeScreenshot()
if (error.message!!.contains(OTHER_EXCEPTION)) {
handleAnrDialogue()
} else if (error.message!!.contains(rootViewWithoutFocusExceptionMsg) &&
anrCount < 20
) {
anrCount++
handleAnrDialogue()
} else { // chain all failures down to the default espresso handler
Log.e("AMINE", "AMINE : ${error.message}")
println("AMINE : ${error.message}")
DefaultFailureHandler(getInstrumentation().targetContext).handle(error, viewMatcher)
}
}
}
}
}
@Suppress("detekt:NestedBlockDepth")
fun takeScreenshot() {
try {
val bitmap = getInstrumentation().uiAutomation.takeScreenshot()
val folder =
File(
File(
getExternalStoragePublicDirectory(DIRECTORY_PICTURES),
"selfoss_tests",
).absolutePath,
"screenshots",
)
if (!folder.exists()) {
folder.mkdirs()
}
var out: BufferedOutputStream? = null
val size = folder.list().size + 1
try {
out = BufferedOutputStream(FileOutputStream(folder.path + "/" + size + ".png"))
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
Log.d("Screenshots", "Screenshot taken")
} catch (e: IOException) {
Log.e("Screenshots", "Could not save the screenshot", e)
} finally {
if (out != null) {
try {
out.close()
} catch (e: IOException) {
Log.e("Screenshots", "Could not save the screenshot", e)
}
}
}
} catch (ex: IOException) {
Log.e("Screenshots", "Could not take the screenshot", ex)
}
}
@@ -0,0 +1,200 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import android.view.View
import android.widget.EditText
import android.widget.ImageView
import android.widget.RelativeLayout
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.isVisible
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.PerformException
import androidx.test.espresso.Root
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withChild
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.espresso.matcher.ViewMatchers.withResourceName
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.util.HumanReadables
import androidx.test.espresso.util.TreeIterables
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.any
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import org.hamcrest.TypeSafeMatcher
import java.util.concurrent.TimeoutException
fun withError(
@StringRes id: Int,
): TypeSafeMatcher<View?> {
return object : TypeSafeMatcher<View?>() {
override fun matchesSafely(view: View?): Boolean {
if (view != null && (view !is EditText || view.error == null)) {
return false
}
val context = view!!.context
return (view as EditText).error.toString() == context.getString(id)
}
override fun describeTo(description: Description?) {
// Nothing
}
}
}
fun waitUntilShown(
viewText: String,
millis: Long,
): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> = isRoot()
override fun getDescription(): String = "wait for $millis millis, for a specific view with text <$viewText> to be visible."
override fun perform(
uiController: UiController,
view: View,
) {
uiController.loopMainThreadUntilIdle()
val startTime = System.currentTimeMillis()
val endTime = startTime + millis
val viewMatcher = withText(viewText)
do {
for (child in TreeIterables.breadthFirstViewTraversal(view)) {
if (viewMatcher.matches(child) && child.isShown) {
return
}
}
uiController.loopMainThreadForAtLeast(100)
} while (System.currentTimeMillis() < endTime)
// timeout happens
throw PerformException
.Builder()
.withActionDescription(this.description)
.withViewDescription(HumanReadables.describe(view))
.withCause(TimeoutException())
.build()
}
}
}
fun waitForRecyclerViewToStopLoading(millis: Long): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> = any(View::class.java)
override fun getDescription(): String = "wait for $millis millis for the recyclerview to stop loading."
override fun perform(
uiController: UiController,
view: View?,
) {
uiController.loopMainThreadUntilIdle()
val startTime = System.currentTimeMillis()
val endTime = startTime + millis
do {
// either the empty view is displayed
for (child in TreeIterables.breadthFirstViewTraversal(view)) {
// found view with required ID
if (withId(R.id.emptyText).matches(child) && child.isVisible) {
return
}
}
// or the refresh layout is refreshing
if (view is SwipeRefreshLayout && !view.isRefreshing) {
return
}
uiController.loopMainThreadForAtLeast(100)
} while (System.currentTimeMillis() < endTime)
// timeout happens
throw PerformException
.Builder()
.withActionDescription(this.description)
.withViewDescription(HumanReadables.describe(view))
.withCause(TimeoutException())
.build()
}
}
}
fun isPopupWindow(): Matcher<Root> = isPlatformPopup()
fun withDrawable(
@DrawableRes id: Int,
) = object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description) {
description.appendText("ImageView with drawable same as drawable with id $id")
}
@Suppress("detekt:SwallowedException")
override fun matchesSafely(view: View): Boolean {
val context = view.context
val expectedBitmap = context.getDrawable(id)!!.toBitmap()
try {
return view is ImageView && view.drawable.toBitmap().sameAs(expectedBitmap)
} catch (e: Exception) {
return false
}
}
}
fun hasBottombarItemText(
@StringRes id: Int,
): Matcher<View>? =
allOf(
withResourceName("fixed_bottom_navigation_icon"),
withParent(
allOf(
withResourceName("fixed_bottom_navigation_icon_container"),
hasSibling(withText(id)),
),
),
)
fun withSettingsCheckboxWidget(
@StringRes id: Int,
): Matcher<View>? =
allOf(
withId(android.R.id.switch_widget),
withParent(
withSettingsCheckboxFrame(id),
),
)
fun withSettingsCheckboxFrame(
@StringRes id: Int,
): Matcher<View>? =
allOf(
withId(android.R.id.widget_frame),
hasSibling(
allOf(
withClassName(Matchers.equalTo(RelativeLayout::class.java.name)),
withChild(
withText(id),
),
),
),
)
fun openMenu() {
openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext<Context>(),
)
}
+87
View File
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name=".MyApp"
android:allowBackup="false"
android:configChanges="uiMode"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@style/NoBar"
tools:replace="android:allowBackup">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
android:name=".LoginActivity"
android:label="@string/title_activity_login"></activity>
<activity android:name=".HomeActivity"></activity>
<activity
android:name=".settings.SettingsActivity"
android:label="@string/title_activity_settings"
android:parentActivityName=".HomeActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".HomeActivity" />
</activity>
<activity
android:name=".SourcesActivity"
android:parentActivityName=".HomeActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".HomeActivity" />
</activity>
<activity
android:name=".UpsertSourceActivity"
android:exported="true"
android:parentActivityName=".SourcesActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".SourcesActivity" />
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity android:name=".ReaderActivity"></activity>
<activity
android:name=".ImageActivity"
android:theme="@style/Theme.AppCompat.ImageActivity"></activity>
<meta-data
android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" />
<meta-data
android:name="android.webkit.WebView.EnableSafeBrowsing"
android:value="true" />
<meta-data
android:name="android.max_aspect"
android:value="2.1" />
</application>
</manifest>
+4 -2
View File
@@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -52,7 +53,7 @@
android:value=".HomeActivity" />
</activity>
<activity
android:name=".AddSourceActivity"
android:name=".UpsertSourceActivity"
android:parentActivityName=".SourcesActivity"
android:exported="true">
<meta-data
@@ -69,7 +70,8 @@
android:name=".ReaderActivity">
</activity>
<activity
android:name=".ImageActivity">
android:name=".ImageActivity"
android:theme="@style/Theme.AppCompat.ImageActivity">
</activity>
<meta-data android:name="android.webkit.WebView.MetricsOptOut"
@@ -1,172 +0,0 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
class AddSourceActivity : AppCompatActivity(), DIAware {
private var mSpoutsValue: String? = null
private lateinit var binding: ActivityAddSourceBinding
override val di by closestDI()
private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAddSourceBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
maybeGetDetailsFromIntentSharing(intent, binding.sourceUri, binding.nameInput)
binding.saveBtn.setOnClickListener {
handleSaveSource(
binding.tags,
binding.nameInput.text.toString(),
binding.sourceUri.text.toString()
)
}
}
override fun onResume() {
super.onResume()
val baseUrl = appSettingsService.getBaseUrl()
if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) {
mustLoginToAddSource()
} else {
handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer)
}
}
private fun handleSpoutsSpinner(
spoutsSpinner: Spinner,
mProgress: ProgressBar,
formContainer: ConstraintLayout
) {
val spoutsKV = HashMap<String, String>()
spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) {
if (view != null) {
val spoutName = (view as TextView).text.toString()
mSpoutsValue = spoutsKV[spoutName]
}
}
override fun onNothingSelected(adapterView: AdapterView<*>) {
mSpoutsValue = null
}
}
fun handleSpoutFailure(networkIssue: Boolean = false) {
Toast.makeText(
this@AddSourceActivity,
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
Toast.LENGTH_SHORT
).show()
mProgress.visibility = View.GONE
}
CoroutineScope(Dispatchers.Main).launch {
try {
val items = repository.getSpouts()
if (items.isNotEmpty()) {
val itemsStrings = items.map { it.value.name }
for ((key, value) in items) {
spoutsKV[value.name] = key
}
mProgress.visibility = View.GONE
formContainer.visibility = View.VISIBLE
val spinnerArrayAdapter =
ArrayAdapter(
this@AddSourceActivity,
android.R.layout.simple_spinner_item,
itemsStrings
)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spoutsSpinner.adapter = spinnerArrayAdapter
} else {
handleSpoutFailure()
}
} catch (e: NetworkUnavailableException) {
handleSpoutFailure(networkIssue = true)
}
}
}
private fun maybeGetDetailsFromIntentSharing(
intent: Intent,
sourceUri: EditText,
nameInput: EditText
) {
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
}
}
private fun mustLoginToAddSource() {
Toast.makeText(this, getString(R.string.addStringNoUrl), Toast.LENGTH_SHORT).show()
val i = Intent(this, LoginActivity::class.java)
startActivity(i)
finish()
}
private fun handleSaveSource(tags: EditText, title: String, url: String) {
val sourceDetailsUnavailable =
title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
when {
sourceDetailsUnavailable -> {
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
}
else -> {
CoroutineScope(Dispatchers.Main).launch {
val successfullyAddedSource = repository.createSource(
title,
url,
mSpoutsValue!!,
tags.text.toString(),
"",
)
if (successfullyAddedSource) {
finish()
} else {
Toast.makeText(
this@AddSourceActivity,
R.string.cant_create_source,
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
}
@@ -1,7 +1,6 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
@@ -13,7 +12,11 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.core.view.doOnNextLayout
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.*
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
@@ -25,8 +28,10 @@ import bou.amine.apps.readerforselfossv2.android.background.LoadingWorker
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding
import bou.amine.apps.readerforselfossv2.android.fragments.FilterSheetFragment
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
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.removeBadge
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
@@ -37,17 +42,19 @@ import com.ashokvarma.bottomnavigation.TextBadgeItem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
import org.matomo.sdk.Tracker
import org.matomo.sdk.extra.TrackHelper
import java.security.MessageDigest
import java.util.concurrent.TimeUnit
private const val MIN_WIDTH_CARD_DP = 300
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware {
class HomeActivity :
AppCompatActivity(),
SearchView.OnQueryTextListener,
DIAware {
private var items: ArrayList<SelfossModel.Item> = ArrayList()
private var elementsShown: ItemType = ItemType.UNREAD
@@ -61,32 +68,30 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private lateinit var recyclerViewScrollListener: RecyclerView.OnScrollListener
private lateinit var binding: ActivityHomeBinding
private var recyclerAdapter: RecyclerView.Adapter<*>? = null
private var recyclerAdapter: ItemsAdapter<out RecyclerView.ViewHolder>? = null
private var fromTabShortcut: Boolean = false
private val settingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
appSettingsService.refreshUserSettings()
}
private val settingsLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
appSettingsService.refreshUserSettings()
}
override val di by closestDI()
private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance()
private val tracker : Tracker by instance()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
val view = binding.root
TrackHelper.track().screen("/home").with(tracker)
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
repository.offlineOverride = intent.getBooleanExtra("startOffline", false)
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
repository.offlineOverride = intent.getBooleanExtra("startOffline", false)
if (fromTabShortcut) {
elementsShown = ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position))
elementsShown =
ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position))
}
setContentView(view)
@@ -97,10 +102,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
handleSwipeRefreshLayout()
if (appSettingsService.isItemCachingEnabled()) {
CoroutineScope(Dispatchers.Main).launch {
CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch {
repository.tryToCacheItemsAndGetNewOnes()
CountingIdlingResourceSingleton.decrement()
}
}
}
@@ -109,42 +115,51 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.swipeRefreshLayout.setColorSchemeResources(
R.color.refresh_progress_1,
R.color.refresh_progress_2,
R.color.refresh_progress_3
R.color.refresh_progress_3,
)
binding.swipeRefreshLayout.setOnRefreshListener {
repository.offlineOverride = false
lastFetchDone = false
CoroutineScope(Dispatchers.Main).launch {
getElementsAccordingToTab()
binding.swipeRefreshLayout.isRefreshing = false
}
items.clear()
getElementsAccordingToTab()
binding.swipeRefreshLayout.isRefreshing = false
}
val swipeDirs =
if (appSettingsService.getPublicAccess()) {
0
} else {
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
}
val simpleItemTouchCallback =
object : ItemTouchHelper.SimpleCallback(
0,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
swipeDirs,
) {
override fun getSwipeDirs(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
viewHolder: RecyclerView.ViewHolder,
): Int =
if (elementsShown == ItemType.STARRED) {
0
} else {
super.getSwipeDirs(
recyclerView,
viewHolder
viewHolder,
)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
target: RecyclerView.ViewHolder,
): Boolean = false
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
override fun onSwiped(
viewHolder: RecyclerView.ViewHolder,
swipeDir: Int,
) {
val position = viewHolder.bindingAdapterPosition
val i = items.elementAtOrNull(position)
@@ -158,11 +173,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
getElementsAccordingToTab()
}
} else {
Toast.makeText(
this@HomeActivity,
"Found null when swiping at positon $position.",
Toast.LENGTH_LONG
).show()
Toast
.makeText(
this@HomeActivity,
"Found null when swiping at positon $position.",
Toast.LENGTH_LONG,
).show()
}
}
}
@@ -170,7 +186,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView)
}
private fun updateBottomBarBadgeCount(badge: TextBadgeItem, count: Int) {
private fun updateBottomBarBadgeCount(
badge: TextBadgeItem,
count: Int,
) {
if (count > 0) {
badge
.setText(count.toString())
@@ -180,17 +199,23 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
}
}
@Suppress("detekt:LongMethod")
private fun handleBottomBar() {
tabNewBadge = TextBadgeItem()
.setText("")
.setHideOnSelect(false).hide(false)
tabArchiveBadge = TextBadgeItem()
.setText("")
.setHideOnSelect(false).hide(false)
tabStarredBadge = TextBadgeItem()
.setText("")
.setHideOnSelect(false).hide(false)
tabNewBadge =
TextBadgeItem()
.setText("")
.setHideOnSelect(false)
.hide(false)
tabArchiveBadge =
TextBadgeItem()
.setText("")
.setHideOnSelect(false)
.hide(false)
tabStarredBadge =
TextBadgeItem()
.setText("")
.setHideOnSelect(false)
.hide(false)
if (appSettingsService.isDisplayUnreadCountEnabled()) {
lifecycleScope.launch {
@@ -217,19 +242,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
val tabNew =
BottomNavigationItem(
R.drawable.ic_tab_fiber_new_black_24dp,
getString(R.string.tab_new)
)
.setBadgeItem(tabNewBadge)
getString(R.string.tab_new),
).setBadgeItem(tabNewBadge)
val tabArchive =
BottomNavigationItem(
R.drawable.ic_tab_archive_black_24dp,
getString(R.string.tab_read)
)
.setBadgeItem(tabArchiveBadge)
getString(R.string.tab_read),
).setBadgeItem(tabArchiveBadge)
val tabStarred =
BottomNavigationItem(
R.drawable.ic_tab_favorite_black_24dp,
getString(R.string.tab_favs)
getString(R.string.tab_favs),
).setActiveColorResource(R.color.pink)
.setBadgeItem(tabStarredBadge)
@@ -259,28 +282,28 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
handleBottomBarActions()
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
handleGdprDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
handleRecurringTask()
CoroutineScope(Dispatchers.Main).launch {
CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch {
repository.handleDBActions()
CountingIdlingResourceSingleton.decrement()
}
getElementsAccordingToTab()
}
private fun handleGDPRDialog(GDPRShown: Boolean) {
private fun handleGdprDialog(gdprShown: Boolean) {
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
if (!GDPRShown) {
if (!gdprShown) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL,
"OK"
"OK",
) { dialog, _ ->
appSettingsService.settings.putBoolean("GDPR_shown", true)
dialog.dismiss()
@@ -291,118 +314,127 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun reloadLayoutManager() {
val currentManager = binding.recyclerView.layoutManager
val layoutManager: RecyclerView.LayoutManager
// This will only update the layout manager if settings changed
fun gridLayoutManager() {
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) {
is StaggeredGridLayoutManager ->
if (!appSettingsService.isCardViewEnabled()) {
layoutManager = GridLayoutManager(
this,
calculateNoOfColumns()
)
binding.recyclerView.layoutManager = layoutManager
gridLayoutManager()
}
is GridLayoutManager ->
if (appSettingsService.isCardViewEnabled()) {
layoutManager = StaggeredGridLayoutManager(
calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL
)
layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
binding.recyclerView.layoutManager = layoutManager
staggererdGridLayoutManager()
}
else ->
if (currentManager == null) {
if (!appSettingsService.isCardViewEnabled()) {
layoutManager = GridLayoutManager(
this,
calculateNoOfColumns()
)
binding.recyclerView.layoutManager = layoutManager
gridLayoutManager()
} else {
layoutManager = StaggeredGridLayoutManager(
calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL
)
layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
binding.recyclerView.layoutManager = layoutManager
staggererdGridLayoutManager()
}
}
}
}
private fun handleBottomBarActions() {
binding.bottomBar.setTabSelectedListener(object : BottomNavigationBar.OnTabSelectedListener {
override fun onTabUnselected(position: Int) = Unit
binding.bottomBar.setTabSelectedListener(
object : BottomNavigationBar.OnTabSelectedListener {
override fun onTabUnselected(position: Int) = Unit
override fun onTabReselected(position: Int) {
override fun onTabReselected(position: Int) {
when (val layoutManager = binding.recyclerView.adapter) {
is StaggeredGridLayoutManager ->
if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) {
getElementsAccordingToTab()
} else {
layoutManager.scrollToPositionWithOffset(0, 0)
}
when (val layoutManager = binding.recyclerView.adapter) {
is StaggeredGridLayoutManager ->
if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) {
getElementsAccordingToTab()
} else {
layoutManager.scrollToPositionWithOffset(0, 0)
}
is GridLayoutManager ->
if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
getElementsAccordingToTab()
} else {
layoutManager.scrollToPositionWithOffset(0, 0)
}
else -> Unit
is GridLayoutManager ->
if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
getElementsAccordingToTab()
} else {
layoutManager.scrollToPositionWithOffset(0, 0)
}
else -> Unit
}
}
}
override fun onTabSelected(position: Int) {
offset = 0
lastFetchDone = false
override fun onTabSelected(position: Int) {
offset = 0
lastFetchDone = false
elementsShown = ItemType.fromInt(position + 1)
getElementsAccordingToTab()
binding.recyclerView.scrollToPosition(0)
elementsShown = ItemType.fromInt(position + 1)
getElementsAccordingToTab()
binding.recyclerView.scrollToPosition(0)
fetchOnEmptyList()
}
})
fetchOnEmptyList()
}
},
)
}
fun fetchOnEmptyList() {
binding.recyclerView.doOnNextLayout {
// TODO: do if last element (or is empty ?)
getElementsAccordingToTab(true)
}
}
private fun handleInfiniteScroll() {
recyclerViewScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(localRecycler: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) {
val lastVisibleItem = getLastVisibleItem()
recyclerViewScrollListener =
object : RecyclerView.OnScrollListener() {
override fun onScrolled(
localRecycler: RecyclerView,
dx: Int,
dy: Int,
) {
if (dy > 0) {
val lastVisibleItem = getLastVisibleItem()
if (lastVisibleItem == (items.size - 1) && items.size < maxItemNumber()) {
getElementsAccordingToTab(appendResults = true)
if (lastVisibleItem == (items.size - 1) && items.size < maxItemNumber()) {
getElementsAccordingToTab(appendResults = true)
}
}
}
}
}
binding.recyclerView.clearOnScrollListeners()
binding.recyclerView.addOnScrollListener(recyclerViewScrollListener)
}
private fun getLastVisibleItem() : Int {
return when (val manager = binding.recyclerView.layoutManager) {
is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
null
).last()
private fun getLastVisibleItem(): Int =
when (val manager = binding.recyclerView.layoutManager) {
is StaggeredGridLayoutManager ->
manager
.findLastCompletelyVisibleItemPositions(
null,
).last()
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
else -> 0
}
}
private fun mayBeEmpty() =
if (items.isEmpty()) {
@@ -411,73 +443,96 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.emptyText.visibility = View.GONE
}
fun getElementsAccordingToTab(
appendResults: Boolean = false
) {
offset = if (appendResults && items.size > 0) {
items.size - 1
} else {
0
}
fun getElementsAccordingToTab(appendResults: Boolean = false) {
offset =
if (appendResults && items.size > 0) {
items.size - 1
} else {
0
}
firstVisible = if (appendResults) firstVisible else 0
getItems(appendResults, elementsShown)
}
private fun getItems(appendResults: Boolean, itemType: ItemType) {
CoroutineScope(Dispatchers.Main).launch {
private fun getItems(
appendResults: Boolean,
itemType: ItemType,
) {
@Suppress("detekt:ComplexCondition")
if ((appendResults && items.size > 0) || (!appendResults && items.size == 0)) {
CountingIdlingResourceSingleton.increment()
binding.swipeRefreshLayout.isRefreshing = true
repository.displayedItems = itemType
items = if (appendResults) {
repository.getOlderItems()
} else {
repository.getNewerItems()
CoroutineScope(Dispatchers.IO).launch {
repository.displayedItems = itemType
items =
if (appendResults) {
repository.getOlderItems()
} else {
repository.getNewerItems()
}
CountingIdlingResourceSingleton.increment()
launch(Dispatchers.Main) {
binding.swipeRefreshLayout.isRefreshing = false
handleListResult()
CountingIdlingResourceSingleton.decrement()
}
CountingIdlingResourceSingleton.decrement()
}
binding.swipeRefreshLayout.isRefreshing = false
} else {
handleListResult()
}
}
private fun handleListResult(appendResults: Boolean = false) {
val oldManager = binding.recyclerView.layoutManager
if (appendResults) {
val oldManager = binding.recyclerView.layoutManager
firstVisible = when (oldManager) {
is StaggeredGridLayoutManager ->
oldManager.findFirstCompletelyVisibleItemPositions(null).last()
is GridLayoutManager ->
oldManager.findFirstCompletelyVisibleItemPosition()
else -> 0
}
firstVisible =
when (oldManager) {
is StaggeredGridLayoutManager ->
oldManager.findFirstCompletelyVisibleItemPositions(null).last()
is GridLayoutManager ->
oldManager.findFirstCompletelyVisibleItemPosition()
else -> 0
}
}
if (recyclerAdapter == null) {
@Suppress("detekt:ComplexCondition")
if (recyclerAdapter == null ||
(
(recyclerAdapter is ItemListAdapter && appSettingsService.isCardViewEnabled()) ||
(recyclerAdapter is ItemCardAdapter && !appSettingsService.isCardViewEnabled())
)
) {
if (appSettingsService.isCardViewEnabled()) {
recyclerAdapter =
ItemCardAdapter(
this,
items,
) {
updateItems(it)
}
ItemCardAdapter(
this,
items,
) {
updateItems(it)
}
} else {
recyclerAdapter =
ItemListAdapter(
this,
items,
) {
updateItems(it)
}
ItemListAdapter(
this,
items,
) {
updateItems(it)
}
binding.recyclerView.addItemDecoration(
DividerItemDecoration(
this@HomeActivity,
DividerItemDecoration.VERTICAL
)
DividerItemDecoration.VERTICAL,
),
)
}
binding.recyclerView.adapter = recyclerAdapter
} else {
(recyclerAdapter as ItemsAdapter<*>).updateAllItems(items)
recyclerAdapter!!.updateAllItems(items)
}
reloadBadges()
@@ -485,9 +540,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
}
private fun reloadBadges() {
if (appSettingsService.isDisplayUnreadCountEnabled() || appSettingsService.isDisplayAllCountEnabled()) {
if (appSettingsService.isInfiniteLoadingEnabled() ||
appSettingsService.isDisplayUnreadCountEnabled() ||
appSettingsService.isDisplayAllCountEnabled()
) {
CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch {
repository.reloadBadges()
CountingIdlingResourceSingleton.decrement()
}
}
}
@@ -495,7 +555,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun calculateNoOfColumns(): Int {
val displayMetrics = resources.displayMetrics
val dpWidth = displayMetrics.widthPixels / displayMetrics.density
return (dpWidth / 300).toInt()
return (dpWidth / MIN_WIDTH_CARD_DP).toInt()
}
override fun onQueryTextChange(p0: String?): Boolean {
@@ -517,16 +577,25 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.home_menu, menu)
if (appSettingsService.getPublicAccess()) {
menu.removeItem(R.id.readAll)
menu.removeItem(R.id.action_sources)
}
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.getActionView() as SearchView
val searchView = searchItem.actionView as SearchView
searchView.setOnQueryTextListener(this)
return true
}
private fun needsConfirmation(titleRes: Int, messageRes: Int, doFn: () -> Unit) {
AlertDialog.Builder(this@HomeActivity)
private fun needsConfirmation(
titleRes: Int,
messageRes: Int,
doFn: () -> Unit,
) {
AlertDialog
.Builder(this@HomeActivity)
.setMessage(messageRes)
.setTitle(titleRes)
.setPositiveButton(android.R.string.ok) { _, _ -> doFn() }
@@ -535,80 +604,113 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
.show()
}
@Suppress("detekt:ReturnCount", "detekt:LongMethod")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.issue_tracker -> {
baseContext.openUrlInBrowserAsNewTask(AppSettingsService.BUG_URL)
return true
}
R.id.action_filter -> {
val filterSheetFragment = FilterSheetFragment()
filterSheetFragment.show(supportFragmentManager, FilterSheetFragment.TAG)
return true
}
R.id.refresh -> {
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
// TODO: Use Dispatchers.IO
CoroutineScope(Dispatchers.Main).launch {
CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch {
val updatedRemote = repository.updateRemote()
if (updatedRemote) {
// TODO: Send toast messages from the repository
Toast.makeText(
this@HomeActivity,
R.string.refresh_success_response, Toast.LENGTH_LONG
)
.show()
} else {
Toast.makeText(
this@HomeActivity,
R.string.refresh_failer_message,
Toast.LENGTH_SHORT
).show()
CountingIdlingResourceSingleton.increment()
launch(Dispatchers.Main) {
if (updatedRemote) {
Toast
.makeText(
this@HomeActivity,
R.string.refresh_success_response,
Toast.LENGTH_LONG,
).show()
} else {
Toast
.makeText(
this@HomeActivity,
R.string.refresh_failer_message,
Toast.LENGTH_SHORT,
).show()
}
CountingIdlingResourceSingleton.decrement()
}
CountingIdlingResourceSingleton.decrement()
}
}
return true
}
R.id.readAll -> {
if (elementsShown == ItemType.UNREAD) {
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
CountingIdlingResourceSingleton.increment()
binding.swipeRefreshLayout.isRefreshing = true
CoroutineScope(Dispatchers.Main).launch {
CoroutineScope(Dispatchers.IO).launch {
val success = repository.markAllAsRead(items)
if (success) {
Toast.makeText(
this@HomeActivity,
R.string.all_posts_read,
Toast.LENGTH_SHORT
).show()
tabNewBadge.removeBadge()
CountingIdlingResourceSingleton.increment()
launch(Dispatchers.Main) {
if (success) {
Toast
.makeText(
this@HomeActivity,
R.string.all_posts_read,
Toast.LENGTH_SHORT,
).show()
tabNewBadge.removeBadge()
getElementsAccordingToTab()
} else {
Toast.makeText(
this@HomeActivity,
R.string.all_posts_not_read,
Toast.LENGTH_SHORT
).show()
getElementsAccordingToTab()
} else {
Toast
.makeText(
this@HomeActivity,
R.string.all_posts_not_read,
Toast.LENGTH_SHORT,
).show()
}
binding.swipeRefreshLayout.isRefreshing = false
CountingIdlingResourceSingleton.decrement()
}
handleListResult()
binding.swipeRefreshLayout.isRefreshing = false
CountingIdlingResourceSingleton.decrement()
}
}
}
return true
}
R.id.action_disconnect -> {
CoroutineScope(Dispatchers.Main).launch {
repository.logout()
needsConfirmation(
R.string.confirm_disconnect_title,
R.string.confirm_disconnect_description,
) {
runBlocking {
repository.logout()
}
val intent = Intent(this, LoginActivity::class.java)
this.startActivity(intent)
finish()
}
this@HomeActivity.finish()
val intent = Intent(this, LoginActivity::class.java)
this.startActivity(intent)
return true
}
R.id.action_settings -> {
settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
return true
}
R.id.action_sources -> {
startActivity(Intent(this, SourcesActivity::class.java))
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
@@ -627,20 +729,30 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun handleRecurringTask() {
if (appSettingsService.isPeriodicRefreshEnabled()) {
val myConstraints = Constraints.Builder()
.setRequiresBatteryNotLow(true)
.setRequiresCharging(appSettingsService.isRefreshWhenChargingOnlyEnabled())
.setRequiresStorageNotLow(true)
.build()
val myConstraints =
Constraints
.Builder()
.setRequiresBatteryNotLow(true)
.setRequiresCharging(appSettingsService.isRefreshWhenChargingOnlyEnabled())
.setRequiresStorageNotLow(true)
.build()
val backgroundWork =
PeriodicWorkRequestBuilder<LoadingWorker>(appSettingsService.getRefreshMinutes(), TimeUnit.MINUTES)
.setConstraints(myConstraints)
PeriodicWorkRequestBuilder<LoadingWorker>(
appSettingsService.getRefreshMinutes(),
TimeUnit.MINUTES,
).setConstraints(myConstraints)
.addTag("selfoss-loading")
.build()
WorkManager.getInstance(baseContext).enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork)
WorkManager
.getInstance(
baseContext,
).enqueueUniquePeriodicWork(
"selfoss-loading",
ExistingPeriodicWorkPolicy.KEEP,
backgroundWork,
)
}
}
}
@@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.android
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
@@ -10,8 +11,8 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivityImageBindin
import bou.amine.apps.readerforselfossv2.android.fragments.ImageFragment
class ImageActivity : AppCompatActivity() {
private lateinit var allImages : ArrayList<String>
private var position : Int = 0
private lateinit var allImages: ArrayList<String>
private var position: Int = 0
private lateinit var binding: ActivityImageBinding
@@ -23,7 +24,6 @@ class ImageActivity : AppCompatActivity() {
setContentView(view)
setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayShowTitleEnabled(false)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
allImages = intent.getStringArrayListExtra("allImages") as ArrayList<String>
@@ -31,12 +31,52 @@ class ImageActivity : AppCompatActivity() {
binding.pager.adapter = ScreenSlidePagerAdapter(this)
binding.pager.setCurrentItem(position, false)
val transitionListener =
object : MotionLayout.TransitionListener {
override fun onTransitionStarted(
motionLayout: MotionLayout?,
startId: Int,
endId: Int,
) {
// Nothing
}
override fun onTransitionChange(
motionLayout: MotionLayout?,
startId: Int,
endId: Int,
progress: Float,
) {
// Nothing
}
override fun onTransitionCompleted(
motionLayout: MotionLayout?,
currentId: Int,
) {
if (motionLayout?.currentState == binding.root.endState) {
onBackPressedDispatcher.onBackPressed()
overridePendingTransition(0, 0)
}
}
override fun onTransitionTrigger(
motionLayout: MotionLayout?,
triggerId: Int,
positive: Boolean,
progress: Float,
) {
// Nothing
}
}
binding.root.setTransitionListener(transitionListener)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
onBackPressedDispatcher.onBackPressed()
return true
}
}
@@ -44,8 +84,9 @@ class ImageActivity : AppCompatActivity() {
return super.onOptionsItemSelected(item)
}
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
private inner class ScreenSlidePagerAdapter(
fa: FragmentActivity,
) : FragmentStateAdapter(fa) {
override fun getItemCount(): Int = allImages.size
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])
@@ -4,6 +4,7 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.view.Menu
@@ -16,6 +17,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
@@ -23,18 +25,16 @@ import com.mikepenz.aboutlibraries.LibsBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.acra.ACRA
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
import org.matomo.sdk.Tracker
import org.matomo.sdk.extra.DimensionQueue
import org.matomo.sdk.extra.DownloadTracker
import org.matomo.sdk.extra.TrackHelper
import java.security.MessageDigest
private const val MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED = 3
class LoginActivity : AppCompatActivity(), DIAware {
class LoginActivity :
AppCompatActivity(),
DIAware {
private var inValidCount: Int = 0
private var isWithLogin = false
@@ -43,17 +43,10 @@ class LoginActivity : AppCompatActivity(), DIAware {
override val di by closestDI()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
private val tracker: Tracker by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
TrackHelper.track().download()
.identifier(DownloadTracker.Extra.ApkChecksum(applicationContext))
.with(tracker)
TrackHelper.track().screen("/login").with(tracker)
handleTheme()
binding = ActivityLoginBinding.inflate(layoutInflater)
@@ -67,29 +60,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
if (appSettingsService.getBaseUrl().isNotEmpty()) {
showProgress(true)
// This should be reverted when "old" users connected with a non-selfoss rss
// are handled. Revert to "simple" way.
CoroutineScope(Dispatchers.Main).launch {
try {
val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
if (!errorFetching && !displaySelfossOnly) {
goToMain()
} else {
showProgress(false)
if (displaySelfossOnly) {
Toast.makeText(
applicationContext,
R.string.application_selfoss_only,
Toast.LENGTH_LONG
).show()
}
repository.logout()
}
} catch (e: Throwable) {
repository.logout()
showProgress(false)
}
}
goToMain()
}
handleActions()
@@ -101,7 +72,6 @@ class LoginActivity : AppCompatActivity(), DIAware {
}
private fun handleActions() {
binding.passwordView.setOnEditorActionListener(
TextView.OnEditorActionListener { _, id, _ ->
if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
@@ -109,7 +79,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
return@OnEditorActionListener true
}
false
}
},
)
binding.signInButton.setOnClickListener { attemptLogin() }
@@ -130,24 +100,21 @@ class LoginActivity : AppCompatActivity(), DIAware {
alertDialog.setMessage(getString(R.string.base_url_error))
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL,
"OK"
"OK",
) { dialog, _ -> dialog.dismiss() }
alertDialog.show()
}
}
private fun goToMain() {
CoroutineScope(Dispatchers.Main).launch {
repository.updateApiVersion()
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
tracker.userId = String(messageDigest.digest())
val mDimensionQueue = DimensionQueue(tracker)
mDimensionQueue.add(1, appSettingsService.getApiVersion().toString())
tracker.isOptOut = !appSettingsService.isAnalyticsEnabled()
CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch {
repository.updateApiInformation()
ACRA.errorReporter.putCustomData(
"SELFOSS_API_VERSION",
appSettingsService.getApiVersion().toString(),
)
CountingIdlingResourceSingleton.decrement()
}
val intent = Intent(this, HomeActivity::class.java)
startActivity(intent)
@@ -160,83 +127,133 @@ class LoginActivity : AppCompatActivity(), DIAware {
binding.urlView.error = getString(R.string.wrong_infos)
binding.loginView.error = getString(R.string.wrong_infos)
binding.passwordView.error = getString(R.string.wrong_infos)
binding.urlView.requestFocus()
showProgress(false)
}
@Suppress("detekt:LongMethod")
private fun attemptLogin() {
// Reset errors.
binding.urlView.error = null
binding.loginView.error = null
binding.passwordView.error = null
// Store values at the time of the login attempt.
val url = binding.urlView.text.toString().trim()
val login = binding.loginView.text.toString().trim()
val password = binding.passwordView.text.toString().trim()
val url =
binding.urlView.text
.toString()
.trim()
val login =
binding.loginView.text
.toString()
.trim()
val password =
binding.passwordView.text
.toString()
.trim()
var cancel = false
var focusView: View? = null
val cancelUrl = failInvalidUrl(url)
if (cancelUrl) return
val cancelDetails = failLoginDetails(password, login)
if (cancelDetails) return
showProgress(true)
if (url.isBaseUrlInvalid()) {
binding.urlView.error = getString(R.string.login_url_problem)
focusView = binding.urlView
cancel = true
inValidCount++
if (inValidCount == 3) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.warning_wrong_url))
alertDialog.setMessage(getString(R.string.text_wrong_url))
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL,
"OK"
) { dialog, _ -> dialog.dismiss() }
alertDialog.show()
inValidCount = 0
appSettingsService.updateSelfSigned(binding.selfSigned.isChecked)
repository.refreshLoginInformation(url, login, password)
CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch {
try {
repository.updateApiInformation()
val result = repository.login()
CountingIdlingResourceSingleton.increment()
launch(Dispatchers.Main) {
if (result) {
val errorFetching = repository.checkIfFetchFails()
if (!errorFetching) {
goToMain()
} else {
preferenceError()
}
} else {
preferenceError()
}
CountingIdlingResourceSingleton.decrement()
}
} catch (e: Exception) {
CountingIdlingResourceSingleton.increment()
launch(Dispatchers.Main) {
if (e.message?.startsWith("No transformation found") == true) {
Toast
.makeText(
applicationContext,
R.string.application_selfoss_only,
Toast.LENGTH_LONG,
).show()
preferenceError()
}
CountingIdlingResourceSingleton.decrement()
}
} finally {
CountingIdlingResourceSingleton.decrement()
}
}
}
private fun failLoginDetails(
password: String,
login: String,
): Boolean {
var lastFocusedView: View? = null
var cancel = false
if (isWithLogin) {
if (TextUtils.isEmpty(password)) {
binding.passwordView.error = getString(R.string.error_invalid_password)
focusView = binding.passwordView
lastFocusedView = binding.passwordView
cancel = true
}
if (TextUtils.isEmpty(login)) {
binding.loginView.error = getString(R.string.error_field_required)
focusView = binding.loginView
lastFocusedView = binding.loginView
cancel = true
}
}
maybeCancelAndFocusView(cancel, lastFocusedView)
return cancel
}
private fun failInvalidUrl(url: String): Boolean {
val focusView = binding.urlView
var cancel = false
if (url.isBaseUrlInvalid()) {
cancel = true
binding.urlView.error = getString(R.string.login_url_problem)
inValidCount++
if (inValidCount == MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.warning_wrong_url))
alertDialog.setMessage(getString(R.string.text_wrong_url))
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL,
"OK",
) { dialog, _ -> dialog.dismiss() }
alertDialog.show()
inValidCount = 0
}
}
maybeCancelAndFocusView(cancel, focusView)
return cancel
}
private fun maybeCancelAndFocusView(
cancel: Boolean,
focusView: View?,
) {
if (cancel) {
focusView?.requestFocus()
} else {
showProgress(true)
repository.refreshLoginInformation(url, login, password)
CoroutineScope(Dispatchers.Main).launch {
val result = repository.login()
if (result) {
val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
if (!errorFetching && !displaySelfossOnly) {
goToMain()
} else {
if (displaySelfossOnly) {
Toast.makeText(
applicationContext,
R.string.application_selfoss_only,
Toast.LENGTH_LONG
).show()
}
preferenceError()
}
} else {
preferenceError()
}
showProgress(false)
}
}
}
@@ -248,12 +265,13 @@ class LoginActivity : AppCompatActivity(), DIAware {
.animate()
.setDuration(shortAnimTime.toLong())
.alpha(
if (show) 0F else 1F
).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
}
}
if (show) 0F else 1F,
).setListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
}
},
)
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
@@ -261,12 +279,13 @@ class LoginActivity : AppCompatActivity(), DIAware {
.animate()
.setDuration(shortAnimTime.toLong())
.alpha(
if (show) 1F else 0F
).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
}
}
if (show) 1F else 0F,
).setListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
}
},
)
}
@@ -277,13 +296,26 @@ class LoginActivity : AppCompatActivity(), DIAware {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.issue_tracker -> {
val browserIntent =
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.BUG_URL))
startActivity(browserIntent)
return true
}
R.id.about -> {
LibsBuilder()
.withAboutIconShown(true)
.withAboutVersionShown(true)
.withAboutSpecial2("Bug reports")
.withAboutSpecial2Description(AppSettingsService.BUG_URL)
.withAboutSpecial1("Project Page")
.withAboutSpecial1Description(AppSettingsService.SOURCE_URL)
.withShowLoadingProgress(false)
.start(this)
true
}
else -> super.onOptionsItemSelected(item)
}
}
@@ -8,7 +8,6 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
@@ -3,29 +3,23 @@ package bou.amine.apps.readerforselfossv2.android
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.widget.ImageView
import android.widget.Toast
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.multidex.MultiDexApplication
import bou.amine.apps.readerforselfossv2.DI.networkModule
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
import bou.amine.apps.readerforselfossv2.android.testing.TestingHelper
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.di.networkModule
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.github.ln_12.library.ConnectivityStatus
import bou.amine.apps.readerforselfossv2.service.ConnectivityService
import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import org.acra.ACRA
import org.acra.ReportField
@@ -34,31 +28,35 @@ import org.acra.config.toast
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import org.kodein.di.*
import org.matomo.sdk.Matomo
import org.matomo.sdk.Tracker
import org.matomo.sdk.TrackerBuilder
class MyApp : MultiDexApplication(), DIAware {
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.bind
import org.kodein.di.instance
import org.kodein.di.singleton
class MyApp :
MultiDexApplication(),
DIAware {
override val di by DI.lazy {
bind<AppSettingsService>() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess() || TestingHelper().isUnitTest()) }
import(networkModule)
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
bind<Repository>() with singleton { Repository(instance(), instance(), isConnectionAvailable, instance()) }
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
bind<Tracker>() with singleton { TrackerBuilder.createDefault("https://matomo.amine-louveau.fr/matomo.php", if (BuildConfig.DEBUG) 4 else 5).build(
Matomo.getInstance(applicationContext)) }
bind<ConnectivityService>() with singleton { ConnectivityService() }
bind<Repository>() with
singleton {
Repository(
instance(),
instance(),
instance(),
instance(),
)
}
}
private val repository: Repository by instance()
private val viewModel: AppViewModel by instance()
private val connectivityStatus: ConnectivityStatus by instance()
private val driverFactory: DriverFactory by instance()
// TODO: handle with the "previous" way
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
private val connectivityService: ConnectivityService by instance()
override fun onCreate() {
super.onCreate()
@@ -69,49 +67,76 @@ class MyApp : MultiDexApplication(), DIAware {
handleNotificationChannels()
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
ProcessLifecycleOwner.get().lifecycle.addObserver(
AppLifeCycleObserver(
connectivityService,
),
)
CoroutineScope(Dispatchers.Main).launch {
viewModel.networkAvailableProvider.collect { networkAvailable ->
val toastMessage = if (networkAvailable) {
repository.handleDBActions()
R.string.network_connectivity_retrieved
} else {
R.string.network_connectivity_lost
CoroutineScope(Dispatchers.Default).launch {
connectivityService.networkAvailableProvider.collect { networkAvailable ->
val toastMessage =
if (networkAvailable) {
repository.handleDBActions()
R.string.network_connectivity_retrieved
} else {
R.string.network_connectivity_lost
}
launch(Dispatchers.Main) {
Toast
.makeText(
applicationContext,
toastMessage,
Toast.LENGTH_SHORT,
).show()
}
Toast.makeText(
applicationContext,
toastMessage,
Toast.LENGTH_SHORT
).show()
}
}
}
repository.migrate(driverFactory)
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
initAcra {
sendReportsInDevMode = false
reportFormat = StringFormat.JSON
reportContent = listOf(
ReportField.REPORT_ID, ReportField.INSTALLATION_ID,
ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME,
ReportField.BUILD, ReportField.ANDROID_VERSION, ReportField.BRAND, ReportField.PHONE_MODEL,
ReportField.AVAILABLE_MEM_SIZE, ReportField.TOTAL_MEM_SIZE,
ReportField.STACK_TRACE, ReportField.APPLICATION_LOG, ReportField.LOGCAT,
ReportField.INITIAL_CONFIGURATION, ReportField.CRASH_CONFIGURATION, ReportField.IS_SILENT,
ReportField.USER_APP_START_DATE, ReportField.USER_COMMENT, ReportField.USER_CRASH_DATE, ReportField.USER_EMAIL, ReportField.CUSTOM_DATA)
reportContent =
listOf(
ReportField.REPORT_ID,
ReportField.INSTALLATION_ID,
ReportField.APP_VERSION_CODE,
ReportField.APP_VERSION_NAME,
ReportField.BUILD,
ReportField.ANDROID_VERSION,
ReportField.BRAND,
ReportField.PHONE_MODEL,
ReportField.AVAILABLE_MEM_SIZE,
ReportField.TOTAL_MEM_SIZE,
ReportField.STACK_TRACE,
ReportField.APPLICATION_LOG,
ReportField.LOGCAT,
ReportField.INITIAL_CONFIGURATION,
ReportField.CRASH_CONFIGURATION,
ReportField.IS_SILENT,
ReportField.USER_APP_START_DATE,
ReportField.USER_COMMENT,
ReportField.USER_CRASH_DATE,
ReportField.USER_EMAIL,
ReportField.CUSTOM_DATA,
)
toast {
//required
// required
text = getString(R.string.crash_toast_text)
length = Toast.LENGTH_SHORT
}
httpSender {
uri = "https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
basicAuthLogin = "LMTlLZuazADohTCm"
basicAuthPassword = "he6ghHp83F0PYPfh"
uri =
"https://bugs.amine-bouabdallaoui.fr/report" // best guess, you may need to adjust this
basicAuthLogin = "qMEscjj89Gwt6cPR"
basicAuthPassword = "Yo58QFlGzFaWlBzP"
httpMethod = HttpSender.Method.POST
}
}
@@ -123,11 +148,16 @@ class MyApp : MultiDexApplication(), DIAware {
val name = getString(R.string.notification_channel_sync)
val importance = NotificationManager.IMPORTANCE_LOW
val mChannel = NotificationChannel(AppSettingsService.syncChannelId, name, importance)
val mChannel = NotificationChannel(AppSettingsService.SYNC_CHANNEL_ID, name, importance)
val newItemsChannelname = getString(R.string.new_items_channel_sync)
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
val newItemsChannelmChannel = NotificationChannel(AppSettingsService.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
val newItemsChannelmChannel =
NotificationChannel(
AppSettingsService.NEW_ITEMS_CHANNEL,
newItemsChannelname,
newItemsChannelimportance,
)
notificationManager.createNotificationChannel(mChannel)
notificationManager.createNotificationChannel(newItemsChannelmChannel)
@@ -138,26 +168,28 @@ class MyApp : MultiDexApplication(), DIAware {
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, e ->
if (e is NoClassDefFoundError && e.stackTrace.asList().any {
if (e is NoClassDefFoundError &&
e.stackTrace.asList().any {
it.toString().contains("android.view.ViewDebug")
}) {
}
) {
// Nothing
} else {
oldHandler.uncaughtException(thread, e)
}
}
}
class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver {
class AppLifeCycleObserver(
val connectivityService: ConnectivityService,
) : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
repository.connectionMonitored = true
connectivityStatus.start()
connectivityService.start()
}
override fun onPause(owner: LifecycleOwner) {
repository.connectionMonitored = false
connectivityStatus.stop()
connectivityService.stop()
super.onPause(owner)
}
}
@@ -22,34 +22,22 @@ import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
class ReaderActivity : AppCompatActivity(), DIAware {
class ReaderActivity :
AppCompatActivity(),
DIAware {
private var currentItem: Int = 0
private lateinit var toolbarMenu: Menu
private var toolbarMenu: Menu? = null
private lateinit var binding: ActivityReaderBinding
private var allItems: ArrayList<SelfossModel.Item> = ArrayList()
override val di by closestDI()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService