Compare commits
	
		
			256 Commits
		
	
	
		
			v161810287
			...
			29d99fca17
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 29d99fca17 | |||
| d7d4eb3974 | |||
| cedb207eca | |||
| 
						 | 
					b9e91f30ef | ||
| 6a8c2d7fcd | |||
| 60a908a44a | |||
| 6b887ff74b | |||
| 721a15ec21 | |||
| a2933ac763 | |||
| e1efe9643c | |||
| 90242ae801 | |||
| 0c88f33981 | |||
| 
						 | 
					5e13a8f20f | ||
| ca4b7ada97 | |||
| 
						 | 
					62a82b01b8 | ||
| 1994fa2f7d | |||
| ae32cbfb6f | |||
| 2dff3d9191 | |||
| c0ae0466c2 | |||
| 58b0574cf9 | |||
| 5472c607cd | |||
| f95cb20408 | |||
| 5640b7e56c | |||
| fa697f1313 | |||
| a12623f8e4 | |||
| abba04839a | |||
| 2e38639910 | |||
| 
						 | 
					db78717eec | ||
| 58a498868d | |||
| 46e723a238 | |||
| 
						 | 
					304b6c3761 | ||
| 33fb04956c | |||
| 6e3381fb61 | |||
| 56720659ee | |||
| 626c9e2797 | |||
| 
						 | 
					05cd96afc0 | ||
| c8faa8984f | |||
| a025efbf3b | |||
| e62e04e13b | |||
| e6b5ea4e67 | |||
| c3148c6744 | |||
| 
						 | 
					193f538d29 | ||
| 
						 | 
					7f45db0473 | ||
| d89423b9ac | |||
| 
						 | 
					25fd869c01 | ||
| d1d956b77a | |||
| 
						 | 
					41c14362a8 | ||
| 6fa8c901fc | |||
| 
						 | 
					db124ab9de | ||
| 
						 | 
					953940690d | ||
| 918661be2d | |||
| 
						 | 
					7b8a5c9a56 | ||
| 2d5ab7bf0c | |||
| 9ba281befb | |||
| 00c8eed034 | |||
| a1e4f89cd1 | |||
| 36a43b3861 | |||
| 
						 | 
					aa6d470f40 | ||
| 
						 | 
					0046a8a477 | ||
| 
						 | 
					43ff9d186a | ||
| 
						 | 
					73dae304be | ||
| 
						 | 
					66103a451b | ||
| 
						 | 
					600c62316d | ||
| 
						 | 
					d370ddc4d1 | ||
| 
						 | 
					4049f6a5c7 | ||
| 
						 | 
					3e96ac207e | ||
| 
						 | 
					84f1ab12cf | ||
| 
						 | 
					f48f6ed788 | ||
| 
						 | 
					e517803bd8 | ||
| 
						 | 
					3eaf390790 | ||
| 
						 | 
					6de54d63e6 | ||
| 
						 | 
					dd7a2f476b | ||
| 
						 | 
					1485cc05f4 | ||
| 
						 | 
					d1dad3e61a | ||
| 
						 | 
					e5024b0420 | ||
| 
						 | 
					9b01692c55 | ||
| 
						 | 
					33aa587d36 | ||
| 
						 | 
					12e0766803 | ||
| 
						 | 
					a8721ad7a4 | ||
| 
						 | 
					bc5e882894 | ||
| 
						 | 
					e3460322b1 | ||
| 
						 | 
					7e3288a076 | ||
| 
						 | 
					ddc754ec25 | ||
| 
						 | 
					134a0766d6 | ||
| 
						 | 
					69da932ab5 | ||
| 
						 | 
					592fb6328a | ||
| 
						 | 
					a0aead6491 | ||
| 
						 | 
					722b6cc06d | ||
| 
						 | 
					6d7c4b40f6 | ||
| 
						 | 
					f538ed39fc | ||
| 
						 | 
					65821492ad | ||
| 
						 | 
					6ede718a9f | ||
| 
						 | 
					f1757937a4 | ||
| 2bd2e0a953 | |||
| 
						 | 
					b5aef28af0 | ||
| 
						 | 
					45747a1506 | ||
| 
						 | 
					c6e2e08bcb | ||
| 
						 | 
					25bf18661e | ||
| 
						 | 
					6b088dcd24 | ||
| 
						 | 
					d2b18e1880 | ||
| 
						 | 
					eec7c94e98 | ||
| 
						 | 
					d1f8fcacc0 | ||
| 
						 | 
					07e4a33cbd | ||
| 
						 | 
					f6317f566e | ||
| 
						 | 
					9f51e4e6a5 | ||
| 
						 | 
					750604a31f | ||
| 
						 | 
					392eee0ad4 | ||
| 
						 | 
					37e7b987ee | ||
| 
						 | 
					9eac51e729 | ||
| 
						 | 
					fa9cce6783 | ||
| 
						 | 
					f0d4b63a97 | ||
| 
						 | 
					83eeb11388 | ||
| 
						 | 
					01f746f33d | ||
| 
						 | 
					200851894b | ||
| 
						 | 
					862e5cf4ab | ||
| 
						 | 
					0b07f2a407 | ||
| 
						 | 
					9ba6feef0b | ||
| 
						 | 
					63a0638522 | ||
| 
						 | 
					f9a4e6e363 | ||
| 
						 | 
					6b40fd4bdc | ||
| 
						 | 
					04c7776466 | ||
| 
						 | 
					92c335b4e1 | ||
| 
						 | 
					17251e576b | ||
| 
						 | 
					62ea782429 | ||
| 
						 | 
					f99474e3c1 | ||
| 
						 | 
					57ac8f428f | ||
| 
						 | 
					9cc1adbf15 | ||
| 
						 | 
					1d9a440ae7 | ||
| 
						 | 
					511553806c | ||
| 
						 | 
					87e7d7c4fe | ||
| 
						 | 
					ec87089310 | ||
| 
						 | 
					d8478ebb01 | ||
| 
						 | 
					600adc81b5 | ||
| 
						 | 
					ddac2870af | ||
| 8d9c8c1394 | |||
| 
						 | 
					b59c3bcb23 | ||
| 7f554adba5 | |||
| 
						 | 
					21ce061282 | ||
| 
						 | 
					bdb71e9b14 | ||
| 
						 | 
					df22e7de15 | ||
| 
						 | 
					6b3550396b | ||
| 
						 | 
					c70f1e31a6 | ||
| 
						 | 
					695670e944 | ||
| 
						 | 
					1028826788 | ||
| 
						 | 
					82a8977c96 | ||
| 
						 | 
					07d9ce1054 | ||
| 
						 | 
					7da7d49277 | ||
| 
						 | 
					9b45365441 | ||
| 
						 | 
					91a7464bce | ||
| 
						 | 
					51add226eb | ||
| 
						 | 
					332e9f5108 | ||
| 
						 | 
					0b91087c07 | ||
| 
						 | 
					ebbb1ba0f8 | ||
| 
						 | 
					e9143ae852 | ||
| 
						 | 
					42e8ecee78 | ||
| 
						 | 
					4efd76fcbc | ||
| 
						 | 
					fb1614070e | ||
| 
						 | 
					c473dd7227 | ||
| 
						 | 
					76bddb195d | ||
| 
						 | 
					1e02ad2041 | ||
| 
						 | 
					f6ab909f8b | ||
| 
						 | 
					7e520e9bed | ||
| 
						 | 
					32e2d05014 | ||
| 
						 | 
					40d9c97f73 | ||
| 
						 | 
					1aa68d3449 | ||
| 
						 | 
					aeeac8cccd | ||
| 
						 | 
					7292edf997 | ||
| 
						 | 
					f49256c72f | ||
| 
						 | 
					d02b28b81f | ||
| 
						 | 
					08117043dd | ||
| 
						 | 
					63496c993e | ||
| 
						 | 
					00ef542e49 | ||
| 
						 | 
					a78c6e6b33 | ||
| 
						 | 
					363eaf9bf9 | ||
| 
						 | 
					fec6683701 | ||
| 
						 | 
					1549edb647 | ||
| 
						 | 
					3de48ba162 | ||
| 
						 | 
					a2a3d6f1a7 | ||
| 
						 | 
					ccab2c7648 | ||
| 
						 | 
					880dd1db5c | ||
| 
						 | 
					ed18fea356 | ||
| 
						 | 
					9816b20bf6 | ||
| 
						 | 
					0bb2195bff | ||
| 
						 | 
					ab2d0c4036 | ||
| 
						 | 
					99fc417109 | ||
| 
						 | 
					dc304ef8c1 | ||
| 
						 | 
					c5511880bc | ||
| 
						 | 
					5fe76d735e | ||
| 
						 | 
					3064b3b835 | ||
| 
						 | 
					70dc8af3ce | ||
| 
						 | 
					53c8c241da | ||
| 
						 | 
					bdc4f5680b | ||
| 
						 | 
					ed290573b2 | ||
| 
						 | 
					1616a97a8a | ||
| 
						 | 
					d090183007 | ||
| 
						 | 
					de337fd260 | ||
| 
						 | 
					12dc206323 | ||
| 
						 | 
					d47c508dee | ||
| 
						 | 
					ed75f55437 | ||
| 
						 | 
					5ad3ad4a57 | ||
| 
						 | 
					aeac1bd1d4 | ||
| 
						 | 
					4d18085072 | ||
| 
						 | 
					0c9f8214ca | ||
| 
						 | 
					a7ce7ce02e | ||
| 
						 | 
					820986c7f0 | ||
| 
						 | 
					8079cae745 | ||
| 
						 | 
					6f067bd258 | ||
| 
						 | 
					b6ade0f212 | ||
| 
						 | 
					27dadc1be3 | ||
| 
						 | 
					95e4162b4c | ||
| 
						 | 
					f75557585e | ||
| 
						 | 
					1b4c26919b | ||
| 
						 | 
					ad085bf129 | ||
| 
						 | 
					8fcd551105 | ||
| 
						 | 
					a0954700e2 | ||
| 
						 | 
					9705560442 | ||
| 
						 | 
					1f47a13ce5 | ||
| 
						 | 
					6f0ff2c975 | ||
| 
						 | 
					76e5477986 | ||
| 
						 | 
					7f308d5be3 | ||
| 
						 | 
					54a43c83e8 | ||
| 
						 | 
					8fe7266c84 | ||
| 
						 | 
					d7a46b27b7 | ||
| 
						 | 
					2257d09fdd | ||
| 
						 | 
					047c5481c4 | ||
| 
						 | 
					8a6719f934 | ||
| 
						 | 
					51a692f3be | ||
| 
						 | 
					b333f93171 | ||
| 
						 | 
					50bcf18096 | ||
| 
						 | 
					a089ced03f | ||
| 
						 | 
					1f18dddf8b | ||
| 
						 | 
					f5934e240e | ||
| 
						 | 
					6b8da2eacf | ||
| 
						 | 
					f4757a67b7 | ||
| 
						 | 
					6edeb9d840 | ||
| 
						 | 
					43ce0fd7bc | ||
| 
						 | 
					5599f5a8fc | ||
| 
						 | 
					6fd45ceb4f | ||
| 
						 | 
					05ad8aac29 | ||
| 
						 | 
					fa4f2476b7 | ||
| 
						 | 
					00818a94e9 | ||
| 
						 | 
					5d5250e44a | ||
| 
						 | 
					3052b33132 | ||
| 
						 | 
					50de6f8b5b | ||
| 
						 | 
					f88a2f415f | ||
| 
						 | 
					96f9813e01 | ||
| 
						 | 
					fee739cb17 | ||
| 
						 | 
					b1814c63b9 | ||
| 
						 | 
					c1d45678f8 | ||
| 
						 | 
					3d34e59a94 | ||
| 
						 | 
					f1133bea8b | ||
| 
						 | 
					ec64c88ff1 | ||
| 
						 | 
					be66dbba6c | ||
| 
						 | 
					8926cdbbf5 | ||
| 
						 | 
					a956870dec | ||
| 
						 | 
					8ed7951c9b | 
							
								
								
									
										6
									
								
								.github/CONTRIBUTING.md
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -41,6 +41,12 @@ Always check if the web version of your instance is working.
 | 
				
			|||||||
* Remember that PR review can take time.
 | 
					* Remember that PR review can take time.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Install Selfoss (if you don't have an instance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I won't provide any selfoss instance url. If you want to help, but to not have one, you'll have to install one, and use it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					All the details to need are [here](https://selfoss.aditu.de/).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Build the project
 | 
					# Build the project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can directly import this project into IntellIJ/Android Studio.
 | 
					You can directly import this project into IntellIJ/Android Studio.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										50
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						@@ -1,3 +1,51 @@
 | 
				
			|||||||
 | 
					**1.7.x**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Hiding tags with 0 articles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed issue with basic auth and images loading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added the ability to justify or left align the reader text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed #251
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added experimental issue to set a default timeout. Should work for #238.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #220.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Start of #238. "Add a quick shortcut to open the app on offline mode ?"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closes #216. Issue with selfoss version 2.19.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closes #179. Sync of read/unread/star/unstar items on background task or on app reload with network available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closes #33. Background sync with settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #1. Initial article caching.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #228 by removing the list action bar. Action buttons are exclusively on the card view from now on.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #38. Only doing api calls on network available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #298 and #287. Issues with Listview rendering
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #290. Fixing back button issue in Settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #300. Fixing issues when displaying some special characters.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #310. Some feeds don't have icons nor thumbnails.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #178. Expending images on tap.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #323. Old issue with textview not having the right color.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #324. Svg images loading crashes the app.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #322. App crashed because of svg images.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Closing #236. New sources can be added in Selfoss 2.19.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Dropped support for android 4, the last version supporting it is v1721030811
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**1.6.x**
 | 
					**1.6.x**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Handling hidden tags.
 | 
					- Handling hidden tags.
 | 
				
			||||||
@@ -18,6 +66,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
- Fixes #215, #208.
 | 
					- Fixes #215, #208.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixes #328.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**1.5.7.x**
 | 
					**1.5.7.x**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added confirmation to the mark as read and update menues.
 | 
					- Added confirmation to the mark as read and update menues.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -1,24 +1,34 @@
 | 
				
			|||||||
# ReaderForSelfoss
 | 
					# ReaderForSelfoss **(Only available from F-Droid)**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[](https://join.slack.com/t/readerforselfoss/shared_invite/enQtMjkyNzc3NjM2Mjc1LTUzZTZhOGM5YjQ1MTI5MWZiODRjMjE1ZDBmMzQxZmQ3NWZhYTNhMTBjNGEwNmE2ZGFjODU5NjUxZjBkMWJmMDQ) [](https://jenkins.amine-bou.fr/job/ReaderForSelfoss/) [](https://www.codetriage.com/aminecmi/readerforselfoss) [](https://crowdin.com/project/readerforselfoss)
 | 
					[](https://crowdin.com/project/readerforselfoss)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
 | 
					It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<a href='https://play.google.com/store/apps/details?id=apps.amine.bou.readerforselfoss'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png' height="100"/></a> <a href="https://f-droid.org/packages/apps.amine.bou.readerforselfoss"><img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"></a>
 | 
					**The project is not dead at all.** 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Also, the last APK built from source is available [here](https://jenkins.amine-bou.fr/job/ReaderForSelfoss/lastSuccessfulBuild/artifact/SignApksBuilder-out/selfoss-key/selfoss/app-githubConfig-release-unsigned.apk/app-githubConfig-release.apk).
 | 
					I still want to work on it, but for the last few months, I didn't have that much time to do so. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Join the alpha channel
 | 
					If you are a developer, don't hesitate to help with PRs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Keep in mind, it could be instable, but you'll have the new updates faster**
 | 
					If you are a user, you can still create new issues. I'll fix them when I can.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- First, join the google [group](https://groups.google.com/d/forum/reader-for-selfoss-alpha-testing).
 | 
					<a href="https://f-droid.org/packages/apps.amine.bou.readerforselfoss"><img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"></a>
 | 
				
			||||||
- Then, join the [alpha channel](https://play.google.com/apps/testing/apps.amine.bou.readerforselfoss) of the app.
 | 
					
 | 
				
			||||||
- You'll be able to update the app for the current alpha version.
 | 
					## 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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Want to help ?
 | 
					## Want to help ?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob/master/.github/CONTRIBUTING.md)
 | 
					1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob/master/.github/CONTRIBUTING.md).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. Build the project by following [these steps](https://github.com/aminecmi/ReaderforSelfoss/blob/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Useful links
 | 
					## Useful links
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,4 +36,12 @@ Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob
 | 
				
			|||||||
- [See what I'm doing](https://github.com/aminecmi/ReaderforSelfoss/projects/1)
 | 
					- [See what I'm doing](https://github.com/aminecmi/ReaderforSelfoss/projects/1)
 | 
				
			||||||
- [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss/issues)
 | 
					- [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss/issues)
 | 
				
			||||||
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
 | 
					- [Help translation the app](https://crowdin.com/project/readerforselfoss)
 | 
				
			||||||
- [Ask for help](https://join.slack.com/t/readerforselfoss/shared_invite/enQtMjkyNzc3NjM2Mjc1LTUzZTZhOGM5YjQ1MTI5MWZiODRjMjE1ZDBmMzQxZmQ3NWZhYTNhMTBjNGEwNmE2ZGFjODU5NjUxZjBkMWJmMDQ)
 | 
					
 | 
				
			||||||
 | 
					## Contributors (Alphabetical order) ❤️
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [@aancel](https://github.com/aancel)
 | 
				
			||||||
 | 
					- [@Binnette](https://github.com/Binnette)
 | 
				
			||||||
 | 
					- [@davidoskky](https://github.com/davidoskky)
 | 
				
			||||||
 | 
					- [@hectorgabucio](https://github.com/hectorgabucio)
 | 
				
			||||||
 | 
					- [@licaon-kter](https://github.com/licaon-kter)
 | 
				
			||||||
 | 
					- [@sergey-babkin](https://github.com/sergey-babkin)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										143
									
								
								app/build.gradle
									
									
									
									
									
								
							
							
						
						@@ -1,17 +1,17 @@
 | 
				
			|||||||
buildscript {
 | 
					buildscript {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ext {
 | 
					 | 
				
			||||||
    configuration = [
 | 
					 | 
				
			||||||
            buildDate: new Date()
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
    // This will make me able to build multiple times a day. May break thinks. I may forget it.
 | 
					 | 
				
			||||||
    todaysBuilds = "1"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def gitVersion() {
 | 
					def gitVersion() {
 | 
				
			||||||
    def process = "git describe --abbrev=0 --tags".execute()
 | 
					    def process
 | 
				
			||||||
    return process.text.substring(1).replaceAll("\\.", "").trim()
 | 
					    def maybeTagOfCurrentCommit = 'git describe --contains HEAD'.execute()
 | 
				
			||||||
 | 
					    if (maybeTagOfCurrentCommit.text.isEmpty()) {
 | 
				
			||||||
 | 
					        println "No tag on current commit. Will take the latest one."
 | 
				
			||||||
 | 
					        process = "git for-each-ref refs/tags --sort=-authordate --format='%(refname:short)' --count=1".execute()
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        println "Tag found on current commit"
 | 
				
			||||||
 | 
					        process = 'git describe --contains HEAD'.execute()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return process.text.replaceAll("'", "").substring(1).replaceAll("\\.", "").trim()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def versionCodeFromGit() {
 | 
					def versionCodeFromGit() {
 | 
				
			||||||
@@ -24,25 +24,29 @@ def versionNameFromGit() {
 | 
				
			|||||||
    return gitVersion()
 | 
					    return gitVersion()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply plugin: 'kotlin-kapt'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
apply plugin: 'com.android.application'
 | 
					apply plugin: 'com.android.application'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply plugin: 'kotlin-android'
 | 
					apply plugin: 'kotlin-android'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply plugin: 'kotlin-android-extensions'
 | 
					apply plugin: 'kotlin-kapt'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
android {
 | 
					android {
 | 
				
			||||||
    compileOptions {
 | 
					    compileOptions {
 | 
				
			||||||
 | 
					        // Flag to enable support for the new language APIs
 | 
				
			||||||
 | 
					        coreLibraryDesugaringEnabled true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
					        sourceCompatibility JavaVersion.VERSION_1_8
 | 
				
			||||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
					        targetCompatibility JavaVersion.VERSION_1_8
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    compileSdkVersion 28
 | 
					    compileSdkVersion 31
 | 
				
			||||||
    buildToolsVersion '28.0.3'
 | 
					    buildToolsVersion '31.0.0'
 | 
				
			||||||
 | 
					    buildFeatures {
 | 
				
			||||||
 | 
					        viewBinding true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    defaultConfig {
 | 
					    defaultConfig {
 | 
				
			||||||
        applicationId "apps.amine.bou.readerforselfoss"
 | 
					        applicationId "apps.amine.bou.readerforselfoss"
 | 
				
			||||||
        minSdkVersion 16
 | 
					        minSdkVersion 21
 | 
				
			||||||
        targetSdkVersion 28
 | 
					        targetSdkVersion 31
 | 
				
			||||||
        versionCode versionCodeFromGit()
 | 
					        versionCode versionCodeFromGit()
 | 
				
			||||||
        versionName versionNameFromGit()
 | 
					        versionName versionNameFromGit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,9 +77,8 @@ android {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        debug {
 | 
					        debug {
 | 
				
			||||||
            buildConfigField "String", "LOGIN_URL", appLoginUrl
 | 
					            buildConfigField "String", "LOGIN_URL", appLoginUrl
 | 
				
			||||||
            buildConfigField "String", "LOGIN_USERNAME", appLoginUsername
 | 
					 | 
				
			||||||
            buildConfigField "String", "LOGIN_PASSWORD", appLoginPassword
 | 
					            buildConfigField "String", "LOGIN_PASSWORD", appLoginPassword
 | 
				
			||||||
            applicationIdSuffix ".dev"
 | 
					            buildConfigField "String", "LOGIN_USERNAME", appLoginUsername
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    flavorDimensions "build"
 | 
					    flavorDimensions "build"
 | 
				
			||||||
@@ -84,97 +87,85 @@ android {
 | 
				
			|||||||
            versionNameSuffix '-github'
 | 
					            versionNameSuffix '-github'
 | 
				
			||||||
            dimension "build"
 | 
					            dimension "build"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        storeConfig {
 | 
					    }
 | 
				
			||||||
            // As jenkins publishes to alpha first, this is the default suffix now.
 | 
					    kotlinOptions {
 | 
				
			||||||
            versionNameSuffix '-store'
 | 
					        jvmTarget = '1.8'
 | 
				
			||||||
            dimension "build"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					dependencies {
 | 
				
			||||||
 | 
					    implementation 'androidx.preference:preference-ktx:1.1.1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Testing
 | 
					    // Testing
 | 
				
			||||||
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-beta02'
 | 
					    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha02'
 | 
				
			||||||
    androidTestImplementation 'androidx.test:runner:1.1.0-beta02'
 | 
					    androidTestImplementation 'androidx.test:runner:1.3.1-alpha02'
 | 
				
			||||||
    // Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
 | 
					    // Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
 | 
				
			||||||
    androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0-beta02'
 | 
					    androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0-alpha02'
 | 
				
			||||||
    // Espresso-intents for validation and stubbing of Intents
 | 
					    // Espresso-intents for validation and stubbing of Intents
 | 
				
			||||||
    androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0-beta02'
 | 
					    androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0-alpha02'
 | 
				
			||||||
    implementation fileTree(include: ['*.jar'], dir: 'libs')
 | 
					    implementation fileTree(include: ['*.jar'], dir: 'libs')
 | 
				
			||||||
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
 | 
					    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
 | 
				
			||||||
    // Android Support
 | 
					    // Android Support
 | 
				
			||||||
    implementation "androidx.appcompat:appcompat:$android_version"
 | 
					    implementation "androidx.appcompat:appcompat:1.4.0-beta01"
 | 
				
			||||||
    implementation "com.google.android.material:material:$android_version"
 | 
					    implementation 'com.google.android.material:material:1.5.0-alpha04'
 | 
				
			||||||
    implementation "androidx.recyclerview:recyclerview:$android_version"
 | 
					    implementation 'androidx.recyclerview:recyclerview:1.3.0-alpha01'
 | 
				
			||||||
    implementation "androidx.legacy:legacy-support-v4:$android_version"
 | 
					    implementation "androidx.legacy:legacy-support-v4:$android_version"
 | 
				
			||||||
    implementation "androidx.vectordrawable:vectordrawable:$android_version"
 | 
					    implementation 'androidx.vectordrawable:vectordrawable:1.2.0-alpha02'
 | 
				
			||||||
    implementation "androidx.browser:browser:$android_version"
 | 
					    implementation "androidx.browser:browser:1.3.0"
 | 
				
			||||||
    implementation "androidx.cardview:cardview:$android_version"
 | 
					    implementation "androidx.cardview:cardview:$android_version"
 | 
				
			||||||
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
 | 
					    implementation "androidx.annotation:annotation:1.2.0"
 | 
				
			||||||
 | 
					    implementation 'androidx.work:work-runtime-ktx:2.7.0'
 | 
				
			||||||
 | 
					    implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
 | 
				
			||||||
 | 
					    implementation 'org.jsoup:jsoup:1.14.3'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //multidex
 | 
					    //multidex
 | 
				
			||||||
    implementation 'androidx.multidex:multidex:2.0.0'
 | 
					    implementation 'androidx.multidex:multidex:2.0.1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // About
 | 
					    // About
 | 
				
			||||||
    implementation('com.mikepenz:aboutlibraries:6.2.0@aar') {
 | 
					    implementation 'com.mikepenz:aboutlibraries-core:8.9.4'
 | 
				
			||||||
        transitive = true
 | 
					    implementation 'com.mikepenz:aboutlibraries:8.9.4'
 | 
				
			||||||
    }
 | 
					    implementation "com.mikepenz:aboutlibraries-definitions:8.9.4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Async
 | 
				
			||||||
 | 
					    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Retrofit + http logging + okhttp
 | 
					    // Retrofit + http logging + okhttp
 | 
				
			||||||
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
 | 
					    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
 | 
				
			||||||
    implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
 | 
					    implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
 | 
				
			||||||
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
 | 
					    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
 | 
				
			||||||
    implementation 'com.burgstaller:okhttp-digest:1.12'
 | 
					    implementation 'com.burgstaller:okhttp-digest:2.5'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Material-ish things
 | 
					    // Material-ish things
 | 
				
			||||||
    implementation 'com.ashokvarma.android:bottom-navigation-bar:2.0.5'
 | 
					    implementation 'com.ashokvarma.android:bottom-navigation-bar:2.2.0'
 | 
				
			||||||
    implementation 'com.github.jd-alexander:LikeButton:0.2.3'
 | 
					 | 
				
			||||||
    implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
 | 
					    implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // glide
 | 
					    // glide
 | 
				
			||||||
    implementation 'com.github.bumptech.glide:glide:4.1.1'
 | 
					    kapt 'com.github.bumptech.glide:compiler:4.11.0'
 | 
				
			||||||
    implementation 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
 | 
					    implementation 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Asking politely users to rate the app
 | 
					 | 
				
			||||||
    implementation 'com.github.stkent:amplify:2.2.0'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Drawer
 | 
					    // Drawer
 | 
				
			||||||
    implementation 'co.zsmb:materialdrawer-kt:2.0.1'
 | 
					    implementation 'com.mikepenz:materialdrawer:8.4.4'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Themes
 | 
					    // Themes
 | 
				
			||||||
    implementation 'com.52inc:scoops:1.0.0'
 | 
					    implementation 'com.52inc:scoops:1.0.0'
 | 
				
			||||||
    implementation 'com.jaredrummler:colorpicker:1.0.2'
 | 
					    implementation 'com.jaredrummler:colorpicker:1.1.0'
 | 
				
			||||||
    implementation 'com.github.rubensousa:floatingtoolbar:1.5.1'
 | 
					    implementation 'com.github.rubensousa:floatingtoolbar:1.5.1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Pager
 | 
					    // Pager
 | 
				
			||||||
    implementation 'me.relex:circleindicator:2.0.0@aar'
 | 
					    implementation 'me.relex:circleindicator:2.1.6'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    implementation 'androidx.core:core-ktx:1.0.0'
 | 
					    //PhotoView
 | 
				
			||||||
 | 
					    implementation 'com.github.chrisbanes:PhotoView:2.3.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Crash
 | 
					    implementation 'androidx.core:core-ktx:1.7.0-rc01'
 | 
				
			||||||
    implementation 'ch.acra:acra-http:5.1.3'
 | 
					 | 
				
			||||||
    implementation 'ch.acra:acra-dialog:5.1.3'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
 | 
					    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0-rc01"
 | 
				
			||||||
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
 | 
					    implementation "androidx.lifecycle:lifecycle-common-java8:2.4.0-rc01"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    implementation "androidx.room:room-runtime:$room_version"
 | 
					    implementation "androidx.room:room-ktx:2.4.0-beta01"
 | 
				
			||||||
    kapt "androidx.room:room-compiler:$room_version"
 | 
					    kapt "androidx.room:room-compiler:2.4.0-beta01"
 | 
				
			||||||
}
 | 
					
 | 
				
			||||||
 | 
					    implementation "android.arch.work:work-runtime-ktx:$work_version"
 | 
				
			||||||
 | 
					 | 
				
			||||||
afterEvaluate {
 | 
					 | 
				
			||||||
    initAppLoginPropertiesIfNeeded()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def initAppLoginPropertiesIfNeeded() {
 | 
					 | 
				
			||||||
    def propertiesFile = file(System.getProperty("user.home") + '/.gradle/gradle.properties')
 | 
					 | 
				
			||||||
    if (!propertiesFile.exists()) {
 | 
					 | 
				
			||||||
        def commentMessage = "This is autogenerated local property from system environment to prevent key to be committed to source control."
 | 
					 | 
				
			||||||
        ant.propertyfile(file: System.getProperty("user.home") + "/.gradle/gradle.properties", comment: commentMessage) {
 | 
					 | 
				
			||||||
            entry(key: "appLoginUrl", value: System.getProperty("appLoginUrl"))
 | 
					 | 
				
			||||||
            entry(key: "appLoginUsername", value: System.getProperty("appLoginUsername"))
 | 
					 | 
				
			||||||
            entry(key: "appLoginPassword", value: System.getProperty("appLoginPassword"))
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -30,22 +30,6 @@
 | 
				
			|||||||
    <fields>;
 | 
					    <fields>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
##Retrofit
 | 
					 | 
				
			||||||
#-keep class com.google.gson.** { *; }
 | 
					 | 
				
			||||||
#-keep class com.google.inject.** { *; }
 | 
					 | 
				
			||||||
#-keep class org.apache.http.** { *; }
 | 
					 | 
				
			||||||
#-keep class org.apache.james.mime4j.** { *; }
 | 
					 | 
				
			||||||
#-keep class javax.inject.** { *; }
 | 
					 | 
				
			||||||
#-keep class retrofit.** { *; }
 | 
					 | 
				
			||||||
#-keepclassmembernames interface * {
 | 
					 | 
				
			||||||
#    @retrofit.http.* <methods>;
 | 
					 | 
				
			||||||
#}
 | 
					 | 
				
			||||||
#-keep class retrofit.** { *; }
 | 
					 | 
				
			||||||
#-keep class apps.amine.bou.readerforselfoss.api.selfoss.model.** { *; }
 | 
					 | 
				
			||||||
#-keepclassmembernames interface * {
 | 
					 | 
				
			||||||
#    @retrofit.http.* <methods>;
 | 
					 | 
				
			||||||
#}
 | 
					 | 
				
			||||||
-dontwarn okio.**
 | 
					-dontwarn okio.**
 | 
				
			||||||
-dontwarn retrofit2.Platform$Java8
 | 
					-dontwarn retrofit2.Platform$Java8
 | 
				
			||||||
-keep class retrofit.** { *; }
 | 
					-keep class retrofit.** { *; }
 | 
				
			||||||
@@ -76,3 +60,6 @@
 | 
				
			|||||||
-dontwarn javax.annotation.**
 | 
					-dontwarn javax.annotation.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-keep class android.support.v7.widget.SearchView { *; }
 | 
					-keep class android.support.v7.widget.SearchView { *; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# maybe remove later ?
 | 
				
			||||||
 | 
					-keep class * extends androidx.fragment.app.Fragment
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,226 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "formatVersion": 1,
 | 
				
			||||||
 | 
					  "database": {
 | 
				
			||||||
 | 
					    "version": 3,
 | 
				
			||||||
 | 
					    "identityHash": "7ad9c4961992c13b670128485ebb3efc",
 | 
				
			||||||
 | 
					    "entities": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "tags",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tag",
 | 
				
			||||||
 | 
					            "columnName": "tag",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "color",
 | 
				
			||||||
 | 
					            "columnName": "color",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "tag"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "sources",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "title",
 | 
				
			||||||
 | 
					            "columnName": "title",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tags",
 | 
				
			||||||
 | 
					            "columnName": "tags",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "spout",
 | 
				
			||||||
 | 
					            "columnName": "spout",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "error",
 | 
				
			||||||
 | 
					            "columnName": "error",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "icon",
 | 
				
			||||||
 | 
					            "columnName": "icon",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "items",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "datetime",
 | 
				
			||||||
 | 
					            "columnName": "datetime",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "title",
 | 
				
			||||||
 | 
					            "columnName": "title",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "content",
 | 
				
			||||||
 | 
					            "columnName": "content",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "starred",
 | 
				
			||||||
 | 
					            "columnName": "starred",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "thumbnail",
 | 
				
			||||||
 | 
					            "columnName": "thumbnail",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "icon",
 | 
				
			||||||
 | 
					            "columnName": "icon",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "link",
 | 
				
			||||||
 | 
					            "columnName": "link",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "sourcetitle",
 | 
				
			||||||
 | 
					            "columnName": "sourcetitle",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tags",
 | 
				
			||||||
 | 
					            "columnName": "tags",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "actions",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL)",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "articleId",
 | 
				
			||||||
 | 
					            "columnName": "articleid",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "read",
 | 
				
			||||||
 | 
					            "columnName": "read",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "starred",
 | 
				
			||||||
 | 
					            "columnName": "starred",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unstarred",
 | 
				
			||||||
 | 
					            "columnName": "unstarred",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "views": [],
 | 
				
			||||||
 | 
					    "setupQueries": [
 | 
				
			||||||
 | 
					      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
 | 
				
			||||||
 | 
					      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"7ad9c4961992c13b670128485ebb3efc\")"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,226 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "formatVersion": 1,
 | 
				
			||||||
 | 
					  "database": {
 | 
				
			||||||
 | 
					    "version": 4,
 | 
				
			||||||
 | 
					    "identityHash": "9cf8b03d32f80dfd58160599a1df197d",
 | 
				
			||||||
 | 
					    "entities": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "tags",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tag",
 | 
				
			||||||
 | 
					            "columnName": "tag",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "color",
 | 
				
			||||||
 | 
					            "columnName": "color",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "tag"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "sources",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "title",
 | 
				
			||||||
 | 
					            "columnName": "title",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tags",
 | 
				
			||||||
 | 
					            "columnName": "tags",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "spout",
 | 
				
			||||||
 | 
					            "columnName": "spout",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "error",
 | 
				
			||||||
 | 
					            "columnName": "error",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "icon",
 | 
				
			||||||
 | 
					            "columnName": "icon",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "items",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT, `icon` TEXT, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "datetime",
 | 
				
			||||||
 | 
					            "columnName": "datetime",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "title",
 | 
				
			||||||
 | 
					            "columnName": "title",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "content",
 | 
				
			||||||
 | 
					            "columnName": "content",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "starred",
 | 
				
			||||||
 | 
					            "columnName": "starred",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "thumbnail",
 | 
				
			||||||
 | 
					            "columnName": "thumbnail",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "icon",
 | 
				
			||||||
 | 
					            "columnName": "icon",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": false
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "link",
 | 
				
			||||||
 | 
					            "columnName": "link",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "sourcetitle",
 | 
				
			||||||
 | 
					            "columnName": "sourcetitle",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "tags",
 | 
				
			||||||
 | 
					            "columnName": "tags",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "tableName": "actions",
 | 
				
			||||||
 | 
					        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL)",
 | 
				
			||||||
 | 
					        "fields": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "id",
 | 
				
			||||||
 | 
					            "columnName": "id",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "articleId",
 | 
				
			||||||
 | 
					            "columnName": "articleid",
 | 
				
			||||||
 | 
					            "affinity": "TEXT",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "read",
 | 
				
			||||||
 | 
					            "columnName": "read",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unread",
 | 
				
			||||||
 | 
					            "columnName": "unread",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "starred",
 | 
				
			||||||
 | 
					            "columnName": "starred",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "fieldPath": "unstarred",
 | 
				
			||||||
 | 
					            "columnName": "unstarred",
 | 
				
			||||||
 | 
					            "affinity": "INTEGER",
 | 
				
			||||||
 | 
					            "notNull": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "primaryKey": {
 | 
				
			||||||
 | 
					          "columnNames": [
 | 
				
			||||||
 | 
					            "id"
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          "autoGenerate": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "indices": [],
 | 
				
			||||||
 | 
					        "foreignKeys": []
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "views": [],
 | 
				
			||||||
 | 
					    "setupQueries": [
 | 
				
			||||||
 | 
					      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
 | 
				
			||||||
 | 
					      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"9cf8b03d32f80dfd58160599a1df197d\")"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.parseDate
 | 
				
			||||||
 | 
					import org.junit.Test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DateUtilsTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun parseDateV4() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Config.apiVersion = 4
 | 
				
			||||||
 | 
					        val dateString = "2013-04-07T13:43:00+01:00"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val milliseconds = parseDate(dateString).toEpochMilli()
 | 
				
			||||||
 | 
					        val correctMilliseconds : Long = 1365338580000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert(milliseconds == correctMilliseconds)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun parseDateV1() {
 | 
				
			||||||
 | 
					        Config.apiVersion = 0
 | 
				
			||||||
 | 
					        val dateString = "2013-04-07 13:43:00"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val milliseconds = parseDate(dateString).toEpochMilli()
 | 
				
			||||||
 | 
					        val correctMilliseconds = 1365342180000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert(milliseconds == correctMilliseconds)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
    package="apps.amine.bou.readerforselfoss"
 | 
					    package="apps.amine.bou.readerforselfoss">
 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools">
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 | 
				
			||||||
    <uses-permission android:name="android.permission.INTERNET" />
 | 
					    <uses-permission android:name="android.permission.INTERNET" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <application
 | 
					    <application
 | 
				
			||||||
@@ -11,15 +11,20 @@
 | 
				
			|||||||
        android:icon="@mipmap/ic_launcher"
 | 
					        android:icon="@mipmap/ic_launcher"
 | 
				
			||||||
        android:label="@string/app_name"
 | 
					        android:label="@string/app_name"
 | 
				
			||||||
        android:supportsRtl="true"
 | 
					        android:supportsRtl="true"
 | 
				
			||||||
 | 
					        android:networkSecurityConfig="@xml/network_security_config"
 | 
				
			||||||
        android:theme="@style/NoBar">
 | 
					        android:theme="@style/NoBar">
 | 
				
			||||||
        <activity
 | 
					        <activity
 | 
				
			||||||
            android:name=".MainActivity"
 | 
					            android:name=".MainActivity"
 | 
				
			||||||
            android:theme="@style/SplashTheme">
 | 
					            android:theme="@style/SplashTheme"
 | 
				
			||||||
 | 
					            android:exported="true">
 | 
				
			||||||
            <intent-filter>
 | 
					            <intent-filter>
 | 
				
			||||||
                <action android:name="android.intent.action.MAIN" />
 | 
					                <action android:name="android.intent.action.MAIN" />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                <category android:name="android.intent.category.LAUNCHER" />
 | 
					                <category android:name="android.intent.category.LAUNCHER" />
 | 
				
			||||||
            </intent-filter>
 | 
					            </intent-filter>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <meta-data
 | 
				
			||||||
 | 
					                android:name="android.app.shortcuts"
 | 
				
			||||||
 | 
					                android:resource="@xml/shortcuts" />
 | 
				
			||||||
        </activity>
 | 
					        </activity>
 | 
				
			||||||
        <activity
 | 
					        <activity
 | 
				
			||||||
            android:name=".LoginActivity"
 | 
					            android:name=".LoginActivity"
 | 
				
			||||||
@@ -33,7 +38,7 @@
 | 
				
			|||||||
            android:parentActivityName=".HomeActivity">
 | 
					            android:parentActivityName=".HomeActivity">
 | 
				
			||||||
            <meta-data
 | 
					            <meta-data
 | 
				
			||||||
                android:name="android.support.PARENT_ACTIVITY"
 | 
					                android:name="android.support.PARENT_ACTIVITY"
 | 
				
			||||||
                android:value="apps.amine.bou.readerforselfoss.HomeActivity" />
 | 
					                android:value=".HomeActivity" />
 | 
				
			||||||
        </activity>
 | 
					        </activity>
 | 
				
			||||||
        <activity
 | 
					        <activity
 | 
				
			||||||
            android:name=".SourcesActivity"
 | 
					            android:name=".SourcesActivity"
 | 
				
			||||||
@@ -44,22 +49,24 @@
 | 
				
			|||||||
        </activity>
 | 
					        </activity>
 | 
				
			||||||
        <activity
 | 
					        <activity
 | 
				
			||||||
            android:name=".AddSourceActivity"
 | 
					            android:name=".AddSourceActivity"
 | 
				
			||||||
            android:parentActivityName=".SourcesActivity">
 | 
					            android:parentActivityName=".SourcesActivity"
 | 
				
			||||||
 | 
					            android:exported="true">
 | 
				
			||||||
            <meta-data
 | 
					            <meta-data
 | 
				
			||||||
                android:name="android.support.PARENT_ACTIVITY"
 | 
					                android:name="android.support.PARENT_ACTIVITY"
 | 
				
			||||||
                android:value=".SourcesActivity" />
 | 
					                android:value=".SourcesActivity" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <intent-filter>
 | 
					            <intent-filter>
 | 
				
			||||||
                <action android:name="android.intent.action.SEND" />
 | 
					                <action android:name="android.intent.action.SEND" />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                <category android:name="android.intent.category.DEFAULT" />
 | 
					                <category android:name="android.intent.category.DEFAULT" />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                <data android:mimeType="text/plain" />
 | 
					                <data android:mimeType="text/plain" />
 | 
				
			||||||
            </intent-filter>
 | 
					            </intent-filter>
 | 
				
			||||||
        </activity>
 | 
					        </activity>
 | 
				
			||||||
        <activity
 | 
					        <activity
 | 
				
			||||||
            android:name=".ReaderActivity">
 | 
					            android:name=".ReaderActivity">
 | 
				
			||||||
        </activity>
 | 
					        </activity>
 | 
				
			||||||
 | 
					        <activity
 | 
				
			||||||
 | 
					            android:name=".ImageActivity">
 | 
				
			||||||
 | 
					        </activity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <meta-data
 | 
					        <meta-data
 | 
				
			||||||
            android:name="apps.amine.bou.readerforselfoss.utils.glide.SelfSignedGlideModule"
 | 
					            android:name="apps.amine.bou.readerforselfoss.utils.glide.SelfSignedGlideModule"
 | 
				
			||||||
@@ -72,6 +79,9 @@
 | 
				
			|||||||
            android:value="true" />
 | 
					            android:value="true" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <meta-data android:name="android.max_aspect" android:value="2.1" />
 | 
					        <meta-data android:name="android.max_aspect" android:value="2.1" />
 | 
				
			||||||
 | 
					        <meta-data
 | 
				
			||||||
 | 
					            android:name="preloaded_fonts"
 | 
				
			||||||
 | 
					            android:resource="@array/preloaded_fonts" />
 | 
				
			||||||
    </application>
 | 
					    </application>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</manifest>
 | 
					</manifest>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss
 | 
					package apps.amine.bou.readerforselfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.os.Build
 | 
					 | 
				
			||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					import androidx.preference.PreferenceManager
 | 
				
			||||||
import androidx.constraintlayout.widget.ConstraintLayout
 | 
					import androidx.constraintlayout.widget.ConstraintLayout
 | 
				
			||||||
import androidx.appcompat.app.AppCompatActivity
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
@@ -22,11 +22,11 @@ import apps.amine.bou.readerforselfoss.themes.Toppings
 | 
				
			|||||||
import apps.amine.bou.readerforselfoss.utils.Config
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
 | 
					import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
 | 
				
			||||||
import com.ftinc.scoop.Scoop
 | 
					import com.ftinc.scoop.Scoop
 | 
				
			||||||
import kotlinx.android.synthetic.main.activity_add_source.*
 | 
					 | 
				
			||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
import retrofit2.Callback
 | 
					import retrofit2.Callback
 | 
				
			||||||
import retrofit2.Response
 | 
					import retrofit2.Response
 | 
				
			||||||
import android.graphics.PorterDuff
 | 
					import android.graphics.PorterDuff
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.databinding.ActivityAddSourceBinding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,71 +36,62 @@ class AddSourceActivity : AppCompatActivity() {
 | 
				
			|||||||
    private lateinit var api: SelfossApi
 | 
					    private lateinit var api: SelfossApi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private lateinit var appColors: AppColors
 | 
					    private lateinit var appColors: AppColors
 | 
				
			||||||
 | 
					    private lateinit var binding: ActivityAddSourceBinding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
        appColors = AppColors(this@AddSourceActivity)
 | 
					        appColors = AppColors(this@AddSourceActivity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super.onCreate(savedInstanceState)
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
 | 
					        binding = ActivityAddSourceBinding.inflate(layoutInflater)
 | 
				
			||||||
 | 
					        val view = binding.root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setContentView(R.layout.activity_add_source)
 | 
					        setContentView(view)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val scoop = Scoop.getInstance()
 | 
					        val scoop = Scoop.getInstance()
 | 
				
			||||||
        scoop.bind(this, Toppings.PRIMARY.value, toolbar)
 | 
					        scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
 | 
				
			||||||
        if  (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 | 
					        scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
 | 
				
			||||||
            scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val drawable = nameInput.background
 | 
					        val drawable = binding.nameInput.background
 | 
				
			||||||
        drawable.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
 | 
					        drawable.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // TODO: clean
 | 
					        // TODO: clean
 | 
				
			||||||
        if(Build.VERSION.SDK_INT > 16) {
 | 
					        binding.nameInput.background = drawable
 | 
				
			||||||
            nameInput.background = drawable
 | 
					 | 
				
			||||||
        } else{
 | 
					 | 
				
			||||||
            nameInput.setBackgroundDrawable(drawable)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val drawable1 = sourceUri.background
 | 
					        val drawable1 = binding.sourceUri.background
 | 
				
			||||||
        drawable1.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
 | 
					        drawable1.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(Build.VERSION.SDK_INT > 16) {
 | 
					        binding.sourceUri.background = drawable1
 | 
				
			||||||
            sourceUri.background = drawable1
 | 
					 | 
				
			||||||
        } else{
 | 
					 | 
				
			||||||
            sourceUri.setBackgroundDrawable(drawable1)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val drawable2 = tags.background
 | 
					        val drawable2 = binding.tags.background
 | 
				
			||||||
        drawable2.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
 | 
					        drawable2.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(Build.VERSION.SDK_INT > 16) {
 | 
					        binding.tags.background = drawable2
 | 
				
			||||||
            tags.background = drawable2
 | 
					 | 
				
			||||||
        } else{
 | 
					 | 
				
			||||||
            tags.setBackgroundDrawable(drawable2)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setSupportActionBar(toolbar)
 | 
					        setSupportActionBar(binding.toolbar)
 | 
				
			||||||
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
					        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
				
			||||||
        supportActionBar?.setDisplayShowHomeEnabled(true)
 | 
					        supportActionBar?.setDisplayShowHomeEnabled(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            val prefs = PreferenceManager.getDefaultSharedPreferences(this)
 | 
					            val prefs = PreferenceManager.getDefaultSharedPreferences(this)
 | 
				
			||||||
 | 
					            val settings =
 | 
				
			||||||
 | 
					                getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
				
			||||||
            api = SelfossApi(
 | 
					            api = SelfossApi(
 | 
				
			||||||
                this,
 | 
					                this,
 | 
				
			||||||
                this@AddSourceActivity,
 | 
					                this@AddSourceActivity,
 | 
				
			||||||
                prefs.getBoolean("isSelfSignedCert", false),
 | 
					                settings.getBoolean("isSelfSignedCert", false),
 | 
				
			||||||
                prefs.getBoolean("should_log_everything", false)
 | 
					                prefs.getString("api_timeout", "-1")!!.toLong()
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        } catch (e: IllegalArgumentException) {
 | 
					        } catch (e: IllegalArgumentException) {
 | 
				
			||||||
            mustLoginToAddSource()
 | 
					            mustLoginToAddSource()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        maybeGetDetailsFromIntentSharing(intent, sourceUri, nameInput)
 | 
					        maybeGetDetailsFromIntentSharing(intent, binding.sourceUri, binding.nameInput)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        saveBtn.setTextColor(appColors.colorAccent)
 | 
					        binding.saveBtn.setTextColor(appColors.colorAccent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        saveBtn.setOnClickListener {
 | 
					        binding.saveBtn.setOnClickListener {
 | 
				
			||||||
            handleSaveSource(tags, nameInput.text.toString(), sourceUri.text.toString(), api!!)
 | 
					            handleSaveSource(binding.tags, binding.nameInput.text.toString(), binding.sourceUri.text.toString(), api)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -108,10 +99,10 @@ class AddSourceActivity : AppCompatActivity() {
 | 
				
			|||||||
        super.onResume()
 | 
					        super.onResume()
 | 
				
			||||||
        val config = Config(this)
 | 
					        val config = Config(this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid()) {
 | 
					        if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid(this@AddSourceActivity)) {
 | 
				
			||||||
            mustLoginToAddSource()
 | 
					            mustLoginToAddSource()
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            handleSpoutsSpinner(spoutsSpinner, api, progress, formContainer)
 | 
					            handleSpoutsSpinner(binding.spoutsSpinner, api, binding.progress, binding.formContainer)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -200,42 +191,78 @@ class AddSourceActivity : AppCompatActivity() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private fun handleSaveSource(tags: EditText, title: String, url: String, api: SelfossApi) {
 | 
					    private fun handleSaveSource(tags: EditText, title: String, url: String, api: SelfossApi) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val sourceDetailsAvailable =
 | 
					        val sourceDetailsUnavailable =
 | 
				
			||||||
            title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
 | 
					            title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (sourceDetailsAvailable) {
 | 
					        when {
 | 
				
			||||||
            Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
 | 
					            sourceDetailsUnavailable -> {
 | 
				
			||||||
        } else {
 | 
					                Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
 | 
				
			||||||
            api.createSource(
 | 
					            }
 | 
				
			||||||
                title,
 | 
					            PreferenceManager.getDefaultSharedPreferences(this).getInt("apiVersionMajor", 0) > 1 -> {
 | 
				
			||||||
                url,
 | 
					                val tagList = tags.text.toString().split(",").map { it.trim() }
 | 
				
			||||||
                mSpoutsValue!!,
 | 
					                api.createSourceApi2(
 | 
				
			||||||
                tags.text.toString(),
 | 
					                    title,
 | 
				
			||||||
                ""
 | 
					                    url,
 | 
				
			||||||
            ).enqueue(object : Callback<SuccessResponse> {
 | 
					                    mSpoutsValue!!,
 | 
				
			||||||
                override fun onResponse(
 | 
					                    tagList,
 | 
				
			||||||
                    call: Call<SuccessResponse>,
 | 
					                    ""
 | 
				
			||||||
                    response: Response<SuccessResponse>
 | 
					                ).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
                ) {
 | 
					                    override fun onResponse(
 | 
				
			||||||
                    if (response.body() != null && response.body()!!.isSuccess) {
 | 
					                        call: Call<SuccessResponse>,
 | 
				
			||||||
                        finish()
 | 
					                        response: Response<SuccessResponse>
 | 
				
			||||||
                    } else {
 | 
					                    ) {
 | 
				
			||||||
 | 
					                        if (response.body() != null && response.body()!!.isSuccess) {
 | 
				
			||||||
 | 
					                            finish()
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            Toast.makeText(
 | 
				
			||||||
 | 
					                                this@AddSourceActivity,
 | 
				
			||||||
 | 
					                                R.string.cant_create_source,
 | 
				
			||||||
 | 
					                                Toast.LENGTH_SHORT
 | 
				
			||||||
 | 
					                            ).show()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
				
			||||||
                        Toast.makeText(
 | 
					                        Toast.makeText(
 | 
				
			||||||
                            this@AddSourceActivity,
 | 
					                            this@AddSourceActivity,
 | 
				
			||||||
                            R.string.cant_create_source,
 | 
					                            R.string.cant_create_source,
 | 
				
			||||||
                            Toast.LENGTH_SHORT
 | 
					                            Toast.LENGTH_SHORT
 | 
				
			||||||
                        ).show()
 | 
					                        ).show()
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else -> {
 | 
				
			||||||
 | 
					                api.createSource(
 | 
				
			||||||
 | 
					                    title,
 | 
				
			||||||
 | 
					                    url,
 | 
				
			||||||
 | 
					                    mSpoutsValue!!,
 | 
				
			||||||
 | 
					                    tags.text.toString(),
 | 
				
			||||||
 | 
					                    ""
 | 
				
			||||||
 | 
					                ).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
 | 
					                    override fun onResponse(
 | 
				
			||||||
 | 
					                        call: Call<SuccessResponse>,
 | 
				
			||||||
 | 
					                        response: Response<SuccessResponse>
 | 
				
			||||||
 | 
					                    ) {
 | 
				
			||||||
 | 
					                        if (response.body() != null && response.body()!!.isSuccess) {
 | 
				
			||||||
 | 
					                            finish()
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            Toast.makeText(
 | 
				
			||||||
 | 
					                                this@AddSourceActivity,
 | 
				
			||||||
 | 
					                                R.string.cant_create_source,
 | 
				
			||||||
 | 
					                                Toast.LENGTH_SHORT
 | 
				
			||||||
 | 
					                            ).show()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
					                    override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
				
			||||||
                    Toast.makeText(
 | 
					                        Toast.makeText(
 | 
				
			||||||
                        this@AddSourceActivity,
 | 
					                            this@AddSourceActivity,
 | 
				
			||||||
                        R.string.cant_create_source,
 | 
					                            R.string.cant_create_source,
 | 
				
			||||||
                        Toast.LENGTH_SHORT
 | 
					                            Toast.LENGTH_SHORT
 | 
				
			||||||
                    ).show()
 | 
					                        ).show()
 | 
				
			||||||
                }
 | 
					                    }
 | 
				
			||||||
            })
 | 
					                })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.os.Bundle
 | 
				
			||||||
 | 
					import android.view.MenuItem
 | 
				
			||||||
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
 | 
					import androidx.fragment.app.FragmentManager
 | 
				
			||||||
 | 
					import androidx.fragment.app.FragmentStatePagerAdapter
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.databinding.ActivityImageBinding
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.fragments.ImageFragment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ImageActivity : AppCompatActivity() {
 | 
				
			||||||
 | 
					    private lateinit var allImages : ArrayList<String>
 | 
				
			||||||
 | 
					    private var position : Int = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lateinit var binding: ActivityImageBinding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
 | 
					        binding = ActivityImageBinding.inflate(layoutInflater)
 | 
				
			||||||
 | 
					        val view = binding.root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setContentView(view)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setSupportActionBar(binding.toolBar)
 | 
				
			||||||
 | 
					        supportActionBar?.setDisplayShowTitleEnabled(false)
 | 
				
			||||||
 | 
					        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        allImages = intent.getStringArrayListExtra("allImages") as ArrayList<String>
 | 
				
			||||||
 | 
					        position = intent.getIntExtra("position", 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        binding.pager.adapter = ScreenSlidePagerAdapter(supportFragmentManager)
 | 
				
			||||||
 | 
					        binding.pager.currentItem = position
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
				
			||||||
 | 
					        when (item.itemId) {
 | 
				
			||||||
 | 
					            android.R.id.home -> {
 | 
				
			||||||
 | 
					                onBackPressed()
 | 
				
			||||||
 | 
					                return true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return super.onOptionsItemSelected(item)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private inner class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        override fun getCount(): Int {
 | 
				
			||||||
 | 
					            return allImages.size
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        override fun getItem(position: Int): ImageFragment {
 | 
				
			||||||
 | 
					            return ImageFragment.newInstance(allImages[position])
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -14,17 +14,14 @@ import android.view.MenuItem
 | 
				
			|||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
import android.view.inputmethod.EditorInfo
 | 
					import android.view.inputmethod.EditorInfo
 | 
				
			||||||
import android.widget.TextView
 | 
					import android.widget.TextView
 | 
				
			||||||
import android.widget.Toast
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.databinding.ActivityLoginBinding
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
					import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.Config
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
 | 
					import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
 | 
				
			||||||
import com.mikepenz.aboutlibraries.Libs
 | 
					 | 
				
			||||||
import com.mikepenz.aboutlibraries.LibsBuilder
 | 
					import com.mikepenz.aboutlibraries.LibsBuilder
 | 
				
			||||||
import kotlinx.android.synthetic.main.activity_login.*
 | 
					 | 
				
			||||||
import org.acra.ACRA
 | 
					 | 
				
			||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
import retrofit2.Callback
 | 
					import retrofit2.Callback
 | 
				
			||||||
import retrofit2.Response
 | 
					import retrofit2.Response
 | 
				
			||||||
@@ -39,28 +36,28 @@ class LoginActivity : AppCompatActivity() {
 | 
				
			|||||||
    private lateinit var settings: SharedPreferences
 | 
					    private lateinit var settings: SharedPreferences
 | 
				
			||||||
    private lateinit var editor: SharedPreferences.Editor
 | 
					    private lateinit var editor: SharedPreferences.Editor
 | 
				
			||||||
    private lateinit var userIdentifier: String
 | 
					    private lateinit var userIdentifier: String
 | 
				
			||||||
    private var logErrors: Boolean = false
 | 
					 | 
				
			||||||
    private lateinit var appColors: AppColors
 | 
					    private lateinit var appColors: AppColors
 | 
				
			||||||
 | 
					    private lateinit var binding: ActivityLoginBinding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
        appColors = AppColors(this@LoginActivity)
 | 
					        appColors = AppColors(this@LoginActivity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super.onCreate(savedInstanceState)
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
 | 
					        binding = ActivityLoginBinding.inflate(layoutInflater)
 | 
				
			||||||
 | 
					        val view = binding.root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setContentView(R.layout.activity_login)
 | 
					        setContentView(view)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setSupportActionBar(toolbar)
 | 
					        setSupportActionBar(binding.toolbar)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        handleBaseUrlFail()
 | 
					        handleBaseUrlFail()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
					        settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
				
			||||||
        userIdentifier = settings.getString("unique_id", "")
 | 
					        userIdentifier = settings.getString("unique_id", "")!!
 | 
				
			||||||
        logErrors = settings.getBoolean("login_debug", false)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        editor = settings.edit()
 | 
					        editor = settings.edit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (settings.getString("url", "").isNotEmpty()) {
 | 
					        if (settings.getString("url", "")!!.isNotEmpty()) {
 | 
				
			||||||
            goToMain()
 | 
					            goToMain()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -69,14 +66,14 @@ class LoginActivity : AppCompatActivity() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private fun handleActions() {
 | 
					    private fun handleActions() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        withSelfhostedCert.setOnCheckedChangeListener { _, b ->
 | 
					        binding.withSelfhostedCert.setOnCheckedChangeListener { _, b ->
 | 
				
			||||||
            isWithSelfSignedCert = !isWithSelfSignedCert
 | 
					            isWithSelfSignedCert = !isWithSelfSignedCert
 | 
				
			||||||
            val visi: Int = if (b) View.VISIBLE else View.GONE
 | 
					            val visi: Int = if (b) View.VISIBLE else View.GONE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            warningText.visibility = visi
 | 
					            binding.warningText.visibility = visi
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        passwordView.setOnEditorActionListener(
 | 
					        binding.passwordView.setOnEditorActionListener(
 | 
				
			||||||
            TextView.OnEditorActionListener { _, id, _ ->
 | 
					            TextView.OnEditorActionListener { _, id, _ ->
 | 
				
			||||||
                if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
 | 
					                if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
 | 
				
			||||||
                    attemptLogin()
 | 
					                    attemptLogin()
 | 
				
			||||||
@@ -86,22 +83,22 @@ class LoginActivity : AppCompatActivity() {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        signInButton.setOnClickListener { attemptLogin() }
 | 
					        binding.signInButton.setOnClickListener { attemptLogin() }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        withLogin.setOnCheckedChangeListener { _, b ->
 | 
					        binding.withLogin.setOnCheckedChangeListener { _, b ->
 | 
				
			||||||
            isWithLogin = !isWithLogin
 | 
					            isWithLogin = !isWithLogin
 | 
				
			||||||
            val visi: Int = if (b) View.VISIBLE else View.GONE
 | 
					            val visi: Int = if (b) View.VISIBLE else View.GONE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            loginLayout.visibility = visi
 | 
					            binding.loginView.visibility = visi
 | 
				
			||||||
            passwordLayout.visibility = visi
 | 
					            binding.passwordView.visibility = visi
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        withHttpLogin.setOnCheckedChangeListener { _, b ->
 | 
					        binding.withHttpLogin.setOnCheckedChangeListener { _, b ->
 | 
				
			||||||
            isWithHTTPLogin = !isWithHTTPLogin
 | 
					            isWithHTTPLogin = !isWithHTTPLogin
 | 
				
			||||||
            val visi: Int = if (b) View.VISIBLE else View.GONE
 | 
					            val visi: Int = if (b) View.VISIBLE else View.GONE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            httpLoginInput.visibility = visi
 | 
					            binding.httpLoginView.visibility = visi
 | 
				
			||||||
            httpPasswordInput.visibility = visi
 | 
					            binding.httpPasswordView.visibility = visi
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -112,9 +109,8 @@ class LoginActivity : AppCompatActivity() {
 | 
				
			|||||||
            alertDialog.setMessage(getString(R.string.base_url_error))
 | 
					            alertDialog.setMessage(getString(R.string.base_url_error))
 | 
				
			||||||
            alertDialog.setButton(
 | 
					            alertDialog.setButton(
 | 
				
			||||||
                AlertDialog.BUTTON_NEUTRAL,
 | 
					                AlertDialog.BUTTON_NEUTRAL,
 | 
				
			||||||
                "OK",
 | 
					                "OK"
 | 
				
			||||||
                { dialog, _ -> dialog.dismiss() }
 | 
					            ) { dialog, _ -> dialog.dismiss() }
 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            alertDialog.show()
 | 
					            alertDialog.show()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -128,25 +124,25 @@ class LoginActivity : AppCompatActivity() {
 | 
				
			|||||||
    private fun attemptLogin() {
 | 
					    private fun attemptLogin() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Reset errors.
 | 
					        // Reset errors.
 | 
				
			||||||
        urlView.error = null
 | 
					        binding.urlView.error = null
 | 
				
			||||||
        loginView.error = null
 | 
					        binding.loginView.error = null
 | 
				
			||||||
        httpLoginView.error = null
 | 
					        binding.httpLoginView.error = null
 | 
				
			||||||
        passwordView.error = null
 | 
					        binding.passwordView.error = null
 | 
				
			||||||
        httpPasswordView.error = null
 | 
					        binding.httpPasswordView.error = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Store values at the time of the login attempt.
 | 
					        // Store values at the time of the login attempt.
 | 
				
			||||||
        val url = urlView.text.toString()
 | 
					        val url = binding.urlView.text.toString()
 | 
				
			||||||
        val login = loginView.text.toString()
 | 
					        val login = binding.loginView.text.toString()
 | 
				
			||||||
        val httpLogin = httpLoginView.text.toString()
 | 
					        val httpLogin = binding.httpLoginView.text.toString()
 | 
				
			||||||
        val password = passwordView.text.toString()
 | 
					        val password = binding.passwordView.text.toString()
 | 
				
			||||||
        val httpPassword = httpPasswordView.text.toString()
 | 
					        val httpPassword = binding.httpPasswordView.text.toString()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var cancel = false
 | 
					        var cancel = false
 | 
				
			||||||
        var focusView: View? = null
 | 
					        var focusView: View? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!url.isBaseUrlValid()) {
 | 
					        if (!url.isBaseUrlValid(this@LoginActivity)) {
 | 
				
			||||||
            urlView.error = getString(R.string.login_url_problem)
 | 
					            binding.urlView.error = getString(R.string.login_url_problem)
 | 
				
			||||||
            focusView = urlView
 | 
					            focusView = binding.urlView
 | 
				
			||||||
            cancel = true
 | 
					            cancel = true
 | 
				
			||||||
            inValidCount++
 | 
					            inValidCount++
 | 
				
			||||||
            if (inValidCount == 3) {
 | 
					            if (inValidCount == 3) {
 | 
				
			||||||
@@ -155,24 +151,37 @@ class LoginActivity : AppCompatActivity() {
 | 
				
			|||||||
                alertDialog.setMessage(getString(R.string.text_wrong_url))
 | 
					                alertDialog.setMessage(getString(R.string.text_wrong_url))
 | 
				
			||||||
                alertDialog.setButton(
 | 
					                alertDialog.setButton(
 | 
				
			||||||
                    AlertDialog.BUTTON_NEUTRAL,
 | 
					                    AlertDialog.BUTTON_NEUTRAL,
 | 
				
			||||||
                    "OK",
 | 
					                    "OK"
 | 
				
			||||||
                    { dialog, _ -> dialog.dismiss() }
 | 
					                ) { dialog, _ -> dialog.dismiss() }
 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                alertDialog.show()
 | 
					                alertDialog.show()
 | 
				
			||||||
                inValidCount = 0
 | 
					                inValidCount = 0
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (isWithLogin || isWithHTTPLogin) {
 | 
					        if (isWithLogin) {
 | 
				
			||||||
            if (TextUtils.isEmpty(password)) {
 | 
					            if (TextUtils.isEmpty(password)) {
 | 
				
			||||||
                passwordView.error = getString(R.string.error_invalid_password)
 | 
					                binding.passwordView.error = getString(R.string.error_invalid_password)
 | 
				
			||||||
                focusView = passwordView
 | 
					                focusView = binding.passwordView
 | 
				
			||||||
                cancel = true
 | 
					                cancel = true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (TextUtils.isEmpty(login)) {
 | 
					            if (TextUtils.isEmpty(login)) {
 | 
				
			||||||
                loginView.error = getString(R.string.error_field_required)
 | 
					                binding.loginView.error = getString(R.string.error_field_required)
 | 
				
			||||||
                focusView = loginView
 | 
					                focusView = binding.loginView
 | 
				
			||||||
 | 
					                cancel = true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isWithHTTPLogin) {
 | 
				
			||||||
 | 
					            if (TextUtils.isEmpty(httpPassword)) {
 | 
				
			||||||
 | 
					                binding.httpPasswordView.error = getString(R.string.error_invalid_password)
 | 
				
			||||||
 | 
					                focusView = binding.httpPasswordView
 | 
				
			||||||
 | 
					                cancel = true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (TextUtils.isEmpty(httpLogin)) {
 | 
				
			||||||
 | 
					                binding.httpLoginView.error = getString(R.string.error_field_required)
 | 
				
			||||||
 | 
					                focusView = binding.httpLoginView
 | 
				
			||||||
                cancel = true
 | 
					                cancel = true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -194,75 +203,72 @@ class LoginActivity : AppCompatActivity() {
 | 
				
			|||||||
                this,
 | 
					                this,
 | 
				
			||||||
                this@LoginActivity,
 | 
					                this@LoginActivity,
 | 
				
			||||||
                isWithSelfSignedCert,
 | 
					                isWithSelfSignedCert,
 | 
				
			||||||
                isWithSelfSignedCert
 | 
					                -1L
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            api.login().enqueue(object : Callback<SuccessResponse> {
 | 
					 | 
				
			||||||
                private fun preferenceError(t: Throwable) {
 | 
					 | 
				
			||||||
                    editor.remove("url")
 | 
					 | 
				
			||||||
                    editor.remove("login")
 | 
					 | 
				
			||||||
                    editor.remove("httpUserName")
 | 
					 | 
				
			||||||
                    editor.remove("password")
 | 
					 | 
				
			||||||
                    editor.remove("httpPassword")
 | 
					 | 
				
			||||||
                    editor.apply()
 | 
					 | 
				
			||||||
                    urlView.error = getString(R.string.wrong_infos)
 | 
					 | 
				
			||||||
                    loginView.error = getString(R.string.wrong_infos)
 | 
					 | 
				
			||||||
                    passwordView.error = getString(R.string.wrong_infos)
 | 
					 | 
				
			||||||
                    httpLoginView.error = getString(R.string.wrong_infos)
 | 
					 | 
				
			||||||
                    httpPasswordView.error = getString(R.string.wrong_infos)
 | 
					 | 
				
			||||||
                    if (logErrors) {
 | 
					 | 
				
			||||||
                        ACRA.getErrorReporter().maybeHandleSilentException(t, this@LoginActivity)
 | 
					 | 
				
			||||||
                        Toast.makeText(
 | 
					 | 
				
			||||||
                            this@LoginActivity,
 | 
					 | 
				
			||||||
                            t.message,
 | 
					 | 
				
			||||||
                            Toast.LENGTH_LONG
 | 
					 | 
				
			||||||
                        ).show()
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    showProgress(false)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                override fun onResponse(
 | 
					            if (this@LoginActivity.isNetworkAccessible(this@LoginActivity.findViewById(R.id.loginForm))) {
 | 
				
			||||||
                    call: Call<SuccessResponse>,
 | 
					                api.login().enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
                    response: Response<SuccessResponse>
 | 
					                    private fun preferenceError(t: Throwable) {
 | 
				
			||||||
                ) {
 | 
					                        editor.remove("url")
 | 
				
			||||||
                    if (response.body() != null && response.body()!!.isSuccess) {
 | 
					                        editor.remove("login")
 | 
				
			||||||
                        goToMain()
 | 
					                        editor.remove("httpUserName")
 | 
				
			||||||
                    } else {
 | 
					                        editor.remove("password")
 | 
				
			||||||
                        preferenceError(Exception("No response body..."))
 | 
					                        editor.remove("httpPassword")
 | 
				
			||||||
 | 
					                        editor.apply()
 | 
				
			||||||
 | 
					                        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.httpLoginView.error = getString(R.string.wrong_infos)
 | 
				
			||||||
 | 
					                        binding.httpPasswordView.error = getString(R.string.wrong_infos)
 | 
				
			||||||
 | 
					                        showProgress(false)
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
					                    override fun onResponse(
 | 
				
			||||||
                    preferenceError(t)
 | 
					                        call: Call<SuccessResponse>,
 | 
				
			||||||
                }
 | 
					                        response: Response<SuccessResponse>
 | 
				
			||||||
            })
 | 
					                    ) {
 | 
				
			||||||
 | 
					                        if (response.body() != null && response.body()!!.isSuccess) {
 | 
				
			||||||
 | 
					                            goToMain()
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            preferenceError(Exception("No response body..."))
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
				
			||||||
 | 
					                        preferenceError(t)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                showProgress(false)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun showProgress(show: Boolean) {
 | 
					    private fun showProgress(show: Boolean) {
 | 
				
			||||||
        val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime)
 | 
					        val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        loginForm.visibility = if (show) View.GONE else View.VISIBLE
 | 
					        binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
 | 
				
			||||||
        loginForm
 | 
					        binding.loginForm
 | 
				
			||||||
            .animate()
 | 
					            .animate()
 | 
				
			||||||
            .setDuration(shortAnimTime.toLong())
 | 
					            .setDuration(shortAnimTime.toLong())
 | 
				
			||||||
            .alpha(
 | 
					            .alpha(
 | 
				
			||||||
                if (show) 0F else 1F
 | 
					                if (show) 0F else 1F
 | 
				
			||||||
            ).setListener(object : AnimatorListenerAdapter() {
 | 
					            ).setListener(object : AnimatorListenerAdapter() {
 | 
				
			||||||
            override fun onAnimationEnd(animation: Animator) {
 | 
					            override fun onAnimationEnd(animation: Animator) {
 | 
				
			||||||
                loginForm.visibility = if (show) View.GONE else View.VISIBLE
 | 
					                binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        loginProgress.visibility = if (show) View.VISIBLE else View.GONE
 | 
					        binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
 | 
				
			||||||
        loginProgress
 | 
					        binding.loginProgress
 | 
				
			||||||
            .animate()
 | 
					            .animate()
 | 
				
			||||||
            .setDuration(shortAnimTime.toLong())
 | 
					            .setDuration(shortAnimTime.toLong())
 | 
				
			||||||
            .alpha(
 | 
					            .alpha(
 | 
				
			||||||
                if (show) 1F else 0F
 | 
					                if (show) 1F else 0F
 | 
				
			||||||
            ).setListener(object : AnimatorListenerAdapter() {
 | 
					            ).setListener(object : AnimatorListenerAdapter() {
 | 
				
			||||||
            override fun onAnimationEnd(animation: Animator) {
 | 
					            override fun onAnimationEnd(animation: Animator) {
 | 
				
			||||||
                loginProgress.visibility = if (show) View.VISIBLE else View.GONE
 | 
					                binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@@ -270,29 +276,19 @@ class LoginActivity : AppCompatActivity() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
 | 
					    override fun onCreateOptionsMenu(menu: Menu): Boolean {
 | 
				
			||||||
        menuInflater.inflate(R.menu.login_menu, menu)
 | 
					        menuInflater.inflate(R.menu.login_menu, menu)
 | 
				
			||||||
        menu.findItem(R.id.login_debug).isChecked = logErrors
 | 
					 | 
				
			||||||
        return true
 | 
					        return true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
					    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
				
			||||||
        when (item.itemId) {
 | 
					        return when (item.itemId) {
 | 
				
			||||||
            R.id.about -> {
 | 
					            R.id.about -> {
 | 
				
			||||||
                LibsBuilder()
 | 
					                LibsBuilder()
 | 
				
			||||||
                    .withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
 | 
					 | 
				
			||||||
                    .withAboutIconShown(true)
 | 
					                    .withAboutIconShown(true)
 | 
				
			||||||
                    .withAboutVersionShown(true)
 | 
					                    .withAboutVersionShown(true)
 | 
				
			||||||
                    .start(this)
 | 
					                    .start(this)
 | 
				
			||||||
                return true
 | 
					                true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            R.id.login_debug -> {
 | 
					            else -> super.onOptionsItemSelected(item)
 | 
				
			||||||
                val newState = !item.isChecked
 | 
					 | 
				
			||||||
                item.isChecked = newState
 | 
					 | 
				
			||||||
                logErrors = newState
 | 
					 | 
				
			||||||
                editor.putBoolean("login_debug", newState)
 | 
					 | 
				
			||||||
                editor.apply()
 | 
					 | 
				
			||||||
                return true
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else -> return super.onOptionsItemSelected(item)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,14 +2,18 @@ package apps.amine.bou.readerforselfoss
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					 | 
				
			||||||
import androidx.appcompat.app.AppCompatActivity
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.databinding.ActivityMainBinding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MainActivity : AppCompatActivity() {
 | 
					class MainActivity : AppCompatActivity() {
 | 
				
			||||||
 | 
					    private lateinit var binding: ActivityMainBinding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
        super.onCreate(savedInstanceState)
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
        setContentView(R.layout.activity_main)
 | 
					        binding = ActivityMainBinding.inflate(layoutInflater)
 | 
				
			||||||
 | 
					        val view = binding.root
 | 
				
			||||||
 | 
					        setContentView(view)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val intent = Intent(this, LoginActivity::class.java)
 | 
					        val intent = Intent(this, LoginActivity::class.java)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,54 +1,32 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss
 | 
					package apps.amine.bou.readerforselfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.app.NotificationChannel
 | 
				
			||||||
 | 
					import android.app.NotificationManager
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.graphics.drawable.Drawable
 | 
					import android.graphics.drawable.Drawable
 | 
				
			||||||
import android.net.Uri
 | 
					import android.net.Uri
 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					import android.os.Build
 | 
				
			||||||
import androidx.multidex.MultiDexApplication
 | 
					import androidx.preference.PreferenceManager
 | 
				
			||||||
import android.widget.ImageView
 | 
					import android.widget.ImageView
 | 
				
			||||||
 | 
					import androidx.multidex.MultiDexApplication
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.Config
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth
 | 
				
			||||||
import com.bumptech.glide.Glide
 | 
					import com.bumptech.glide.Glide
 | 
				
			||||||
import com.bumptech.glide.request.RequestOptions
 | 
					import com.bumptech.glide.request.RequestOptions
 | 
				
			||||||
import com.ftinc.scoop.Scoop
 | 
					import com.ftinc.scoop.Scoop
 | 
				
			||||||
import com.github.stkent.amplify.feedback.DefaultEmailFeedbackCollector
 | 
					 | 
				
			||||||
import com.github.stkent.amplify.feedback.GooglePlayStoreFeedbackCollector
 | 
					 | 
				
			||||||
import com.github.stkent.amplify.tracking.Amplify
 | 
					 | 
				
			||||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
 | 
					import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
 | 
				
			||||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
 | 
					import com.mikepenz.materialdrawer.util.DrawerImageLoader
 | 
				
			||||||
import org.acra.ACRA
 | 
					 | 
				
			||||||
import org.acra.ReportField
 | 
					 | 
				
			||||||
import org.acra.annotation.AcraCore
 | 
					 | 
				
			||||||
import org.acra.annotation.AcraDialog
 | 
					 | 
				
			||||||
import org.acra.annotation.AcraHttpSender
 | 
					 | 
				
			||||||
import org.acra.sender.HttpSender
 | 
					 | 
				
			||||||
import java.io.IOException
 | 
					 | 
				
			||||||
import java.util.UUID.randomUUID
 | 
					import java.util.UUID.randomUUID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
@AcraHttpSender(uri = "http://amine-bou.fr:5984/acra-selfoss/_design/acra-storage/_update/report",
 | 
					 | 
				
			||||||
                basicAuthLogin = "selfoss",
 | 
					 | 
				
			||||||
                basicAuthPassword = "selfoss",
 | 
					 | 
				
			||||||
                httpMethod = HttpSender.Method.PUT)
 | 
					 | 
				
			||||||
@AcraDialog(resText = R.string.crash_dialog_text,
 | 
					 | 
				
			||||||
            resCommentPrompt = R.string.crash_dialog_comment,
 | 
					 | 
				
			||||||
            resTheme = android.R.style.Theme_DeviceDefault_Dialog)
 | 
					 | 
				
			||||||
@AcraCore(reportContent = [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],
 | 
					 | 
				
			||||||
          buildConfigClass = BuildConfig::class)
 | 
					 | 
				
			||||||
class MyApp : MultiDexApplication() {
 | 
					class MyApp : MultiDexApplication() {
 | 
				
			||||||
 | 
					    private lateinit var config: Config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreate() {
 | 
					    override fun onCreate() {
 | 
				
			||||||
        super.onCreate()
 | 
					        super.onCreate()
 | 
				
			||||||
 | 
					        config = Config(baseContext)
 | 
				
			||||||
        initAmplify()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
					        val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
				
			||||||
        if (prefs.getString("unique_id", "").isEmpty()) {
 | 
					        if (prefs.getString("unique_id", "")!!.isEmpty()) {
 | 
				
			||||||
            val editor = prefs.edit()
 | 
					            val editor = prefs.edit()
 | 
				
			||||||
            editor.putString("unique_id", randomUUID().toString())
 | 
					            editor.putString("unique_id", randomUUID().toString())
 | 
				
			||||||
            editor.apply()
 | 
					            editor.apply()
 | 
				
			||||||
@@ -59,42 +37,41 @@ class MyApp : MultiDexApplication() {
 | 
				
			|||||||
        initTheme()
 | 
					        initTheme()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tryToHandleBug()
 | 
					        tryToHandleBug()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        handleNotificationChannels()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun attachBaseContext(base: Context?) {
 | 
					    private fun handleNotificationChannels() {
 | 
				
			||||||
        super.attachBaseContext(base)
 | 
					        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 | 
				
			||||||
        val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
					            val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
 | 
				
			||||||
        ACRA.init(this)
 | 
					 | 
				
			||||||
        ACRA.getErrorReporter().putCustomData("unique_id", prefs.getString("unique_id", ""))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					            val name = getString(R.string.notification_channel_sync)
 | 
				
			||||||
 | 
					            val importance = NotificationManager.IMPORTANCE_LOW
 | 
				
			||||||
 | 
					            val mChannel = NotificationChannel(Config.syncChannelId, name, importance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun initAmplify() {
 | 
					            val newItemsChannelname = getString(R.string.new_items_channel_sync)
 | 
				
			||||||
        Amplify.initSharedInstance(this)
 | 
					            val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
 | 
				
			||||||
            .setPositiveFeedbackCollectors(GooglePlayStoreFeedbackCollector())
 | 
					            val newItemsChannelmChannel = NotificationChannel(Config.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
 | 
				
			||||||
            .setCriticalFeedbackCollectors(DefaultEmailFeedbackCollector(Config.feedbackEmail))
 | 
					
 | 
				
			||||||
            .applyAllDefaultRules()
 | 
					            notificationManager.createNotificationChannel(mChannel)
 | 
				
			||||||
 | 
					            notificationManager.createNotificationChannel(newItemsChannelmChannel)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun initDrawerImageLoader() {
 | 
					    private fun initDrawerImageLoader() {
 | 
				
			||||||
        DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
 | 
					        DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
 | 
				
			||||||
            override fun set(
 | 
					            override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
 | 
				
			||||||
                imageView: ImageView?,
 | 
					 | 
				
			||||||
                uri: Uri?,
 | 
					 | 
				
			||||||
                placeholder: Drawable?,
 | 
					 | 
				
			||||||
                tag: String?
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
                Glide.with(imageView?.context)
 | 
					                Glide.with(imageView?.context)
 | 
				
			||||||
                    .load(uri)
 | 
					                    .loadMaybeBasicAuth(config, uri.toString())
 | 
				
			||||||
                    .apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
 | 
					                    .apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
 | 
				
			||||||
                    .into(imageView)
 | 
					                    .into(imageView)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            override fun cancel(imageView: ImageView?) {
 | 
					            override fun cancel(imageView: ImageView) {
 | 
				
			||||||
                Glide.with(imageView?.context).clear(imageView)
 | 
					                Glide.with(imageView?.context).clear(imageView)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            override fun placeholder(ctx: Context?, tag: String?): Drawable {
 | 
					            override fun placeholder(ctx: Context, tag: String?): Drawable {
 | 
				
			||||||
                return baseContext.resources.getDrawable(R.mipmap.ic_launcher)
 | 
					                return baseContext.resources.getDrawable(R.mipmap.ic_launcher)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,14 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss
 | 
					package apps.amine.bou.readerforselfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.content.SharedPreferences
 | 
				
			||||||
 | 
					import android.graphics.Color
 | 
				
			||||||
 | 
					import android.graphics.PorterDuff
 | 
				
			||||||
 | 
					import android.graphics.PorterDuffColorFilter
 | 
				
			||||||
import android.graphics.drawable.ColorDrawable
 | 
					import android.graphics.drawable.ColorDrawable
 | 
				
			||||||
import android.os.Build
 | 
					import android.os.Build
 | 
				
			||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					import androidx.preference.PreferenceManager
 | 
				
			||||||
import androidx.fragment.app.FragmentManager
 | 
					import androidx.fragment.app.FragmentManager
 | 
				
			||||||
import androidx.fragment.app.FragmentStatePagerAdapter
 | 
					import androidx.fragment.app.FragmentStatePagerAdapter
 | 
				
			||||||
import androidx.core.content.ContextCompat
 | 
					import androidx.core.content.ContextCompat
 | 
				
			||||||
@@ -12,29 +17,27 @@ import androidx.appcompat.app.AppCompatActivity
 | 
				
			|||||||
import android.view.Menu
 | 
					import android.view.Menu
 | 
				
			||||||
import android.view.MenuItem
 | 
					import android.view.MenuItem
 | 
				
			||||||
import android.view.ViewGroup
 | 
					import android.view.ViewGroup
 | 
				
			||||||
import android.widget.Toast
 | 
					import androidx.room.Room
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
					import apps.amine.bou.readerforselfoss.databinding.ActivityReaderBinding
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.fragments.ArticleFragment
 | 
					import apps.amine.bou.readerforselfoss.fragments.ArticleFragment
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_3_4
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
					import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.Toppings
 | 
					import apps.amine.bou.readerforselfoss.themes.Toppings
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.transformers.DepthPageTransformer
 | 
					import apps.amine.bou.readerforselfoss.transformers.DepthPageTransformer
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.succeeded
 | 
					import apps.amine.bou.readerforselfoss.utils.SharedItems
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.toggleStar
 | 
					import apps.amine.bou.readerforselfoss.utils.toggleStar
 | 
				
			||||||
import com.ftinc.scoop.Scoop
 | 
					import com.ftinc.scoop.Scoop
 | 
				
			||||||
import kotlinx.android.synthetic.main.activity_reader.*
 | 
					 | 
				
			||||||
import me.relex.circleindicator.CircleIndicator
 | 
					import me.relex.circleindicator.CircleIndicator
 | 
				
			||||||
import org.acra.ACRA
 | 
					 | 
				
			||||||
import retrofit2.Call
 | 
					 | 
				
			||||||
import retrofit2.Callback
 | 
					 | 
				
			||||||
import retrofit2.Response
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ReaderActivity : AppCompatActivity() {
 | 
					class ReaderActivity : AppCompatActivity() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var markOnScroll: Boolean = false
 | 
					    private var markOnScroll: Boolean = false
 | 
				
			||||||
    private var debugReadingItems: Boolean = false
 | 
					 | 
				
			||||||
    private var currentItem: Int = 0
 | 
					    private var currentItem: Int = 0
 | 
				
			||||||
    private lateinit var userIdentifier: String
 | 
					    private lateinit var userIdentifier: String
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -42,9 +45,20 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private lateinit var toolbarMenu: Menu
 | 
					    private lateinit var toolbarMenu: Menu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lateinit var db: AppDatabase
 | 
				
			||||||
 | 
					    private lateinit var prefs: SharedPreferences
 | 
				
			||||||
 | 
					    private lateinit var binding: ActivityReaderBinding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var activeAlignment: Int = 1
 | 
				
			||||||
 | 
					    val JUSTIFY = 1
 | 
				
			||||||
 | 
					    val ALIGN_LEFT = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun showMenuItem(willAddToFavorite: Boolean) {
 | 
					    private fun showMenuItem(willAddToFavorite: Boolean) {
 | 
				
			||||||
        toolbarMenu.findItem(R.id.save).isVisible = willAddToFavorite
 | 
					        if (willAddToFavorite) {
 | 
				
			||||||
        toolbarMenu.findItem(R.id.unsave).isVisible = !willAddToFavorite
 | 
					            toolbarMenu.findItem(R.id.star).icon.colorFilter = PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            toolbarMenu.findItem(R.id.star).icon.colorFilter = PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun canFavorite() {
 | 
					    private fun canFavorite() {
 | 
				
			||||||
@@ -55,32 +69,43 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
        showMenuItem(false)
 | 
					        showMenuItem(false)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lateinit var editor: SharedPreferences.Editor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
        super.onCreate(savedInstanceState)
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
 | 
					        binding = ActivityReaderBinding.inflate(layoutInflater)
 | 
				
			||||||
 | 
					        val view = binding.root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setContentView(R.layout.activity_reader)
 | 
					        setContentView(view)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        db = Room.databaseBuilder(
 | 
				
			||||||
 | 
					            applicationContext,
 | 
				
			||||||
 | 
					            AppDatabase::class.java, "selfoss-database"
 | 
				
			||||||
 | 
					        ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val scoop = Scoop.getInstance()
 | 
					        val scoop = Scoop.getInstance()
 | 
				
			||||||
        scoop.bind(this, Toppings.PRIMARY.value, toolBar)
 | 
					        scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar)
 | 
				
			||||||
        if  (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 | 
					        scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
 | 
				
			||||||
            scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setSupportActionBar(toolBar)
 | 
					        setSupportActionBar(binding.toolBar)
 | 
				
			||||||
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
					        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
				
			||||||
        supportActionBar?.setDisplayShowHomeEnabled(true)
 | 
					        supportActionBar?.setDisplayShowHomeEnabled(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val prefs = PreferenceManager.getDefaultSharedPreferences(this)
 | 
					        val settings =
 | 
				
			||||||
 | 
					            getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        debugReadingItems = prefs.getBoolean("read_debug", false)
 | 
					        prefs = PreferenceManager.getDefaultSharedPreferences(this)
 | 
				
			||||||
        userIdentifier = prefs.getString("unique_id", "")
 | 
					        editor = prefs.edit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        userIdentifier = prefs.getString("unique_id", "")!!
 | 
				
			||||||
        markOnScroll = prefs.getBoolean("mark_on_scroll", false)
 | 
					        markOnScroll = prefs.getBoolean("mark_on_scroll", false)
 | 
				
			||||||
 | 
					        activeAlignment = prefs.getInt("text_align", JUSTIFY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        api = SelfossApi(
 | 
					        api = SelfossApi(
 | 
				
			||||||
            this,
 | 
					            this,
 | 
				
			||||||
            this@ReaderActivity,
 | 
					            this@ReaderActivity,
 | 
				
			||||||
            prefs.getBoolean("isSelfSignedCert", false),
 | 
					            settings.getBoolean("isSelfSignedCert", false),
 | 
				
			||||||
            prefs.getBoolean("should_log_everything", false)
 | 
					            prefs.getString("api_timeout", "-1")!!.toLong()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (allItems.isEmpty()) {
 | 
					        if (allItems.isEmpty()) {
 | 
				
			||||||
@@ -89,10 +114,11 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        currentItem = intent.getIntExtra("currentItem", 0)
 | 
					        currentItem = intent.getIntExtra("currentItem", 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        readItem(allItems[currentItem].id)
 | 
					        readItem(allItems[currentItem])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pager.adapter = ScreenSlidePagerAdapter(supportFragmentManager, AppColors(this@ReaderActivity))
 | 
					        binding.pager.adapter =
 | 
				
			||||||
        pager.currentItem = currentItem
 | 
					                ScreenSlidePagerAdapter(supportFragmentManager, AppColors(this@ReaderActivity))
 | 
				
			||||||
 | 
					        binding.pager.currentItem = currentItem
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onResume() {
 | 
					    override fun onResume() {
 | 
				
			||||||
@@ -100,10 +126,10 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        notifyAdapter()
 | 
					        notifyAdapter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pager.setPageTransformer(true, DepthPageTransformer())
 | 
					        binding.pager.setPageTransformer(true, DepthPageTransformer())
 | 
				
			||||||
        (indicator as CircleIndicator).setViewPager(pager)
 | 
					        (binding.indicator as CircleIndicator).setViewPager(binding.pager)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pager.addOnPageChangeListener(
 | 
					        binding.pager.addOnPageChangeListener(
 | 
				
			||||||
            object : ViewPager.SimpleOnPageChangeListener() {
 | 
					            object : ViewPager.SimpleOnPageChangeListener() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                override fun onPageSelected(position: Int) {
 | 
					                override fun onPageSelected(position: Int) {
 | 
				
			||||||
@@ -113,67 +139,37 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        canFavorite()
 | 
					                        canFavorite()
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    readItem(allItems[pager.currentItem].id)
 | 
					                    readItem(allItems[position])
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun readItem(id: String) {
 | 
					    private fun readItem(item: Item) {
 | 
				
			||||||
        if (markOnScroll) {
 | 
					        if (markOnScroll) {
 | 
				
			||||||
            api.markItem(id).enqueue(
 | 
					                SharedItems.readItem(applicationContext, api, db, item)
 | 
				
			||||||
                object : Callback<SuccessResponse> {
 | 
					            }
 | 
				
			||||||
                    override fun onResponse(
 | 
					 | 
				
			||||||
                        call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                        response: Response<SuccessResponse>
 | 
					 | 
				
			||||||
                    ) {
 | 
					 | 
				
			||||||
                        if (!response.succeeded() && debugReadingItems) {
 | 
					 | 
				
			||||||
                            val message =
 | 
					 | 
				
			||||||
                                "message: ${response.message()} " +
 | 
					 | 
				
			||||||
                                        "response isSuccess: ${response.isSuccessful} " +
 | 
					 | 
				
			||||||
                                        "response code: ${response.code()} " +
 | 
					 | 
				
			||||||
                                        "response message: ${response.message()} " +
 | 
					 | 
				
			||||||
                                        "response errorBody: ${response.errorBody()?.string()} " +
 | 
					 | 
				
			||||||
                                        "body success: ${response.body()?.success} " +
 | 
					 | 
				
			||||||
                                        "body isSuccess: ${response.body()?.isSuccess}"
 | 
					 | 
				
			||||||
                            ACRA.getErrorReporter()
 | 
					 | 
				
			||||||
                                .maybeHandleSilentException(Exception(message), this@ReaderActivity)
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    override fun onFailure(
 | 
					 | 
				
			||||||
                        call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                        t: Throwable
 | 
					 | 
				
			||||||
                    ) {
 | 
					 | 
				
			||||||
                        if (debugReadingItems) {
 | 
					 | 
				
			||||||
                            ACRA.getErrorReporter().maybeHandleSilentException(t, this@ReaderActivity)
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun notifyAdapter() {
 | 
					    private fun notifyAdapter() {
 | 
				
			||||||
        (pager.adapter as ScreenSlidePagerAdapter).notifyDataSetChanged()
 | 
					        (binding.pager.adapter as ScreenSlidePagerAdapter).notifyDataSetChanged()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onPause() {
 | 
					    override fun onPause() {
 | 
				
			||||||
        super.onPause()
 | 
					        super.onPause()
 | 
				
			||||||
        if (markOnScroll) {
 | 
					        if (markOnScroll) {
 | 
				
			||||||
            pager.clearOnPageChangeListeners()
 | 
					            binding.pager.clearOnPageChangeListeners()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onSaveInstanceState(oldInstanceState: Bundle?) {
 | 
					    override fun onSaveInstanceState(oldInstanceState: Bundle) {
 | 
				
			||||||
        super.onSaveInstanceState(oldInstanceState)
 | 
					        super.onSaveInstanceState(oldInstanceState)
 | 
				
			||||||
        oldInstanceState!!.clear()
 | 
					        oldInstanceState.clear()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private inner class ScreenSlidePagerAdapter(fm: FragmentManager, val appColors: AppColors) :
 | 
					    private inner class ScreenSlidePagerAdapter(fm: FragmentManager, val appColors: AppColors) :
 | 
				
			||||||
        FragmentStatePagerAdapter(fm) {
 | 
					        FragmentStatePagerAdapter(fm) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        override fun getCount(): Int {
 | 
					        override fun getCount(): Int {
 | 
				
			||||||
            return allItems.size
 | 
					            return allItems.size
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -185,10 +181,20 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
        override fun startUpdate(container: ViewGroup) {
 | 
					        override fun startUpdate(container: ViewGroup) {
 | 
				
			||||||
            super.startUpdate(container)
 | 
					            super.startUpdate(container)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            container.background = ColorDrawable(ContextCompat.getColor(this@ReaderActivity, appColors.colorBackground))
 | 
					            container.background = ColorDrawable(
 | 
				
			||||||
 | 
					                ContextCompat.getColor(
 | 
				
			||||||
 | 
					                    this@ReaderActivity,
 | 
				
			||||||
 | 
					                    appColors.colorBackground
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun alignmentMenu(showJustify: Boolean) {
 | 
				
			||||||
 | 
					        toolbarMenu.findItem(R.id.align_left).isVisible = !showJustify
 | 
				
			||||||
 | 
					        toolbarMenu.findItem(R.id.align_justify).isVisible = showJustify
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
 | 
					    override fun onCreateOptionsMenu(menu: Menu): Boolean {
 | 
				
			||||||
        val inflater = menuInflater
 | 
					        val inflater = menuInflater
 | 
				
			||||||
        inflater.inflate(R.menu.reader_menu, menu)
 | 
					        inflater.inflate(R.menu.reader_menu, menu)
 | 
				
			||||||
@@ -199,68 +205,76 @@ class ReaderActivity : AppCompatActivity() {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            canFavorite()
 | 
					            canFavorite()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (activeAlignment == JUSTIFY) {
 | 
				
			||||||
 | 
					            alignmentMenu(false)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            alignmentMenu(true)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return true
 | 
					        return true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
					    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
				
			||||||
 | 
					        fun afterSave() {
 | 
				
			||||||
 | 
					            allItems[binding.pager.currentItem] =
 | 
				
			||||||
 | 
					                    allItems[binding.pager.currentItem].toggleStar()
 | 
				
			||||||
 | 
					            notifyAdapter()
 | 
				
			||||||
 | 
					            canRemoveFromFavorite()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fun afterUnsave() {
 | 
				
			||||||
 | 
					            allItems[binding.pager.currentItem] = allItems[binding.pager.currentItem].toggleStar()
 | 
				
			||||||
 | 
					            notifyAdapter()
 | 
				
			||||||
 | 
					            canFavorite()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        when (item.itemId) {
 | 
					        when (item.itemId) {
 | 
				
			||||||
            android.R.id.home -> {
 | 
					            android.R.id.home -> {
 | 
				
			||||||
                onBackPressed()
 | 
					                onBackPressed()
 | 
				
			||||||
                return true
 | 
					                return true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            R.id.save -> {
 | 
					            R.id.star -> {
 | 
				
			||||||
                api.starrItem(allItems[pager.currentItem].id)
 | 
					                if (allItems[binding.pager.currentItem].starred) {
 | 
				
			||||||
                    .enqueue(object : Callback<SuccessResponse> {
 | 
					                    SharedItems.unstarItem(
 | 
				
			||||||
                        override fun onResponse(
 | 
					                        this@ReaderActivity,
 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					                        api,
 | 
				
			||||||
                            response: Response<SuccessResponse>
 | 
					                        db,
 | 
				
			||||||
                        ) {
 | 
					                        allItems[binding.pager.currentItem]
 | 
				
			||||||
                            allItems[pager.currentItem] = allItems[pager.currentItem].toggleStar()
 | 
					                    )
 | 
				
			||||||
                            notifyAdapter()
 | 
					                    afterUnsave()
 | 
				
			||||||
                            canRemoveFromFavorite()
 | 
					                } else {
 | 
				
			||||||
                        }
 | 
					                    SharedItems.starItem(
 | 
				
			||||||
 | 
					                        this@ReaderActivity,
 | 
				
			||||||
                        override fun onFailure(
 | 
					                        api,
 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					                        db,
 | 
				
			||||||
                            t: Throwable
 | 
					                        allItems[binding.pager.currentItem]
 | 
				
			||||||
                        ) {
 | 
					                    )
 | 
				
			||||||
                            Toast.makeText(
 | 
					                    afterSave()
 | 
				
			||||||
                                baseContext,
 | 
					                }
 | 
				
			||||||
                                R.string.cant_mark_favortie,
 | 
					 | 
				
			||||||
                                Toast.LENGTH_SHORT
 | 
					 | 
				
			||||||
                            ).show()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            R.id.unsave -> {
 | 
					            R.id.align_left -> {
 | 
				
			||||||
                api.unstarrItem(allItems[pager.currentItem].id)
 | 
					                editor.putInt("text_align", ALIGN_LEFT)
 | 
				
			||||||
                    .enqueue(object : Callback<SuccessResponse> {
 | 
					                editor.apply()
 | 
				
			||||||
                        override fun onResponse(
 | 
					                alignmentMenu(true)
 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					                refreshFragment()
 | 
				
			||||||
                            response: Response<SuccessResponse>
 | 
					            }
 | 
				
			||||||
                        ) {
 | 
					            R.id.align_justify -> {
 | 
				
			||||||
                            allItems[pager.currentItem] = allItems[pager.currentItem].toggleStar()
 | 
					                editor.putInt("text_align", JUSTIFY)
 | 
				
			||||||
                            notifyAdapter()
 | 
					                editor.apply()
 | 
				
			||||||
                            canFavorite()
 | 
					                alignmentMenu(false)
 | 
				
			||||||
                        }
 | 
					                refreshFragment()
 | 
				
			||||||
 | 
					 | 
				
			||||||
                        override fun onFailure(
 | 
					 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                            t: Throwable
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                            Toast.makeText(
 | 
					 | 
				
			||||||
                                baseContext,
 | 
					 | 
				
			||||||
                                R.string.cant_unmark_favortie,
 | 
					 | 
				
			||||||
                                Toast.LENGTH_SHORT
 | 
					 | 
				
			||||||
                            ).show()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return super.onOptionsItemSelected(item)
 | 
					        return super.onOptionsItemSelected(item)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun refreshFragment() {
 | 
				
			||||||
 | 
					        finish()
 | 
				
			||||||
 | 
					        overridePendingTransition(0, 0)
 | 
				
			||||||
 | 
					        startActivity(intent)
 | 
				
			||||||
 | 
					        overridePendingTransition(0, 0)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
        var allItems: ArrayList<Item> = ArrayList()
 | 
					        var allItems: ArrayList<Item> = ArrayList()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,22 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss
 | 
					package apps.amine.bou.readerforselfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.content.res.ColorStateList
 | 
					import android.content.res.ColorStateList
 | 
				
			||||||
import android.os.Build
 | 
					 | 
				
			||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					import androidx.preference.PreferenceManager
 | 
				
			||||||
import androidx.appcompat.app.AppCompatActivity
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
					import androidx.recyclerview.widget.LinearLayoutManager
 | 
				
			||||||
import android.widget.Toast
 | 
					import android.widget.Toast
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
 | 
					import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Source
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Source
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.databinding.ActivitySourcesBinding
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
					import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.Toppings
 | 
					import apps.amine.bou.readerforselfoss.themes.Toppings
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
 | 
				
			||||||
import com.ftinc.scoop.Scoop
 | 
					import com.ftinc.scoop.Scoop
 | 
				
			||||||
import kotlinx.android.synthetic.main.activity_sources.*
 | 
					 | 
				
			||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
import retrofit2.Callback
 | 
					import retrofit2.Callback
 | 
				
			||||||
import retrofit2.Response
 | 
					import retrofit2.Response
 | 
				
			||||||
@@ -22,80 +24,91 @@ import retrofit2.Response
 | 
				
			|||||||
class SourcesActivity : AppCompatActivity() {
 | 
					class SourcesActivity : AppCompatActivity() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private lateinit var appColors: AppColors
 | 
					    private lateinit var appColors: AppColors
 | 
				
			||||||
 | 
					    private lateinit var binding: ActivitySourcesBinding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
        appColors = AppColors(this@SourcesActivity)
 | 
					        appColors = AppColors(this@SourcesActivity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super.onCreate(savedInstanceState)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        setContentView(R.layout.activity_sources)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        val scoop = Scoop.getInstance()
 | 
					        val scoop = Scoop.getInstance()
 | 
				
			||||||
        scoop.bind(this, Toppings.PRIMARY.value, toolbar)
 | 
					        scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
 | 
				
			||||||
        if  (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 | 
					        if  (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 | 
				
			||||||
            scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
 | 
					            scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setSupportActionBar(toolbar)
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
 | 
					        binding = ActivitySourcesBinding.inflate(layoutInflater)
 | 
				
			||||||
 | 
					        val view = binding.root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setContentView(view)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val scoop = Scoop.getInstance()
 | 
				
			||||||
 | 
					        scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
 | 
				
			||||||
 | 
					        scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setSupportActionBar(binding.toolbar)
 | 
				
			||||||
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
					        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
				
			||||||
        supportActionBar?.setDisplayShowHomeEnabled(true)
 | 
					        supportActionBar?.setDisplayShowHomeEnabled(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fab.rippleColor = appColors.colorAccentDark
 | 
					        binding.fab.rippleColor = appColors.colorAccentDark
 | 
				
			||||||
        fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
 | 
					        binding.fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onStop() {
 | 
					    override fun onStop() {
 | 
				
			||||||
        super.onStop()
 | 
					        super.onStop()
 | 
				
			||||||
        recyclerView.clearOnScrollListeners()
 | 
					        binding.recyclerView.clearOnScrollListeners()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onResume() {
 | 
					    override fun onResume() {
 | 
				
			||||||
        super.onResume()
 | 
					        super.onResume()
 | 
				
			||||||
        val mLayoutManager = LinearLayoutManager(this)
 | 
					        val mLayoutManager = LinearLayoutManager(this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val settings =
 | 
				
			||||||
 | 
					            getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
				
			||||||
        val prefs = PreferenceManager.getDefaultSharedPreferences(this)
 | 
					        val prefs = PreferenceManager.getDefaultSharedPreferences(this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val api = SelfossApi(
 | 
					        val api = SelfossApi(
 | 
				
			||||||
            this,
 | 
					            this,
 | 
				
			||||||
            this@SourcesActivity,
 | 
					            this@SourcesActivity,
 | 
				
			||||||
            prefs.getBoolean("isSelfSignedCert", false),
 | 
					            settings.getBoolean("isSelfSignedCert", false),
 | 
				
			||||||
            prefs.getBoolean("should_log_everything", false)
 | 
					            prefs.getString("api_timeout", "-1")!!.toLong()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        var items: ArrayList<Source> = ArrayList()
 | 
					        var items: ArrayList<Source> = ArrayList()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        recyclerView.setHasFixedSize(true)
 | 
					        binding.recyclerView.setHasFixedSize(true)
 | 
				
			||||||
        recyclerView.layoutManager = mLayoutManager
 | 
					        binding.recyclerView.layoutManager = mLayoutManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        api.sources.enqueue(object : Callback<List<Source>> {
 | 
					        if (this@SourcesActivity.isNetworkAccessible(binding.recyclerView)) {
 | 
				
			||||||
            override fun onResponse(
 | 
					            api.sources.enqueue(object : Callback<List<Source>> {
 | 
				
			||||||
                call: Call<List<Source>>,
 | 
					                override fun onResponse(
 | 
				
			||||||
                response: Response<List<Source>>
 | 
					                    call: Call<List<Source>>,
 | 
				
			||||||
            ) {
 | 
					                    response: Response<List<Source>>
 | 
				
			||||||
                if (response.body() != null && response.body()!!.isNotEmpty()) {
 | 
					                ) {
 | 
				
			||||||
                    items = response.body() as ArrayList<Source>
 | 
					                    if (response.body() != null && response.body()!!.isNotEmpty()) {
 | 
				
			||||||
 | 
					                        items = response.body() as ArrayList<Source>
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
 | 
				
			||||||
 | 
					                    binding.recyclerView.adapter = mAdapter
 | 
				
			||||||
 | 
					                    mAdapter.notifyDataSetChanged()
 | 
				
			||||||
 | 
					                    if (items.isEmpty()) {
 | 
				
			||||||
 | 
					                        Toast.makeText(
 | 
				
			||||||
 | 
					                            this@SourcesActivity,
 | 
				
			||||||
 | 
					                            R.string.nothing_here,
 | 
				
			||||||
 | 
					                            Toast.LENGTH_SHORT
 | 
				
			||||||
 | 
					                        ).show()
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
 | 
					
 | 
				
			||||||
                recyclerView.adapter = mAdapter
 | 
					                override fun onFailure(call: Call<List<Source>>, t: Throwable) {
 | 
				
			||||||
                mAdapter.notifyDataSetChanged()
 | 
					 | 
				
			||||||
                if (items.isEmpty()) {
 | 
					 | 
				
			||||||
                    Toast.makeText(
 | 
					                    Toast.makeText(
 | 
				
			||||||
                        this@SourcesActivity,
 | 
					                        this@SourcesActivity,
 | 
				
			||||||
                        R.string.nothing_here,
 | 
					                        R.string.cant_get_sources,
 | 
				
			||||||
                        Toast.LENGTH_SHORT
 | 
					                        Toast.LENGTH_SHORT
 | 
				
			||||||
                    ).show()
 | 
					                    ).show()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            override fun onFailure(call: Call<List<Source>>, t: Throwable) {
 | 
					        binding.fab.setOnClickListener {
 | 
				
			||||||
                Toast.makeText(
 | 
					 | 
				
			||||||
                    this@SourcesActivity,
 | 
					 | 
				
			||||||
                    R.string.cant_get_sources,
 | 
					 | 
				
			||||||
                    Toast.LENGTH_SHORT
 | 
					 | 
				
			||||||
                ).show()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fab.setOnClickListener {
 | 
					 | 
				
			||||||
            startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
 | 
					            startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,23 +2,26 @@ package apps.amine.bou.readerforselfoss.adapters
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import androidx.cardview.widget.CardView
 | 
					 | 
				
			||||||
import androidx.recyclerview.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import android.text.Html
 | 
					 | 
				
			||||||
import android.view.LayoutInflater
 | 
					import android.view.LayoutInflater
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
import android.view.ViewGroup
 | 
					import android.view.ViewGroup
 | 
				
			||||||
import android.widget.ImageView.ScaleType
 | 
					import android.widget.ImageView.ScaleType
 | 
				
			||||||
import android.widget.Toast
 | 
					import androidx.core.content.ContextCompat
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
					import apps.amine.bou.readerforselfoss.databinding.CardItemBinding
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
					import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.LinkOnTouchListener
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.SharedItems
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
 | 
					import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
 | 
					import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
 | 
					import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
 | 
					import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAvailable
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.openInBrowserAsNewTask
 | 
					import apps.amine.bou.readerforselfoss.utils.openInBrowserAsNewTask
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
 | 
					import apps.amine.bou.readerforselfoss.utils.openItemUrl
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.shareLink
 | 
					import apps.amine.bou.readerforselfoss.utils.shareLink
 | 
				
			||||||
@@ -27,24 +30,19 @@ import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
 | 
				
			|||||||
import com.amulyakhare.textdrawable.TextDrawable
 | 
					import com.amulyakhare.textdrawable.TextDrawable
 | 
				
			||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
					import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
				
			||||||
import com.bumptech.glide.Glide
 | 
					import com.bumptech.glide.Glide
 | 
				
			||||||
import com.like.LikeButton
 | 
					 | 
				
			||||||
import com.like.OnLikeListener
 | 
					 | 
				
			||||||
import kotlinx.android.synthetic.main.card_item.view.*
 | 
					 | 
				
			||||||
import retrofit2.Call
 | 
					 | 
				
			||||||
import retrofit2.Callback
 | 
					 | 
				
			||||||
import retrofit2.Response
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ItemCardAdapter(
 | 
					class ItemCardAdapter(
 | 
				
			||||||
    override val app: Activity,
 | 
					    override val app: Activity,
 | 
				
			||||||
    override var items: ArrayList<Item>,
 | 
					    override var items: ArrayList<Item>,
 | 
				
			||||||
    override val api: SelfossApi,
 | 
					    override val api: SelfossApi,
 | 
				
			||||||
 | 
					    override val db: AppDatabase,
 | 
				
			||||||
    private val helper: CustomTabActivityHelper,
 | 
					    private val helper: CustomTabActivityHelper,
 | 
				
			||||||
    private val internalBrowser: Boolean,
 | 
					    private val internalBrowser: Boolean,
 | 
				
			||||||
    private val articleViewer: Boolean,
 | 
					    private val articleViewer: Boolean,
 | 
				
			||||||
    private val fullHeightCards: Boolean,
 | 
					    private val fullHeightCards: Boolean,
 | 
				
			||||||
    override val appColors: AppColors,
 | 
					    override val appColors: AppColors,
 | 
				
			||||||
    override val debugReadingItems: Boolean,
 | 
					 | 
				
			||||||
    override val userIdentifier: String,
 | 
					    override val userIdentifier: String,
 | 
				
			||||||
 | 
					    override val config: Config,
 | 
				
			||||||
    override val updateItems: (ArrayList<Item>) -> Unit
 | 
					    override val updateItems: (ArrayList<Item>) -> Unit
 | 
				
			||||||
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
 | 
					) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
 | 
				
			||||||
    private val c: Context = app.baseContext
 | 
					    private val c: Context = app.baseContext
 | 
				
			||||||
@@ -53,130 +51,108 @@ class ItemCardAdapter(
 | 
				
			|||||||
        c.resources.getDimension(R.dimen.card_image_max_height).toInt()
 | 
					        c.resources.getDimension(R.dimen.card_image_max_height).toInt()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
 | 
					    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
 | 
				
			||||||
        val v = LayoutInflater.from(c).inflate(R.layout.card_item, parent, false) as CardView
 | 
					        val binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
 | 
				
			||||||
        return ViewHolder(v)
 | 
					        return ViewHolder(binding)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
 | 
					    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
 | 
				
			||||||
        val itm = items[position]
 | 
					        with(holder) {
 | 
				
			||||||
 | 
					            val itm = items[position]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            binding.favButton.isSelected = itm.starred
 | 
				
			||||||
 | 
					            binding.title.text = itm.getTitleDecoded()
 | 
				
			||||||
 | 
					            binding.title.setTextColor(ContextCompat.getColor(
 | 
				
			||||||
 | 
					                    c,
 | 
				
			||||||
 | 
					                    appColors.textColor
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        holder.mView.favButton.isLiked = itm.starred
 | 
					            binding.title.setOnTouchListener(LinkOnTouchListener())
 | 
				
			||||||
        holder.mView.title.text = Html.fromHtml(itm.title)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        holder.mView.title.setLinkTextColor(appColors.colorAccent)
 | 
					            binding.title.setLinkTextColor(appColors.colorAccent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
 | 
					            binding.sourceTitleAndDate.text = itm.sourceAndDateText()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!fullHeightCards) {
 | 
					            binding.sourceTitleAndDate.setTextColor(ContextCompat.getColor(
 | 
				
			||||||
            holder.mView.itemImage.maxHeight = imageMaxHeight
 | 
					                    c,
 | 
				
			||||||
            holder.mView.itemImage.scaleType = ScaleType.CENTER_CROP
 | 
					                    appColors.textColor
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!fullHeightCards) {
 | 
				
			||||||
 | 
					                binding.itemImage.maxHeight = imageMaxHeight
 | 
				
			||||||
 | 
					                binding.itemImage.scaleType = ScaleType.CENTER_CROP
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (itm.getThumbnail(c).isEmpty()) {
 | 
				
			||||||
 | 
					                binding.itemImage.visibility = View.GONE
 | 
				
			||||||
 | 
					                Glide.with(c).clear(binding.itemImage)
 | 
				
			||||||
 | 
					                binding.itemImage.setImageDrawable(null)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                binding.itemImage.visibility = View.VISIBLE
 | 
				
			||||||
 | 
					                c.bitmapCenterCrop(config, itm.getThumbnail(c), binding.itemImage)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (itm.getIcon(c).isEmpty()) {
 | 
				
			||||||
 | 
					                val color = generator.getColor(itm.getSourceTitle())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                val drawable =
 | 
				
			||||||
 | 
					                        TextDrawable
 | 
				
			||||||
 | 
					                                .builder()
 | 
				
			||||||
 | 
					                                .round()
 | 
				
			||||||
 | 
					                                .build(itm.getSourceTitle().toTextDrawableString(c), color)
 | 
				
			||||||
 | 
					                binding.sourceImage.setImageDrawable(drawable)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                c.circularBitmapDrawable(config, itm.getIcon(c), binding.sourceImage)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (itm.getThumbnail(c).isEmpty()) {
 | 
					 | 
				
			||||||
            holder.mView.itemImage.visibility = View.GONE
 | 
					 | 
				
			||||||
            Glide.with(c).clear(holder.mView.itemImage)
 | 
					 | 
				
			||||||
            holder.mView.itemImage.setImageDrawable(null)
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            holder.mView.itemImage.visibility = View.VISIBLE
 | 
					 | 
				
			||||||
            c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (itm.getIcon(c).isEmpty()) {
 | 
					 | 
				
			||||||
            val color = generator.getColor(itm.sourcetitle)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            val drawable =
 | 
					 | 
				
			||||||
                TextDrawable
 | 
					 | 
				
			||||||
                    .builder()
 | 
					 | 
				
			||||||
                    .round()
 | 
					 | 
				
			||||||
                    .build(itm.sourcetitle.toTextDrawableString(c), color)
 | 
					 | 
				
			||||||
            holder.mView.sourceImage.setImageDrawable(drawable)
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            c.circularBitmapDrawable(itm.getIcon(c), holder.mView.sourceImage)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        holder.mView.favButton.isLiked = itm.starred
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun getItemCount(): Int {
 | 
					    override fun getItemCount(): Int {
 | 
				
			||||||
        return items.size
 | 
					        return items.size
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    inner class ViewHolder(val mView: CardView) : RecyclerView.ViewHolder(mView) {
 | 
					    inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root) {
 | 
				
			||||||
        init {
 | 
					        init {
 | 
				
			||||||
            mView.setCardBackgroundColor(appColors.cardBackgroundColor)
 | 
					            binding.root.setCardBackgroundColor(appColors.cardBackgroundColor)
 | 
				
			||||||
            handleClickListeners()
 | 
					            handleClickListeners()
 | 
				
			||||||
            handleCustomTabActions()
 | 
					            handleCustomTabActions()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private fun handleClickListeners() {
 | 
					        private fun handleClickListeners() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            mView.favButton.setOnLikeListener(object : OnLikeListener {
 | 
					            binding.favButton.setOnClickListener {
 | 
				
			||||||
                override fun liked(likeButton: LikeButton) {
 | 
					                val item = items[bindingAdapterPosition]
 | 
				
			||||||
                    val (id) = items[adapterPosition]
 | 
					                if (isNetworkAvailable(c)) {
 | 
				
			||||||
                    api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
 | 
					                    if (item.starred) {
 | 
				
			||||||
                        override fun onResponse(
 | 
					                        SharedItems.unstarItem(c, api, db, item)
 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					                        item.starred = false
 | 
				
			||||||
                            response: Response<SuccessResponse>
 | 
					                        binding.favButton.isSelected = false
 | 
				
			||||||
                        ) {
 | 
					                    } else {
 | 
				
			||||||
                        }
 | 
					                        SharedItems.starItem(c, api, db, item)
 | 
				
			||||||
 | 
					                        item.starred = true
 | 
				
			||||||
                        override fun onFailure(
 | 
					                        binding.favButton.isSelected = true
 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					                    }
 | 
				
			||||||
                            t: Throwable
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                            mView.favButton.isLiked = false
 | 
					 | 
				
			||||||
                            Toast.makeText(
 | 
					 | 
				
			||||||
                                c,
 | 
					 | 
				
			||||||
                                R.string.cant_mark_favortie,
 | 
					 | 
				
			||||||
                                Toast.LENGTH_SHORT
 | 
					 | 
				
			||||||
                            ).show()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                override fun unLiked(likeButton: LikeButton) {
 | 
					 | 
				
			||||||
                    val (id) = items[adapterPosition]
 | 
					 | 
				
			||||||
                    api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
 | 
					 | 
				
			||||||
                        override fun onResponse(
 | 
					 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                            response: Response<SuccessResponse>
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        override fun onFailure(
 | 
					 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                            t: Throwable
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                            mView.favButton.isLiked = true
 | 
					 | 
				
			||||||
                            Toast.makeText(
 | 
					 | 
				
			||||||
                                c,
 | 
					 | 
				
			||||||
                                R.string.cant_unmark_favortie,
 | 
					 | 
				
			||||||
                                Toast.LENGTH_SHORT
 | 
					 | 
				
			||||||
                            ).show()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mView.shareBtn.setOnClickListener {
 | 
					 | 
				
			||||||
                c.shareLink(items[adapterPosition].getLinkDecoded())
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            mView.browserBtn.setOnClickListener {
 | 
					                binding.shareBtn.setOnClickListener {
 | 
				
			||||||
                c.openInBrowserAsNewTask(items[adapterPosition])
 | 
					                    val item = items[bindingAdapterPosition]
 | 
				
			||||||
 | 
					                    c.shareLink(item.getLinkDecoded(), item.getTitleDecoded())
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                binding.browserBtn.setOnClickListener {
 | 
				
			||||||
 | 
					                    c.openInBrowserAsNewTask(items[bindingAdapterPosition])
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private fun handleCustomTabActions() {
 | 
					        private fun handleCustomTabActions() {
 | 
				
			||||||
            val customTabsIntent = c.buildCustomTabsIntent()
 | 
					            val customTabsIntent = c.buildCustomTabsIntent()
 | 
				
			||||||
            helper.bindCustomTabsService(app)
 | 
					            helper.bindCustomTabsService(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            mView.setOnClickListener {
 | 
					            binding.root.setOnClickListener {
 | 
				
			||||||
                c.openItemUrl(
 | 
					                c.openItemUrl(
 | 
				
			||||||
                    items,
 | 
					                    items,
 | 
				
			||||||
                    adapterPosition,
 | 
					                    bindingAdapterPosition,
 | 
				
			||||||
                    items[adapterPosition].getLinkDecoded(),
 | 
					                    items[bindingAdapterPosition].getLinkDecoded(),
 | 
				
			||||||
                    customTabsIntent,
 | 
					                    customTabsIntent,
 | 
				
			||||||
                    internalBrowser,
 | 
					                    internalBrowser,
 | 
				
			||||||
                    articleViewer,
 | 
					                    articleViewer,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,218 +2,115 @@ package apps.amine.bou.readerforselfoss.adapters
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import androidx.constraintlayout.widget.ConstraintLayout
 | 
					 | 
				
			||||||
import androidx.recyclerview.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import android.text.Html
 | 
					 | 
				
			||||||
import android.util.TypedValue
 | 
					 | 
				
			||||||
import android.view.LayoutInflater
 | 
					import android.view.LayoutInflater
 | 
				
			||||||
import android.view.View
 | 
					 | 
				
			||||||
import android.view.ViewGroup
 | 
					import android.view.ViewGroup
 | 
				
			||||||
import android.widget.Toast
 | 
					import androidx.core.content.ContextCompat
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
					import apps.amine.bou.readerforselfoss.databinding.ListItemBinding
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
					import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.LinkOnTouchListener
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
 | 
					import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
 | 
					import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
 | 
					import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
 | 
					import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.openInBrowserAsNewTask
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
 | 
					import apps.amine.bou.readerforselfoss.utils.openItemUrl
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.shareLink
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
 | 
					import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
 | 
					import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
 | 
				
			||||||
import com.amulyakhare.textdrawable.TextDrawable
 | 
					import com.amulyakhare.textdrawable.TextDrawable
 | 
				
			||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
					import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
				
			||||||
import com.like.LikeButton
 | 
					 | 
				
			||||||
import com.like.OnLikeListener
 | 
					 | 
				
			||||||
import kotlinx.android.synthetic.main.list_item.view.*
 | 
					 | 
				
			||||||
import retrofit2.Call
 | 
					 | 
				
			||||||
import retrofit2.Callback
 | 
					 | 
				
			||||||
import retrofit2.Response
 | 
					 | 
				
			||||||
import java.util.*
 | 
					 | 
				
			||||||
import kotlin.collections.ArrayList
 | 
					import kotlin.collections.ArrayList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ItemListAdapter(
 | 
					class ItemListAdapter(
 | 
				
			||||||
    override val app: Activity,
 | 
					    override val app: Activity,
 | 
				
			||||||
    override var items: ArrayList<Item>,
 | 
					    override var items: ArrayList<Item>,
 | 
				
			||||||
    override val api: SelfossApi,
 | 
					    override val api: SelfossApi,
 | 
				
			||||||
 | 
					    override val db: AppDatabase,
 | 
				
			||||||
    private val helper: CustomTabActivityHelper,
 | 
					    private val helper: CustomTabActivityHelper,
 | 
				
			||||||
    private val internalBrowser: Boolean,
 | 
					    private val internalBrowser: Boolean,
 | 
				
			||||||
    private val articleViewer: Boolean,
 | 
					    private val articleViewer: Boolean,
 | 
				
			||||||
    override val debugReadingItems: Boolean,
 | 
					 | 
				
			||||||
    override val userIdentifier: String,
 | 
					    override val userIdentifier: String,
 | 
				
			||||||
    override val appColors: AppColors,
 | 
					    override val appColors: AppColors,
 | 
				
			||||||
 | 
					    override val config: Config,
 | 
				
			||||||
    override val updateItems: (ArrayList<Item>) -> Unit
 | 
					    override val updateItems: (ArrayList<Item>) -> Unit
 | 
				
			||||||
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
 | 
					) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
 | 
				
			||||||
    private val generator: ColorGenerator = ColorGenerator.MATERIAL
 | 
					    private val generator: ColorGenerator = ColorGenerator.MATERIAL
 | 
				
			||||||
    private val c: Context = app.baseContext
 | 
					    private val c: Context = app.baseContext
 | 
				
			||||||
    private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
 | 
					    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
 | 
				
			||||||
        val v = LayoutInflater.from(c).inflate(
 | 
					        val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
 | 
				
			||||||
            R.layout.list_item,
 | 
					        return ViewHolder(binding)
 | 
				
			||||||
            parent,
 | 
					 | 
				
			||||||
            false
 | 
					 | 
				
			||||||
        ) as ConstraintLayout
 | 
					 | 
				
			||||||
        return ViewHolder(v)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
 | 
					    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
 | 
				
			||||||
        val itm = items[position]
 | 
					        with(holder) {
 | 
				
			||||||
 | 
					            val itm = items[position]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        holder.mView.title.text = Html.fromHtml(itm.title)
 | 
					            binding.title.text = itm.getTitleDecoded()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        holder.mView.title.setLinkTextColor(appColors.colorAccent)
 | 
					            binding.title.setTextColor(ContextCompat.getColor(
 | 
				
			||||||
 | 
					                    c,
 | 
				
			||||||
 | 
					                    appColors.textColor
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
 | 
					            binding.title.setOnTouchListener(LinkOnTouchListener())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (itm.getThumbnail(c).isEmpty()) {
 | 
					            binding.title.setLinkTextColor(appColors.colorAccent)
 | 
				
			||||||
            val sizeInInt = 46
 | 
					 | 
				
			||||||
            val sizeInDp = TypedValue.applyDimension(
 | 
					 | 
				
			||||||
                TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
 | 
					 | 
				
			||||||
                    .displayMetrics
 | 
					 | 
				
			||||||
            ).toInt()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val marginInInt = 16
 | 
					            binding.sourceTitleAndDate.text = itm.sourceAndDateText()
 | 
				
			||||||
            val marginInDp = TypedValue.applyDimension(
 | 
					 | 
				
			||||||
                TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources
 | 
					 | 
				
			||||||
                    .displayMetrics
 | 
					 | 
				
			||||||
            ).toInt()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val params = holder.mView.itemImage.layoutParams as ViewGroup.MarginLayoutParams
 | 
					            binding.sourceTitleAndDate.setTextColor(ContextCompat.getColor(
 | 
				
			||||||
            params.height = sizeInDp
 | 
					                    c,
 | 
				
			||||||
            params.width = sizeInDp
 | 
					                    appColors.textColor
 | 
				
			||||||
            params.setMargins(marginInDp, 0, 0, 0)
 | 
					            ))
 | 
				
			||||||
            holder.mView.itemImage.layoutParams = params
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (itm.getIcon(c).isEmpty()) {
 | 
					            if (itm.getThumbnail(c).isEmpty()) {
 | 
				
			||||||
                val color = generator.getColor(itm.sourcetitle)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                val drawable =
 | 
					                if (itm.getIcon(c).isEmpty()) {
 | 
				
			||||||
                    TextDrawable
 | 
					                    val color = generator.getColor(itm.getSourceTitle())
 | 
				
			||||||
                        .builder()
 | 
					 | 
				
			||||||
                        .round()
 | 
					 | 
				
			||||||
                        .build(itm.sourcetitle.toTextDrawableString(c), color)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                holder.mView.itemImage.setImageDrawable(drawable)
 | 
					                    val drawable =
 | 
				
			||||||
 | 
					                            TextDrawable
 | 
				
			||||||
 | 
					                                    .builder()
 | 
				
			||||||
 | 
					                                    .round()
 | 
				
			||||||
 | 
					                                    .build(itm.getSourceTitle().toTextDrawableString(c), color)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    binding.itemImage.setImageDrawable(drawable)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    c.circularBitmapDrawable(config, itm.getIcon(c), binding.itemImage)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
 | 
					                c.bitmapCenterCrop(config, itm.getThumbnail(c), binding.itemImage)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // TODO: maybe handle this differently. It crashes when changing tab
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            if (bars[position]) {
 | 
					 | 
				
			||||||
                holder.mView.actionBar.visibility = View.VISIBLE
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                holder.mView.actionBar.visibility = View.GONE
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } catch (e: IndexOutOfBoundsException) {
 | 
					 | 
				
			||||||
            holder.mView.actionBar.visibility = View.GONE
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        holder.mView.favButton.isLiked = itm.starred
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun getItemCount(): Int = items.size
 | 
					    override fun getItemCount(): Int = items.size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
 | 
					    inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        init {
 | 
					        init {
 | 
				
			||||||
            handleClickListeners()
 | 
					 | 
				
			||||||
            handleCustomTabActions()
 | 
					            handleCustomTabActions()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private fun handleClickListeners() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mView.favButton.setOnLikeListener(object : OnLikeListener {
 | 
					 | 
				
			||||||
                override fun liked(likeButton: LikeButton) {
 | 
					 | 
				
			||||||
                    val (id) = items[adapterPosition]
 | 
					 | 
				
			||||||
                    api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
 | 
					 | 
				
			||||||
                        override fun onResponse(
 | 
					 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                            response: Response<SuccessResponse>
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        override fun onFailure(
 | 
					 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                            t: Throwable
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                            mView.favButton.isLiked = false
 | 
					 | 
				
			||||||
                            Toast.makeText(
 | 
					 | 
				
			||||||
                                c,
 | 
					 | 
				
			||||||
                                R.string.cant_mark_favortie,
 | 
					 | 
				
			||||||
                                Toast.LENGTH_SHORT
 | 
					 | 
				
			||||||
                            ).show()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                override fun unLiked(likeButton: LikeButton) {
 | 
					 | 
				
			||||||
                    val (id) = items[adapterPosition]
 | 
					 | 
				
			||||||
                    api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
 | 
					 | 
				
			||||||
                        override fun onResponse(
 | 
					 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                            response: Response<SuccessResponse>
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        override fun onFailure(
 | 
					 | 
				
			||||||
                            call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                            t: Throwable
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                            mView.favButton.isLiked = true
 | 
					 | 
				
			||||||
                            Toast.makeText(
 | 
					 | 
				
			||||||
                                c,
 | 
					 | 
				
			||||||
                                R.string.cant_unmark_favortie,
 | 
					 | 
				
			||||||
                                Toast.LENGTH_SHORT
 | 
					 | 
				
			||||||
                            ).show()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mView.shareBtn.setOnClickListener {
 | 
					 | 
				
			||||||
                c.shareLink(items[adapterPosition].getLinkDecoded())
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mView.browserBtn.setOnClickListener {
 | 
					 | 
				
			||||||
                c.openInBrowserAsNewTask(items[adapterPosition])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private fun handleCustomTabActions() {
 | 
					        private fun handleCustomTabActions() {
 | 
				
			||||||
            val customTabsIntent = c.buildCustomTabsIntent()
 | 
					            val customTabsIntent = c.buildCustomTabsIntent()
 | 
				
			||||||
            helper.bindCustomTabsService(app)
 | 
					            helper.bindCustomTabsService(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            mView.setOnClickListener { actionBarShowHide() }
 | 
					            binding.root.setOnClickListener {
 | 
				
			||||||
            mView.setOnLongClickListener {
 | 
					 | 
				
			||||||
                c.openItemUrl(
 | 
					                c.openItemUrl(
 | 
				
			||||||
                    items,
 | 
					                    items,
 | 
				
			||||||
                    adapterPosition,
 | 
					                    bindingAdapterPosition,
 | 
				
			||||||
                    items[adapterPosition].getLinkDecoded(),
 | 
					                    items[bindingAdapterPosition].getLinkDecoded(),
 | 
				
			||||||
                    customTabsIntent,
 | 
					                    customTabsIntent,
 | 
				
			||||||
                    internalBrowser,
 | 
					                    internalBrowser,
 | 
				
			||||||
                    articleViewer,
 | 
					                    articleViewer,
 | 
				
			||||||
                    app
 | 
					                    app
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                true
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private fun actionBarShowHide() {
 | 
					 | 
				
			||||||
            bars[adapterPosition] = true
 | 
					 | 
				
			||||||
            if (mView.actionBar.visibility == View.GONE) {
 | 
					 | 
				
			||||||
                mView.actionBar.visibility = View.VISIBLE
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                mView.actionBar.visibility = View.GONE
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,38 +2,34 @@ package apps.amine.bou.readerforselfoss.adapters
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.graphics.Color
 | 
					import android.graphics.Color
 | 
				
			||||||
import com.google.android.material.snackbar.Snackbar
 | 
					 | 
				
			||||||
import androidx.recyclerview.widget.RecyclerView
 | 
					 | 
				
			||||||
import android.widget.TextView
 | 
					import android.widget.TextView
 | 
				
			||||||
import android.widget.Toast
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
					import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.succeeded
 | 
					import apps.amine.bou.readerforselfoss.utils.SharedItems
 | 
				
			||||||
import org.acra.ACRA
 | 
					import com.google.android.material.snackbar.Snackbar
 | 
				
			||||||
import retrofit2.Call
 | 
					 | 
				
			||||||
import retrofit2.Callback
 | 
					 | 
				
			||||||
import retrofit2.Response
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>() {
 | 
					abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>() {
 | 
				
			||||||
    abstract var items: ArrayList<Item>
 | 
					    abstract var items: ArrayList<Item>
 | 
				
			||||||
    abstract val api: SelfossApi
 | 
					    abstract val api: SelfossApi
 | 
				
			||||||
    abstract val debugReadingItems: Boolean
 | 
					    abstract val db: AppDatabase
 | 
				
			||||||
    abstract val userIdentifier: String
 | 
					    abstract val userIdentifier: String
 | 
				
			||||||
    abstract val app: Activity
 | 
					    abstract val app: Activity
 | 
				
			||||||
    abstract val appColors: AppColors
 | 
					    abstract val appColors: AppColors
 | 
				
			||||||
 | 
					    abstract val config: Config
 | 
				
			||||||
    abstract val updateItems: (ArrayList<Item>) -> Unit
 | 
					    abstract val updateItems: (ArrayList<Item>) -> Unit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun updateAllItems(newItems: ArrayList<Item>) {
 | 
					    fun updateAllItems() {
 | 
				
			||||||
        items = newItems
 | 
					        items = SharedItems.focusedItems
 | 
				
			||||||
        notifyDataSetChanged()
 | 
					        notifyDataSetChanged()
 | 
				
			||||||
        updateItems(items)
 | 
					        updateItems(items)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun doUnmark(i: Item, position: Int) {
 | 
					    private fun unmarkSnackbar(i: Item, position: Int) {
 | 
				
			||||||
        val s = Snackbar
 | 
					        val s = Snackbar
 | 
				
			||||||
            .make(
 | 
					            .make(
 | 
				
			||||||
                app.findViewById(R.id.coordLayout),
 | 
					                app.findViewById(R.id.coordLayout),
 | 
				
			||||||
@@ -41,24 +37,12 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
 | 
				
			|||||||
                Snackbar.LENGTH_LONG
 | 
					                Snackbar.LENGTH_LONG
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .setAction(R.string.undo_string) {
 | 
					            .setAction(R.string.undo_string) {
 | 
				
			||||||
                items.add(position, i)
 | 
					                SharedItems.unreadItem(app, api, db, i)
 | 
				
			||||||
                notifyItemInserted(position)
 | 
					                if (SharedItems.displayedItems == "unread") {
 | 
				
			||||||
                updateItems(items)
 | 
					                    addItemAtIndex(i, position)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
                api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> {
 | 
					                    notifyItemChanged(position)
 | 
				
			||||||
                    override fun onResponse(
 | 
					                }
 | 
				
			||||||
                        call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                        response: Response<SuccessResponse>
 | 
					 | 
				
			||||||
                    ) {
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
					 | 
				
			||||||
                        items.remove(i)
 | 
					 | 
				
			||||||
                        notifyItemRemoved(position)
 | 
					 | 
				
			||||||
                        updateItems(items)
 | 
					 | 
				
			||||||
                        doUnmark(i, position)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val view = s.view
 | 
					        val view = s.view
 | 
				
			||||||
@@ -67,51 +51,55 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
 | 
				
			|||||||
        s.show()
 | 
					        s.show()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun removeItemAtIndex(position: Int) {
 | 
					    private fun markSnackbar(position: Int) {
 | 
				
			||||||
 | 
					        val s = Snackbar
 | 
				
			||||||
 | 
					            .make(
 | 
				
			||||||
 | 
					                app.findViewById(R.id.coordLayout),
 | 
				
			||||||
 | 
					                R.string.marked_as_unread,
 | 
				
			||||||
 | 
					                Snackbar.LENGTH_LONG
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .setAction(R.string.undo_string) {
 | 
				
			||||||
 | 
					                SharedItems.readItem(app, api, db, items[position])
 | 
				
			||||||
 | 
					                items = SharedItems.focusedItems
 | 
				
			||||||
 | 
					                if (SharedItems.displayedItems == "unread") {
 | 
				
			||||||
 | 
					                    notifyItemRemoved(position)
 | 
				
			||||||
 | 
					                    updateItems(items)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    notifyItemChanged(position)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val view = s.view
 | 
				
			||||||
 | 
					        val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
 | 
				
			||||||
 | 
					        tv.setTextColor(Color.WHITE)
 | 
				
			||||||
 | 
					        s.show()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun handleItemAtIndex(position: Int) {
 | 
				
			||||||
 | 
					        if (SharedItems.unreadItemStatusAtIndex(position)) {
 | 
				
			||||||
 | 
					            readItemAtIndex(position)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            unreadItemAtIndex(position)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun readItemAtIndex(position: Int) {
 | 
				
			||||||
        val i = items[position]
 | 
					        val i = items[position]
 | 
				
			||||||
 | 
					        SharedItems.readItem(app, api, db, i)
 | 
				
			||||||
 | 
					        if (SharedItems.displayedItems == "unread") {
 | 
				
			||||||
 | 
					            items.remove(i)
 | 
				
			||||||
 | 
					            notifyItemRemoved(position)
 | 
				
			||||||
 | 
					            updateItems(items)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            notifyItemChanged(position)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        unmarkSnackbar(i, position)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        items.remove(i)
 | 
					    private fun unreadItemAtIndex(position: Int) {
 | 
				
			||||||
        notifyItemRemoved(position)
 | 
					        SharedItems.unreadItem(app, api, db, items[position])
 | 
				
			||||||
        updateItems(items)
 | 
					        notifyItemChanged(position)
 | 
				
			||||||
 | 
					        markSnackbar(position)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
 | 
					 | 
				
			||||||
            override fun onResponse(
 | 
					 | 
				
			||||||
                call: Call<SuccessResponse>,
 | 
					 | 
				
			||||||
                response: Response<SuccessResponse>
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
                if (!response.succeeded() && debugReadingItems) {
 | 
					 | 
				
			||||||
                    val message =
 | 
					 | 
				
			||||||
                        "message: ${response.message()} " +
 | 
					 | 
				
			||||||
                                "response isSuccess: ${response.isSuccessful} " +
 | 
					 | 
				
			||||||
                                "response code: ${response.code()} " +
 | 
					 | 
				
			||||||
                                "response message: ${response.message()} " +
 | 
					 | 
				
			||||||
                                "response errorBody: ${response.errorBody()?.string()} " +
 | 
					 | 
				
			||||||
                                "body success: ${response.body()?.success} " +
 | 
					 | 
				
			||||||
                                "body isSuccess: ${response.body()?.isSuccess}"
 | 
					 | 
				
			||||||
                    ACRA.getErrorReporter().maybeHandleSilentException(Exception(message), app)
 | 
					 | 
				
			||||||
                    Toast.makeText(app.baseContext, message, Toast.LENGTH_LONG).show()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                doUnmark(i, position)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
					 | 
				
			||||||
                if (debugReadingItems) {
 | 
					 | 
				
			||||||
                    ACRA.getErrorReporter().maybeHandleSilentException(t, app)
 | 
					 | 
				
			||||||
                    Toast.makeText(app.baseContext, t.message, Toast.LENGTH_LONG).show()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                Toast.makeText(
 | 
					 | 
				
			||||||
                    app,
 | 
					 | 
				
			||||||
                    app.getString(R.string.cant_mark_read),
 | 
					 | 
				
			||||||
                    Toast.LENGTH_SHORT
 | 
					 | 
				
			||||||
                ).show()
 | 
					 | 
				
			||||||
                items.add(i)
 | 
					 | 
				
			||||||
                notifyItemInserted(position)
 | 
					 | 
				
			||||||
                updateItems(items)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun addItemAtIndex(item: Item, position: Int) {
 | 
					    fun addItemAtIndex(item: Item, position: Int) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,11 +12,13 @@ import apps.amine.bou.readerforselfoss.R
 | 
				
			|||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Source
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Source
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.databinding.SourceListItemBinding
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
 | 
					import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
 | 
					import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
 | 
				
			||||||
import com.amulyakhare.textdrawable.TextDrawable
 | 
					import com.amulyakhare.textdrawable.TextDrawable
 | 
				
			||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
					import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
				
			||||||
import kotlinx.android.synthetic.main.source_list_item.view.*
 | 
					 | 
				
			||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
import retrofit2.Callback
 | 
					import retrofit2.Callback
 | 
				
			||||||
import retrofit2.Response
 | 
					import retrofit2.Response
 | 
				
			||||||
@@ -28,33 +30,32 @@ class SourcesListAdapter(
 | 
				
			|||||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
 | 
					) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
 | 
				
			||||||
    private val c: Context = app.baseContext
 | 
					    private val c: Context = app.baseContext
 | 
				
			||||||
    private val generator: ColorGenerator = ColorGenerator.MATERIAL
 | 
					    private val generator: ColorGenerator = ColorGenerator.MATERIAL
 | 
				
			||||||
 | 
					    private lateinit var config: Config
 | 
				
			||||||
 | 
					    private lateinit var binding: SourceListItemBinding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
 | 
					    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
 | 
				
			||||||
        val v = LayoutInflater.from(c).inflate(
 | 
					        binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
 | 
				
			||||||
            R.layout.source_list_item,
 | 
					        return ViewHolder(binding.root)
 | 
				
			||||||
            parent,
 | 
					 | 
				
			||||||
            false
 | 
					 | 
				
			||||||
        ) as ConstraintLayout
 | 
					 | 
				
			||||||
        return ViewHolder(v)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
 | 
					    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
 | 
				
			||||||
        val itm = items[position]
 | 
					        val itm = items[position]
 | 
				
			||||||
 | 
					        config = Config(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (itm.getIcon(c).isEmpty()) {
 | 
					        if (itm.getIcon(c).isEmpty()) {
 | 
				
			||||||
            val color = generator.getColor(itm.title)
 | 
					            val color = generator.getColor(itm.getTitleDecoded())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val drawable =
 | 
					            val drawable =
 | 
				
			||||||
                TextDrawable
 | 
					                TextDrawable
 | 
				
			||||||
                    .builder()
 | 
					                    .builder()
 | 
				
			||||||
                    .round()
 | 
					                    .round()
 | 
				
			||||||
                    .build(itm.title.toTextDrawableString(c), color)
 | 
					                    .build(itm.getTitleDecoded().toTextDrawableString(c), color)
 | 
				
			||||||
            holder.mView.itemImage.setImageDrawable(drawable)
 | 
					            binding.itemImage.setImageDrawable(drawable)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
 | 
					            c.circularBitmapDrawable(config, itm.getIcon(c), binding.itemImage)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        holder.mView.sourceTitle.text = itm.title
 | 
					        binding.sourceTitle.text = itm.getTitleDecoded()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun getItemCount(): Int = items.size
 | 
					    override fun getItemCount(): Int = items.size
 | 
				
			||||||
@@ -70,33 +71,35 @@ class SourcesListAdapter(
 | 
				
			|||||||
            val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
 | 
					            val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            deleteBtn.setOnClickListener {
 | 
					            deleteBtn.setOnClickListener {
 | 
				
			||||||
                val (id) = items[adapterPosition]
 | 
					                if (c.isNetworkAccessible(null)) {
 | 
				
			||||||
                api.deleteSource(id).enqueue(object : Callback<SuccessResponse> {
 | 
					                    val (id) = items[adapterPosition]
 | 
				
			||||||
                    override fun onResponse(
 | 
					                    api.deleteSource(id).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
                        call: Call<SuccessResponse>,
 | 
					                        override fun onResponse(
 | 
				
			||||||
                        response: Response<SuccessResponse>
 | 
					                            call: Call<SuccessResponse>,
 | 
				
			||||||
                    ) {
 | 
					                            response: Response<SuccessResponse>
 | 
				
			||||||
                        if (response.body() != null && response.body()!!.isSuccess) {
 | 
					                        ) {
 | 
				
			||||||
                            items.removeAt(adapterPosition)
 | 
					                            if (response.body() != null && response.body()!!.isSuccess) {
 | 
				
			||||||
                            notifyItemRemoved(adapterPosition)
 | 
					                                items.removeAt(adapterPosition)
 | 
				
			||||||
                            notifyItemRangeChanged(adapterPosition, itemCount)
 | 
					                                notifyItemRemoved(adapterPosition)
 | 
				
			||||||
                        } else {
 | 
					                                notifyItemRangeChanged(adapterPosition, itemCount)
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                Toast.makeText(
 | 
				
			||||||
 | 
					                                    app,
 | 
				
			||||||
 | 
					                                    R.string.can_delete_source,
 | 
				
			||||||
 | 
					                                    Toast.LENGTH_SHORT
 | 
				
			||||||
 | 
					                                ).show()
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
				
			||||||
                            Toast.makeText(
 | 
					                            Toast.makeText(
 | 
				
			||||||
                                app,
 | 
					                                app,
 | 
				
			||||||
                                R.string.can_delete_source,
 | 
					                                R.string.can_delete_source,
 | 
				
			||||||
                                Toast.LENGTH_SHORT
 | 
					                                Toast.LENGTH_SHORT
 | 
				
			||||||
                            ).show()
 | 
					                            ).show()
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    })
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                    override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
					 | 
				
			||||||
                        Toast.makeText(
 | 
					 | 
				
			||||||
                            app,
 | 
					 | 
				
			||||||
                            R.string.can_delete_source,
 | 
					 | 
				
			||||||
                            Toast.LENGTH_SHORT
 | 
					 | 
				
			||||||
                        ).show()
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,17 +7,13 @@ import retrofit2.Call
 | 
				
			|||||||
import retrofit2.Retrofit
 | 
					import retrofit2.Retrofit
 | 
				
			||||||
import retrofit2.converter.gson.GsonConverterFactory
 | 
					import retrofit2.converter.gson.GsonConverterFactory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MercuryApi(shouldLog: Boolean) {
 | 
					class MercuryApi() {
 | 
				
			||||||
    private val service: MercuryService
 | 
					    private val service: MercuryService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    init {
 | 
					    init {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val interceptor = HttpLoggingInterceptor()
 | 
					        val interceptor = HttpLoggingInterceptor()
 | 
				
			||||||
        interceptor.level = if (shouldLog) {
 | 
					        interceptor.level = HttpLoggingInterceptor.Level.NONE
 | 
				
			||||||
            HttpLoggingInterceptor.Level.BODY
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            HttpLoggingInterceptor.Level.NONE
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
 | 
					        val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val gson = GsonBuilder()
 | 
					        val gson = GsonBuilder()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,17 +28,17 @@ class ParsedContent(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(source: Parcel) : this(
 | 
					    constructor(source: Parcel) : this(
 | 
				
			||||||
        title = source.readString(),
 | 
					        title = source.readString().orEmpty(),
 | 
				
			||||||
        content = source.readString(),
 | 
					        content = source.readString(),
 | 
				
			||||||
        date_published = source.readString(),
 | 
					        date_published = source.readString().orEmpty(),
 | 
				
			||||||
        lead_image_url = source.readString(),
 | 
					        lead_image_url = source.readString(),
 | 
				
			||||||
        dek = source.readString(),
 | 
					        dek = source.readString().orEmpty(),
 | 
				
			||||||
        url = source.readString(),
 | 
					        url = source.readString().orEmpty(),
 | 
				
			||||||
        domain = source.readString(),
 | 
					        domain = source.readString().orEmpty(),
 | 
				
			||||||
        excerpt = source.readString(),
 | 
					        excerpt = source.readString().orEmpty(),
 | 
				
			||||||
        total_pages = source.readInt(),
 | 
					        total_pages = source.readInt(),
 | 
				
			||||||
        rendered_pages = source.readInt(),
 | 
					        rendered_pages = source.readInt(),
 | 
				
			||||||
        next_page_url = source.readString()
 | 
					        next_page_url = source.readString().orEmpty()
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun describeContents() = 0
 | 
					    override fun describeContents() = 0
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ package apps.amine.bou.readerforselfoss.api.selfoss
 | 
				
			|||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.Config
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.SharedItems
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient
 | 
					import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient
 | 
				
			||||||
import com.burgstaller.okhttp.AuthenticationCacheInterceptor
 | 
					import com.burgstaller.okhttp.AuthenticationCacheInterceptor
 | 
				
			||||||
import com.burgstaller.okhttp.CachingAuthenticatorDecorator
 | 
					import com.burgstaller.okhttp.CachingAuthenticatorDecorator
 | 
				
			||||||
@@ -12,18 +13,22 @@ import com.burgstaller.okhttp.digest.CachingAuthenticator
 | 
				
			|||||||
import com.burgstaller.okhttp.digest.Credentials
 | 
					import com.burgstaller.okhttp.digest.Credentials
 | 
				
			||||||
import com.burgstaller.okhttp.digest.DigestAuthenticator
 | 
					import com.burgstaller.okhttp.digest.DigestAuthenticator
 | 
				
			||||||
import com.google.gson.GsonBuilder
 | 
					import com.google.gson.GsonBuilder
 | 
				
			||||||
import okhttp3.OkHttpClient
 | 
					import okhttp3.*
 | 
				
			||||||
 | 
					import okhttp3.MediaType.Companion.toMediaTypeOrNull
 | 
				
			||||||
 | 
					import okhttp3.ResponseBody.Companion.toResponseBody
 | 
				
			||||||
import okhttp3.logging.HttpLoggingInterceptor
 | 
					import okhttp3.logging.HttpLoggingInterceptor
 | 
				
			||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
import retrofit2.Retrofit
 | 
					import retrofit2.Retrofit
 | 
				
			||||||
import retrofit2.converter.gson.GsonConverterFactory
 | 
					import retrofit2.converter.gson.GsonConverterFactory
 | 
				
			||||||
 | 
					import java.net.SocketTimeoutException
 | 
				
			||||||
import java.util.concurrent.ConcurrentHashMap
 | 
					import java.util.concurrent.ConcurrentHashMap
 | 
				
			||||||
 | 
					import java.util.concurrent.TimeUnit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SelfossApi(
 | 
					class SelfossApi(
 | 
				
			||||||
    c: Context,
 | 
					    c: Context,
 | 
				
			||||||
    callingActivity: Activity,
 | 
					    callingActivity: Activity?,
 | 
				
			||||||
    isWithSelfSignedCert: Boolean,
 | 
					    isWithSelfSignedCert: Boolean,
 | 
				
			||||||
    shouldLog: Boolean
 | 
					    timeout: Long
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private lateinit var service: SelfossService
 | 
					    private lateinit var service: SelfossService
 | 
				
			||||||
@@ -38,19 +43,39 @@ class SelfossApi(
 | 
				
			|||||||
            this
 | 
					            this
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun OkHttpClient.Builder.maybeWithSettingsTimeout(timeout: Long): OkHttpClient.Builder =
 | 
				
			||||||
 | 
					        if (timeout != -1L) {
 | 
				
			||||||
 | 
					            this.readTimeout(timeout, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					                .connectTimeout(timeout, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            this
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun Credentials.createAuthenticator(): DispatchingAuthenticator =
 | 
					    fun Credentials.createAuthenticator(): DispatchingAuthenticator =
 | 
				
			||||||
        DispatchingAuthenticator.Builder()
 | 
					        DispatchingAuthenticator.Builder()
 | 
				
			||||||
            .with("digest", DigestAuthenticator(this))
 | 
					            .with("digest", DigestAuthenticator(this))
 | 
				
			||||||
            .with("basic", BasicAuthenticator(this))
 | 
					            .with("basic", BasicAuthenticator(this))
 | 
				
			||||||
            .build()
 | 
					            .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient.Builder {
 | 
					    fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean, timeout: Long): OkHttpClient.Builder {
 | 
				
			||||||
        val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
 | 
					        val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
 | 
				
			||||||
        return OkHttpClient
 | 
					        return OkHttpClient
 | 
				
			||||||
            .Builder()
 | 
					            .Builder()
 | 
				
			||||||
 | 
					            .maybeWithSettingsTimeout(timeout)
 | 
				
			||||||
            .maybeWithSelfSigned(isWithSelfSignedCert)
 | 
					            .maybeWithSelfSigned(isWithSelfSignedCert)
 | 
				
			||||||
            .authenticator(CachingAuthenticatorDecorator(this, authCache))
 | 
					            .authenticator(CachingAuthenticatorDecorator(this, authCache))
 | 
				
			||||||
            .addInterceptor(AuthenticationCacheInterceptor(authCache))
 | 
					            .addInterceptor(AuthenticationCacheInterceptor(authCache))
 | 
				
			||||||
 | 
					            .addInterceptor(object: Interceptor {
 | 
				
			||||||
 | 
					                override fun intercept(chain: Interceptor.Chain): Response {
 | 
				
			||||||
 | 
					                    val request: Request = chain.request()
 | 
				
			||||||
 | 
					                    val response: Response = chain.proceed(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (response.code == 408) {
 | 
				
			||||||
 | 
					                        return response
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    return response
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    init {
 | 
					    init {
 | 
				
			||||||
@@ -66,20 +91,40 @@ class SelfossApi(
 | 
				
			|||||||
        val gson =
 | 
					        val gson =
 | 
				
			||||||
            GsonBuilder()
 | 
					            GsonBuilder()
 | 
				
			||||||
                .registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
 | 
					                .registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
 | 
				
			||||||
 | 
					                .registerTypeAdapter(SelfossTagType::class.java, SelfossTagTypeTypeAdapter())
 | 
				
			||||||
                .setLenient()
 | 
					                .setLenient()
 | 
				
			||||||
                .create()
 | 
					                .create()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val logging = HttpLoggingInterceptor()
 | 
					        val logging = HttpLoggingInterceptor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logging.level = if (shouldLog) {
 | 
					 | 
				
			||||||
            HttpLoggingInterceptor.Level.BODY
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            HttpLoggingInterceptor.Level.NONE
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val httpClient = authenticator.getHttpClien(isWithSelfSignedCert)
 | 
					        logging.level = HttpLoggingInterceptor.Level.NONE
 | 
				
			||||||
 | 
					        val httpClient = authenticator.getHttpClien(isWithSelfSignedCert, timeout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        httpClient.addInterceptor(logging)
 | 
					        val timeoutCode = 504
 | 
				
			||||||
 | 
					        httpClient
 | 
				
			||||||
 | 
					                .addInterceptor { chain ->
 | 
				
			||||||
 | 
					                    val res = chain.proceed(chain.request())
 | 
				
			||||||
 | 
					                    if (res.code == timeoutCode) {
 | 
				
			||||||
 | 
					                        throw SocketTimeoutException("timeout")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    res
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .addInterceptor(logging)
 | 
				
			||||||
 | 
					                .addInterceptor { chain ->
 | 
				
			||||||
 | 
					                    val request = chain.request()
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        chain.proceed(request)
 | 
				
			||||||
 | 
					                    } catch (e: SocketTimeoutException) {
 | 
				
			||||||
 | 
					                        Response.Builder()
 | 
				
			||||||
 | 
					                                .code(timeoutCode)
 | 
				
			||||||
 | 
					                                .protocol(Protocol.HTTP_2)
 | 
				
			||||||
 | 
					                                .body("".toResponseBody("text/plain".toMediaTypeOrNull()))
 | 
				
			||||||
 | 
					                                .message("")
 | 
				
			||||||
 | 
					                                .request(request)
 | 
				
			||||||
 | 
					                                .build()
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            val retrofit =
 | 
					            val retrofit =
 | 
				
			||||||
@@ -91,52 +136,59 @@ class SelfossApi(
 | 
				
			|||||||
                    .build()
 | 
					                    .build()
 | 
				
			||||||
            service = retrofit.create(SelfossService::class.java)
 | 
					            service = retrofit.create(SelfossService::class.java)
 | 
				
			||||||
        } catch (e: IllegalArgumentException) {
 | 
					        } catch (e: IllegalArgumentException) {
 | 
				
			||||||
            Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true)
 | 
					            if (callingActivity != null) {
 | 
				
			||||||
 | 
					                Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun login(): Call<SuccessResponse> =
 | 
					    fun login(): Call<SuccessResponse> =
 | 
				
			||||||
        service.loginToSelfoss(config.userLogin, config.userPassword)
 | 
					        service.loginToSelfoss(config.userLogin, config.userPassword)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun readItems(
 | 
					    suspend fun readItems(
 | 
				
			||||||
        tag: String?,
 | 
					 | 
				
			||||||
        sourceId: Long?,
 | 
					 | 
				
			||||||
        search: String?,
 | 
					 | 
				
			||||||
        itemsNumber: Int,
 | 
					        itemsNumber: Int,
 | 
				
			||||||
        offset: Int
 | 
					        offset: Int
 | 
				
			||||||
    ): Call<List<Item>> =
 | 
					    ): retrofit2.Response<List<Item>> =
 | 
				
			||||||
        getItems("read", tag, sourceId, search, itemsNumber, offset)
 | 
					        getItems("read", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun newItems(
 | 
					    suspend fun newItems(
 | 
				
			||||||
        tag: String?,
 | 
					 | 
				
			||||||
        sourceId: Long?,
 | 
					 | 
				
			||||||
        search: String?,
 | 
					 | 
				
			||||||
        itemsNumber: Int,
 | 
					        itemsNumber: Int,
 | 
				
			||||||
        offset: Int
 | 
					        offset: Int
 | 
				
			||||||
    ): Call<List<Item>> =
 | 
					    ): retrofit2.Response<List<Item>> =
 | 
				
			||||||
        getItems("unread", tag, sourceId, search, itemsNumber, offset)
 | 
					        getItems("unread", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun starredItems(
 | 
					    suspend fun starredItems(
 | 
				
			||||||
        tag: String?,
 | 
					 | 
				
			||||||
        sourceId: Long?,
 | 
					 | 
				
			||||||
        search: String?,
 | 
					 | 
				
			||||||
        itemsNumber: Int,
 | 
					        itemsNumber: Int,
 | 
				
			||||||
        offset: Int
 | 
					        offset: Int
 | 
				
			||||||
    ): Call<List<Item>> =
 | 
					    ): retrofit2.Response<List<Item>> =
 | 
				
			||||||
        getItems("starred", tag, sourceId, search, itemsNumber, offset)
 | 
					        getItems("starred", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun allItems(): Call<List<Item>> =
 | 
					    fun allItems(): Call<List<Item>> =
 | 
				
			||||||
        service.allItems(userName, password)
 | 
					        service.allItems(userName, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun getItems(
 | 
					    suspend fun allNewItems(): retrofit2.Response<List<Item>> =
 | 
				
			||||||
 | 
					            getItems("unread", null, null, null, 200, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun allReadItems(): retrofit2.Response<List<Item>> =
 | 
				
			||||||
 | 
					            getItems("read", null, null, null, 200, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun allStarredItems(): retrofit2.Response<List<Item>> =
 | 
				
			||||||
 | 
					        getItems("read", null, null, null, 200, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private suspend fun getItems(
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
        tag: String?,
 | 
					        tag: String?,
 | 
				
			||||||
        sourceId: Long?,
 | 
					        sourceId: Long?,
 | 
				
			||||||
        search: String?,
 | 
					        search: String?,
 | 
				
			||||||
        items: Int,
 | 
					        items: Int,
 | 
				
			||||||
        offset: Int
 | 
					        offset: Int
 | 
				
			||||||
    ): Call<List<Item>> =
 | 
					    ): retrofit2.Response<List<Item>> =
 | 
				
			||||||
        service.getItems(type, tag, sourceId, search, userName, password, items, offset)
 | 
					        service.getItems(type, tag, sourceId, search, null, userName, password, items, offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun updateItems(
 | 
				
			||||||
 | 
					        updatedSince: String
 | 
				
			||||||
 | 
					    ): retrofit2.Response<List<Item>> =
 | 
				
			||||||
 | 
					        service.getItems("read", null, null, null, updatedSince, userName, password, 200, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun markItem(itemId: String): Call<SuccessResponse> =
 | 
					    fun markItem(itemId: String): Call<SuccessResponse> =
 | 
				
			||||||
        service.markAsRead(itemId, userName, password)
 | 
					        service.markAsRead(itemId, userName, password)
 | 
				
			||||||
@@ -144,7 +196,7 @@ class SelfossApi(
 | 
				
			|||||||
    fun unmarkItem(itemId: String): Call<SuccessResponse> =
 | 
					    fun unmarkItem(itemId: String): Call<SuccessResponse> =
 | 
				
			||||||
        service.unmarkAsRead(itemId, userName, password)
 | 
					        service.unmarkAsRead(itemId, userName, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun readAll(ids: List<String>): Call<SuccessResponse> =
 | 
					    suspend fun readAll(ids: List<String>): SuccessResponse =
 | 
				
			||||||
        service.markAllAsRead(ids, userName, password)
 | 
					        service.markAllAsRead(ids, userName, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun starrItem(itemId: String): Call<SuccessResponse> =
 | 
					    fun starrItem(itemId: String): Call<SuccessResponse> =
 | 
				
			||||||
@@ -153,8 +205,7 @@ class SelfossApi(
 | 
				
			|||||||
    fun unstarrItem(itemId: String): Call<SuccessResponse> =
 | 
					    fun unstarrItem(itemId: String): Call<SuccessResponse> =
 | 
				
			||||||
        service.unstarr(itemId, userName, password)
 | 
					        service.unstarr(itemId, userName, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val stats: Call<Stats>
 | 
					    suspend fun stats(): retrofit2.Response<Stats> = service.stats(userName, password)
 | 
				
			||||||
        get() = service.stats(userName, password)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val tags: Call<List<Tag>>
 | 
					    val tags: Call<List<Tag>>
 | 
				
			||||||
        get() = service.tags(userName, password)
 | 
					        get() = service.tags(userName, password)
 | 
				
			||||||
@@ -162,6 +213,9 @@ class SelfossApi(
 | 
				
			|||||||
    fun update(): Call<String> =
 | 
					    fun update(): Call<String> =
 | 
				
			||||||
        service.update(userName, password)
 | 
					        service.update(userName, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val apiVersion: Call<ApiVersion>
 | 
				
			||||||
 | 
					        get() = service.version()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val sources: Call<List<Source>>
 | 
					    val sources: Call<List<Source>>
 | 
				
			||||||
        get() = service.sources(userName, password)
 | 
					        get() = service.sources(userName, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -179,4 +233,13 @@ class SelfossApi(
 | 
				
			|||||||
        filter: String
 | 
					        filter: String
 | 
				
			||||||
    ): Call<SuccessResponse> =
 | 
					    ): Call<SuccessResponse> =
 | 
				
			||||||
        service.createSource(title, url, spout, tags, filter, userName, password)
 | 
					        service.createSource(title, url, spout, tags, filter, userName, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun createSourceApi2(
 | 
				
			||||||
 | 
					        title: String,
 | 
				
			||||||
 | 
					        url: String,
 | 
				
			||||||
 | 
					        spout: String,
 | 
				
			||||||
 | 
					        tags: List<String>,
 | 
				
			||||||
 | 
					        filter: String
 | 
				
			||||||
 | 
					    ): Call<SuccessResponse> =
 | 
				
			||||||
 | 
					        service.createSourceApi2(title, url, spout, tags, filter, userName, password)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,134 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.api.selfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.SharedItems
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAvailable
 | 
				
			||||||
 | 
					import kotlinx.coroutines.*
 | 
				
			||||||
 | 
					import retrofit2.Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend fun getAndStoreAllItems(context: Context, api: SelfossApi, db: AppDatabase) = withContext(Dispatchers.IO) {
 | 
				
			||||||
 | 
					    if (isNetworkAvailable(context)) {
 | 
				
			||||||
 | 
					        launch {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                enqueueArticles(api.allNewItems(), db, true)
 | 
				
			||||||
 | 
					            } catch (e: Throwable) {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        launch {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                enqueueArticles(api.allReadItems(), db, false)
 | 
				
			||||||
 | 
					            } catch (e: Throwable) {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        launch {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                enqueueArticles(api.allStarredItems(), db, false)
 | 
				
			||||||
 | 
					            } catch (e: Throwable) {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        launch { SharedItems.updateDatabase(db) }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend fun updateItems(context: Context, api: SelfossApi, db: AppDatabase) = coroutineScope {
 | 
				
			||||||
 | 
					    if (isNetworkAvailable(context)) {
 | 
				
			||||||
 | 
					        launch {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                enqueueArticles(api.updateItems(SharedItems.items[0].datetime), db, true)
 | 
				
			||||||
 | 
					            } catch (e: Throwable) {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend fun refreshFocusedItems(context: Context, api: SelfossApi, db: AppDatabase) = withContext(Dispatchers.IO) {
 | 
				
			||||||
 | 
					    if (isNetworkAvailable(context)) {
 | 
				
			||||||
 | 
					        val response = when (SharedItems.displayedItems) {
 | 
				
			||||||
 | 
					            "read" -> api.readItems(200, 0)
 | 
				
			||||||
 | 
					            "unread" -> api.newItems(200, 0)
 | 
				
			||||||
 | 
					            "starred" -> api.starredItems(200, 0)
 | 
				
			||||||
 | 
					            else -> api.readItems(200, 0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (response.isSuccessful) {
 | 
				
			||||||
 | 
					            SharedItems.refreshFocusedItems(response.body() as ArrayList<Item>)
 | 
				
			||||||
 | 
					            SharedItems.updateDatabase(db)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend fun getReadItems(context: Context, api: SelfossApi, db: AppDatabase, offset: Int) = withContext(Dispatchers.IO) {
 | 
				
			||||||
 | 
					    if (isNetworkAvailable(context)) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                enqueueArticles(api.readItems( 200, offset), db, false)
 | 
				
			||||||
 | 
					                SharedItems.fetchedAll = true
 | 
				
			||||||
 | 
					                SharedItems.updateDatabase(db)
 | 
				
			||||||
 | 
					            } catch (e: Throwable) {}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend fun getUnreadItems(context: Context, api: SelfossApi, db: AppDatabase, offset: Int) = withContext(Dispatchers.IO) {
 | 
				
			||||||
 | 
					    if (isNetworkAvailable(context)) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            if (!SharedItems.fetchedUnread) {
 | 
				
			||||||
 | 
					                SharedItems.clearDBItems(db)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            enqueueArticles(api.newItems(200, offset), db, false)
 | 
				
			||||||
 | 
					            SharedItems.fetchedUnread = true
 | 
				
			||||||
 | 
					        } catch (e: Throwable) {}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    SharedItems.updateDatabase(db)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend fun getStarredItems(context: Context, api: SelfossApi, db: AppDatabase, offset: Int) = withContext(Dispatchers.IO) {
 | 
				
			||||||
 | 
					    if (isNetworkAvailable(context)) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            enqueueArticles(api.starredItems(200, offset), db, false)
 | 
				
			||||||
 | 
					            SharedItems.fetchedStarred = true
 | 
				
			||||||
 | 
					            SharedItems.updateDatabase(db)
 | 
				
			||||||
 | 
					        } catch (e: Throwable) {
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend fun readAll(context: Context, api: SelfossApi, db: AppDatabase): Boolean {
 | 
				
			||||||
 | 
					    var success = false
 | 
				
			||||||
 | 
					    if (isNetworkAvailable(context)) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            val ids = SharedItems.focusedItems.map { it.id }
 | 
				
			||||||
 | 
					            if (ids.isNotEmpty()) {
 | 
				
			||||||
 | 
					                val result = api.readAll(ids)
 | 
				
			||||||
 | 
					                SharedItems.readItems(db, ids)
 | 
				
			||||||
 | 
					                success = result.isSuccess
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (e: Throwable) {}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return success
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend fun reloadBadges(context: Context, api: SelfossApi) = withContext(Dispatchers.IO) {
 | 
				
			||||||
 | 
					    if (isNetworkAvailable(context)) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            val response = api.stats()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (response.isSuccessful) {
 | 
				
			||||||
 | 
					                val badges = response.body()
 | 
				
			||||||
 | 
					                SharedItems.badgeUnread = badges!!.unread
 | 
				
			||||||
 | 
					                SharedItems.badgeAll = badges.total
 | 
				
			||||||
 | 
					                SharedItems.badgeStarred = badges.starred
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (e: Throwable) {}
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        SharedItems.computeBadges()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private fun enqueueArticles(response: Response<List<Item>>, db: AppDatabase, clearDatabase: Boolean) {
 | 
				
			||||||
 | 
					        if (response.isSuccessful) {
 | 
				
			||||||
 | 
					            if (clearDatabase) {
 | 
				
			||||||
 | 
					                CoroutineScope(Dispatchers.IO).launch {
 | 
				
			||||||
 | 
					                    SharedItems.clearDBItems(db)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            val allItems = response.body() as ArrayList<Item>
 | 
				
			||||||
 | 
					            SharedItems.appendNewItems(allItems)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,9 +4,15 @@ import android.content.Context
 | 
				
			|||||||
import android.net.Uri
 | 
					import android.net.Uri
 | 
				
			||||||
import android.os.Parcel
 | 
					import android.os.Parcel
 | 
				
			||||||
import android.os.Parcelable
 | 
					import android.os.Parcelable
 | 
				
			||||||
 | 
					import android.text.Html
 | 
				
			||||||
 | 
					import android.webkit.URLUtil
 | 
				
			||||||
 | 
					import org.jsoup.Jsoup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.Config
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
 | 
					import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
 | 
				
			||||||
 | 
					import com.bumptech.glide.Glide
 | 
				
			||||||
 | 
					import com.bumptech.glide.load.engine.DiskCacheStrategy
 | 
				
			||||||
 | 
					import com.bumptech.glide.request.RequestOptions
 | 
				
			||||||
import com.google.gson.annotations.SerializedName
 | 
					import com.google.gson.annotations.SerializedName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private fun constructUrl(config: Config?, path: String, file: String?): String {
 | 
					private fun constructUrl(config: Config?, path: String, file: String?): String {
 | 
				
			||||||
@@ -24,7 +30,11 @@ data class Tag(
 | 
				
			|||||||
    @SerializedName("tag") val tag: String,
 | 
					    @SerializedName("tag") val tag: String,
 | 
				
			||||||
    @SerializedName("color") val color: String,
 | 
					    @SerializedName("color") val color: String,
 | 
				
			||||||
    @SerializedName("unread") val unread: Int
 | 
					    @SerializedName("unread") val unread: Int
 | 
				
			||||||
)
 | 
					) {
 | 
				
			||||||
 | 
					    fun getTitleDecoded(): String {
 | 
				
			||||||
 | 
					        return Html.fromHtml(tag).toString()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SuccessResponse(@SerializedName("success") val success: Boolean) {
 | 
					class SuccessResponse(@SerializedName("success") val success: Boolean) {
 | 
				
			||||||
    val isSuccess: Boolean
 | 
					    val isSuccess: Boolean
 | 
				
			||||||
@@ -42,10 +52,23 @@ data class Spout(
 | 
				
			|||||||
    @SerializedName("description") val description: String
 | 
					    @SerializedName("description") val description: String
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class ApiVersion(
 | 
				
			||||||
 | 
					        @SerializedName("version") val version: String?,
 | 
				
			||||||
 | 
					        @SerializedName("apiversion") val apiversion: String?
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    fun getApiMajorVersion() : Int {
 | 
				
			||||||
 | 
					        var versionNumber = 0
 | 
				
			||||||
 | 
					        if (apiversion != null) {
 | 
				
			||||||
 | 
					            versionNumber = apiversion.substringBefore(".").toInt()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return versionNumber
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data class Source(
 | 
					data class Source(
 | 
				
			||||||
    @SerializedName("id") val id: String,
 | 
					    @SerializedName("id") val id: String,
 | 
				
			||||||
    @SerializedName("title") val title: String,
 | 
					    @SerializedName("title") val title: String,
 | 
				
			||||||
    @SerializedName("tags") val tags: String,
 | 
					    @SerializedName("tags") val tags: SelfossTagType,
 | 
				
			||||||
    @SerializedName("spout") val spout: String,
 | 
					    @SerializedName("spout") val spout: String,
 | 
				
			||||||
    @SerializedName("error") val error: String,
 | 
					    @SerializedName("error") val error: String,
 | 
				
			||||||
    @SerializedName("icon") val icon: String
 | 
					    @SerializedName("icon") val icon: String
 | 
				
			||||||
@@ -58,6 +81,10 @@ data class Source(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return constructUrl(config, "favicons", icon)
 | 
					        return constructUrl(config, "favicons", icon)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getTitleDecoded(): String {
 | 
				
			||||||
 | 
					        return Html.fromHtml(title).toString()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data class Item(
 | 
					data class Item(
 | 
				
			||||||
@@ -65,13 +92,13 @@ data class Item(
 | 
				
			|||||||
    @SerializedName("datetime") val datetime: String,
 | 
					    @SerializedName("datetime") val datetime: String,
 | 
				
			||||||
    @SerializedName("title") val title: String,
 | 
					    @SerializedName("title") val title: String,
 | 
				
			||||||
    @SerializedName("content") val content: String,
 | 
					    @SerializedName("content") val content: String,
 | 
				
			||||||
    @SerializedName("unread") val unread: Boolean,
 | 
					    @SerializedName("unread") var unread: Boolean,
 | 
				
			||||||
    @SerializedName("starred") var starred: Boolean,
 | 
					    @SerializedName("starred") var starred: Boolean,
 | 
				
			||||||
    @SerializedName("thumbnail") val thumbnail: String,
 | 
					    @SerializedName("thumbnail") val thumbnail: String?,
 | 
				
			||||||
    @SerializedName("icon") val icon: String,
 | 
					    @SerializedName("icon") val icon: String?,
 | 
				
			||||||
    @SerializedName("link") val link: String,
 | 
					    @SerializedName("link") val link: String,
 | 
				
			||||||
    @SerializedName("sourcetitle") val sourcetitle: String,
 | 
					    @SerializedName("sourcetitle") val sourcetitle: String,
 | 
				
			||||||
    @SerializedName("tags") val tags: String
 | 
					    @SerializedName("tags") val tags: SelfossTagType
 | 
				
			||||||
) : Parcelable {
 | 
					) : Parcelable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var config: Config? = null
 | 
					    var config: Config? = null
 | 
				
			||||||
@@ -84,17 +111,17 @@ data class Item(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(source: Parcel) : this(
 | 
					    constructor(source: Parcel) : this(
 | 
				
			||||||
        id = source.readString(),
 | 
					        id = source.readString().orEmpty(),
 | 
				
			||||||
        datetime = source.readString(),
 | 
					        datetime = source.readString().orEmpty(),
 | 
				
			||||||
        title = source.readString(),
 | 
					        title = source.readString().orEmpty(),
 | 
				
			||||||
        content = source.readString(),
 | 
					        content = source.readString().orEmpty(),
 | 
				
			||||||
        unread = 0.toByte() != source.readByte(),
 | 
					        unread = 0.toByte() != source.readByte(),
 | 
				
			||||||
        starred = 0.toByte() != source.readByte(),
 | 
					        starred = 0.toByte() != source.readByte(),
 | 
				
			||||||
        thumbnail = source.readString(),
 | 
					        thumbnail = source.readString(),
 | 
				
			||||||
        icon = source.readString(),
 | 
					        icon = source.readString(),
 | 
				
			||||||
        link = source.readString(),
 | 
					        link = source.readString().orEmpty(),
 | 
				
			||||||
        sourcetitle = source.readString(),
 | 
					        sourcetitle = source.readString().orEmpty(),
 | 
				
			||||||
        tags = source.readString()
 | 
					        tags = if (source.readParcelable<SelfossTagType>(ClassLoader.getSystemClassLoader()) != null) source.readParcelable(ClassLoader.getSystemClassLoader())!! else SelfossTagType("")
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun describeContents() = 0
 | 
					    override fun describeContents() = 0
 | 
				
			||||||
@@ -110,7 +137,7 @@ data class Item(
 | 
				
			|||||||
        dest.writeString(icon)
 | 
					        dest.writeString(icon)
 | 
				
			||||||
        dest.writeString(link)
 | 
					        dest.writeString(link)
 | 
				
			||||||
        dest.writeString(sourcetitle)
 | 
					        dest.writeString(sourcetitle)
 | 
				
			||||||
        dest.writeString(tags)
 | 
					        dest.writeParcelable(tags, flags)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun getIcon(app: Context): String {
 | 
					    fun getIcon(app: Context): String {
 | 
				
			||||||
@@ -127,6 +154,51 @@ data class Item(
 | 
				
			|||||||
        return constructUrl(config, "thumbnails", thumbnail)
 | 
					        return constructUrl(config, "thumbnails", thumbnail)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getImages() : ArrayList<String> {
 | 
				
			||||||
 | 
					        var allImages = ArrayList<String>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for ( image in Jsoup.parse(content).getElementsByTag("img")) {
 | 
				
			||||||
 | 
					            val url = image.attr("src")
 | 
				
			||||||
 | 
					            if (url.toLowerCase().contains(".jpg") ||
 | 
				
			||||||
 | 
					                    url.toLowerCase().contains(".jpeg") ||
 | 
				
			||||||
 | 
					                    url.toLowerCase().contains(".png") ||
 | 
				
			||||||
 | 
					                    url.toLowerCase().contains(".webp"))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                allImages.add(url)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return allImages
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun preloadImages(context: Context) : Boolean {
 | 
				
			||||||
 | 
					        val imageUrls = this.getImages()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            for (url in imageUrls) {
 | 
				
			||||||
 | 
					                if ( URLUtil.isValidUrl(url)) {
 | 
				
			||||||
 | 
					                    val image = Glide.with(context).asBitmap()
 | 
				
			||||||
 | 
					                            .apply(glideOptions)
 | 
				
			||||||
 | 
					                            .load(url).submit()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (e : Error) {
 | 
				
			||||||
 | 
					            return false
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getTitleDecoded(): String {
 | 
				
			||||||
 | 
					        return Html.fromHtml(title).toString()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getSourceTitle(): String {
 | 
				
			||||||
 | 
					        return Html.fromHtml(sourcetitle).toString()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: maybe find a better way to handle these kind of urls
 | 
					    // TODO: maybe find a better way to handle these kind of urls
 | 
				
			||||||
    fun getLinkDecoded(): String {
 | 
					    fun getLinkDecoded(): String {
 | 
				
			||||||
        var stringUrl: String
 | 
					        var stringUrl: String
 | 
				
			||||||
@@ -154,3 +226,26 @@ data class Item(
 | 
				
			|||||||
        return stringUrl
 | 
					        return stringUrl
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class SelfossTagType(val tags: String) : Parcelable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        @JvmField val CREATOR: Parcelable.Creator<SelfossTagType> =
 | 
				
			||||||
 | 
					            object : Parcelable.Creator<SelfossTagType> {
 | 
				
			||||||
 | 
					                override fun createFromParcel(source: Parcel): SelfossTagType =
 | 
				
			||||||
 | 
					                    SelfossTagType(source)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                override fun newArray(size: Int): Array<SelfossTagType?> = arrayOfNulls(size)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(source: Parcel) : this(
 | 
				
			||||||
 | 
					        tags = source.readString().orEmpty()
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun describeContents() = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun writeToParcel(dest: Parcel, flags: Int) {
 | 
				
			||||||
 | 
					        dest.writeString(tags)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.api.selfoss
 | 
					package apps.amine.bou.readerforselfoss.api.selfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
 | 
					import retrofit2.Response
 | 
				
			||||||
import retrofit2.http.DELETE
 | 
					import retrofit2.http.DELETE
 | 
				
			||||||
import retrofit2.http.Field
 | 
					import retrofit2.http.Field
 | 
				
			||||||
import retrofit2.http.FormUrlEncoded
 | 
					import retrofit2.http.FormUrlEncoded
 | 
				
			||||||
@@ -16,16 +17,17 @@ internal interface SelfossService {
 | 
				
			|||||||
    fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
 | 
					    fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GET("items")
 | 
					    @GET("items")
 | 
				
			||||||
    fun getItems(
 | 
					    suspend fun getItems(
 | 
				
			||||||
        @Query("type") type: String,
 | 
					        @Query("type") type: String,
 | 
				
			||||||
        @Query("tag") tag: String?,
 | 
					        @Query("tag") tag: String?,
 | 
				
			||||||
        @Query("source") source: Long?,
 | 
					        @Query("source") source: Long?,
 | 
				
			||||||
        @Query("search") search: String?,
 | 
					        @Query("search") search: String?,
 | 
				
			||||||
 | 
					        @Query("updatedsince") updatedSince: String?,
 | 
				
			||||||
        @Query("username") username: String,
 | 
					        @Query("username") username: String,
 | 
				
			||||||
        @Query("password") password: String,
 | 
					        @Query("password") password: String,
 | 
				
			||||||
        @Query("items") items: Int,
 | 
					        @Query("items") items: Int,
 | 
				
			||||||
        @Query("offset") offset: Int
 | 
					        @Query("offset") offset: Int
 | 
				
			||||||
    ): Call<List<Item>>
 | 
					    ): Response<List<Item>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GET("items")
 | 
					    @GET("items")
 | 
				
			||||||
    fun allItems(
 | 
					    fun allItems(
 | 
				
			||||||
@@ -51,11 +53,11 @@ internal interface SelfossService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @FormUrlEncoded
 | 
					    @FormUrlEncoded
 | 
				
			||||||
    @POST("mark")
 | 
					    @POST("mark")
 | 
				
			||||||
    fun markAllAsRead(
 | 
					    suspend fun markAllAsRead(
 | 
				
			||||||
        @Field("ids[]") ids: List<String>,
 | 
					        @Field("ids[]") ids: List<String>,
 | 
				
			||||||
        @Query("username") username: String,
 | 
					        @Query("username") username: String,
 | 
				
			||||||
        @Query("password") password: String
 | 
					        @Query("password") password: String
 | 
				
			||||||
    ): Call<SuccessResponse>
 | 
					    ): SuccessResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Headers("Content-Type: application/x-www-form-urlencoded")
 | 
					    @Headers("Content-Type: application/x-www-form-urlencoded")
 | 
				
			||||||
    @POST("starr/{id}")
 | 
					    @POST("starr/{id}")
 | 
				
			||||||
@@ -74,10 +76,10 @@ internal interface SelfossService {
 | 
				
			|||||||
    ): Call<SuccessResponse>
 | 
					    ): Call<SuccessResponse>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GET("stats")
 | 
					    @GET("stats")
 | 
				
			||||||
    fun stats(
 | 
					    suspend fun stats(
 | 
				
			||||||
        @Query("username") username: String,
 | 
					        @Query("username") username: String,
 | 
				
			||||||
        @Query("password") password: String
 | 
					        @Query("password") password: String
 | 
				
			||||||
    ): Call<Stats>
 | 
					    ): Response<Stats>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GET("tags")
 | 
					    @GET("tags")
 | 
				
			||||||
    fun tags(
 | 
					    fun tags(
 | 
				
			||||||
@@ -103,6 +105,9 @@ internal interface SelfossService {
 | 
				
			|||||||
        @Query("password") password: String
 | 
					        @Query("password") password: String
 | 
				
			||||||
    ): Call<List<Source>>
 | 
					    ): Call<List<Source>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GET("api/about")
 | 
				
			||||||
 | 
					    fun version(): Call<ApiVersion>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @DELETE("source/{id}")
 | 
					    @DELETE("source/{id}")
 | 
				
			||||||
    fun deleteSource(
 | 
					    fun deleteSource(
 | 
				
			||||||
        @Path("id") id: String,
 | 
					        @Path("id") id: String,
 | 
				
			||||||
@@ -121,4 +126,16 @@ internal interface SelfossService {
 | 
				
			|||||||
        @Query("username") username: String,
 | 
					        @Query("username") username: String,
 | 
				
			||||||
        @Query("password") password: String
 | 
					        @Query("password") password: String
 | 
				
			||||||
    ): Call<SuccessResponse>
 | 
					    ): Call<SuccessResponse>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @FormUrlEncoded
 | 
				
			||||||
 | 
					    @POST("source")
 | 
				
			||||||
 | 
					    fun createSourceApi2(
 | 
				
			||||||
 | 
					        @Field("title") title: String,
 | 
				
			||||||
 | 
					        @Field("url") url: String,
 | 
				
			||||||
 | 
					        @Field("spout") spout: String,
 | 
				
			||||||
 | 
					        @Field("tags[]") tags: List<String>,
 | 
				
			||||||
 | 
					        @Field("filter") filter: String,
 | 
				
			||||||
 | 
					        @Query("username") username: String,
 | 
				
			||||||
 | 
					        @Query("password") password: String
 | 
				
			||||||
 | 
					    ): Call<SuccessResponse>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.api.selfoss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.gson.JsonDeserializationContext
 | 
				
			||||||
 | 
					import com.google.gson.JsonDeserializer
 | 
				
			||||||
 | 
					import com.google.gson.JsonElement
 | 
				
			||||||
 | 
					import com.google.gson.JsonParseException
 | 
				
			||||||
 | 
					import java.lang.reflect.Type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal class SelfossTagTypeTypeAdapter : JsonDeserializer<SelfossTagType> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Throws(JsonParseException::class)
 | 
				
			||||||
 | 
					    override fun deserialize(
 | 
				
			||||||
 | 
					        json: JsonElement,
 | 
				
			||||||
 | 
					        typeOfT: Type,
 | 
				
			||||||
 | 
					        context: JsonDeserializationContext
 | 
				
			||||||
 | 
					    ): SelfossTagType? =
 | 
				
			||||||
 | 
					        if (json.isJsonArray) {
 | 
				
			||||||
 | 
					            SelfossTagType(json.asJsonArray.joinToString(",") { it.toString() })
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            SelfossTagType(json.toString())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,169 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.background
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.app.NotificationManager
 | 
				
			||||||
 | 
					import android.app.PendingIntent
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.content.Intent
 | 
				
			||||||
 | 
					import android.os.Build
 | 
				
			||||||
 | 
					import androidx.preference.PreferenceManager
 | 
				
			||||||
 | 
					import androidx.core.app.NotificationCompat
 | 
				
			||||||
 | 
					import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
 | 
				
			||||||
 | 
					import androidx.core.app.NotificationCompat.PRIORITY_LOW
 | 
				
			||||||
 | 
					import androidx.room.Room
 | 
				
			||||||
 | 
					import androidx.work.Worker
 | 
				
			||||||
 | 
					import androidx.work.WorkerParameters
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.MainActivity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.getAndStoreAllItems
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_3_4
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.SharedItems
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAvailable
 | 
				
			||||||
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
 | 
					import retrofit2.Call
 | 
				
			||||||
 | 
					import retrofit2.Callback
 | 
				
			||||||
 | 
					import retrofit2.Response
 | 
				
			||||||
 | 
					import java.util.*
 | 
				
			||||||
 | 
					import kotlin.concurrent.schedule
 | 
				
			||||||
 | 
					import kotlin.concurrent.thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
 | 
				
			||||||
 | 
					    lateinit var db: AppDatabase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					override fun doWork(): Result {
 | 
				
			||||||
 | 
					    val settings =
 | 
				
			||||||
 | 
					        this.context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
				
			||||||
 | 
					    val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context)
 | 
				
			||||||
 | 
					    val periodicRefresh = sharedPref.getBoolean("periodic_refresh", false)
 | 
				
			||||||
 | 
					    if (periodicRefresh) {
 | 
				
			||||||
 | 
					        val api = SelfossApi(
 | 
				
			||||||
 | 
					            this.context,
 | 
				
			||||||
 | 
					            null,
 | 
				
			||||||
 | 
					            settings.getBoolean("isSelfSignedCert", false),
 | 
				
			||||||
 | 
					            sharedPref.getString("api_timeout", "-1")!!.toLong()
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isNetworkAvailable(context)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            CoroutineScope(Dispatchers.IO).launch {
 | 
				
			||||||
 | 
					                val notificationManager =
 | 
				
			||||||
 | 
					                    applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                val notification =
 | 
				
			||||||
 | 
					                    NotificationCompat.Builder(applicationContext, Config.syncChannelId)
 | 
				
			||||||
 | 
					                        .setContentTitle(context.getString(R.string.loading_notification_title))
 | 
				
			||||||
 | 
					                        .setContentText(context.getString(R.string.loading_notification_text))
 | 
				
			||||||
 | 
					                        .setOngoing(true)
 | 
				
			||||||
 | 
					                        .setPriority(PRIORITY_LOW)
 | 
				
			||||||
 | 
					                        .setChannelId(Config.syncChannelId)
 | 
				
			||||||
 | 
					                        .setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                notificationManager.notify(1, notification.build())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                val notifyNewItems = sharedPref.getBoolean("notify_new_items", false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                db = Room.databaseBuilder(
 | 
				
			||||||
 | 
					                    applicationContext,
 | 
				
			||||||
 | 
					                    AppDatabase::class.java, "selfoss-database"
 | 
				
			||||||
 | 
					                ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3)
 | 
				
			||||||
 | 
					                    .addMigrations(MIGRATION_3_4).build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                val actions = db.actionsDao().actions()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                actions.forEach { action ->
 | 
				
			||||||
 | 
					                    when {
 | 
				
			||||||
 | 
					                        action.read -> doAndReportOnFail(
 | 
				
			||||||
 | 
					                            api.markItem(action.articleId),
 | 
				
			||||||
 | 
					                            action
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        action.unread -> doAndReportOnFail(
 | 
				
			||||||
 | 
					                            api.unmarkItem(action.articleId),
 | 
				
			||||||
 | 
					                            action
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        action.starred -> doAndReportOnFail(
 | 
				
			||||||
 | 
					                            api.starrItem(action.articleId),
 | 
				
			||||||
 | 
					                            action
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        action.unstarred -> doAndReportOnFail(
 | 
				
			||||||
 | 
					                            api.unstarrItem(action.articleId),
 | 
				
			||||||
 | 
					                            action
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                getAndStoreAllItems(context, api, db)
 | 
				
			||||||
 | 
					                SharedItems.updateDatabase(db)
 | 
				
			||||||
 | 
					                storeItems(notifyNewItems, notificationManager)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return Result.success()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun storeItems(notifyNewItems: Boolean, notificationManager: NotificationManager) {
 | 
				
			||||||
 | 
					        CoroutineScope(Dispatchers.IO).launch {
 | 
				
			||||||
 | 
					                val apiItems = SharedItems.items
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                val newSize = apiItems.filter { it.unread }.size
 | 
				
			||||||
 | 
					                if (notifyNewItems && newSize > 0) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    val intent = Intent(context, MainActivity::class.java).apply {
 | 
				
			||||||
 | 
					                        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
				
			||||||
 | 
					                        PendingIntent.FLAG_IMMUTABLE
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        0
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, pflags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    val newItemsNotification =
 | 
				
			||||||
 | 
					                        NotificationCompat.Builder(applicationContext, Config.newItemsChannelId)
 | 
				
			||||||
 | 
					                            .setContentTitle(context.getString(R.string.new_items_notification_title))
 | 
				
			||||||
 | 
					                            .setContentText(
 | 
				
			||||||
 | 
					                                context.getString(
 | 
				
			||||||
 | 
					                                    R.string.new_items_notification_text,
 | 
				
			||||||
 | 
					                                    newSize
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                            .setPriority(PRIORITY_DEFAULT)
 | 
				
			||||||
 | 
					                            .setChannelId(Config.newItemsChannelId)
 | 
				
			||||||
 | 
					                            .setContentIntent(pendingIntent)
 | 
				
			||||||
 | 
					                            .setAutoCancel(true)
 | 
				
			||||||
 | 
					                            .setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Timer("", false).schedule(4000) {
 | 
				
			||||||
 | 
					                        notificationManager.notify(2, newItemsNotification.build())
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                apiItems.map { it.preloadImages(context) }
 | 
				
			||||||
 | 
					            Timer("", false).schedule(4000) {
 | 
				
			||||||
 | 
					                notificationManager.cancel(1)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun <T> doAndReportOnFail(call: Call<T>, action: ActionEntity) {
 | 
				
			||||||
 | 
					        call.enqueue(object : Callback<T> {
 | 
				
			||||||
 | 
					            override fun onResponse(
 | 
				
			||||||
 | 
					                call: Call<T>,
 | 
				
			||||||
 | 
					                response: Response<T>
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                thread {
 | 
				
			||||||
 | 
					                    db.actionsDao().delete(action)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            override fun onFailure(call: Call<T>, t: Throwable) {
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,334 +1,416 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.fragments
 | 
					package apps.amine.bou.readerforselfoss.fragments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.content.Intent
 | 
				
			||||||
import android.content.SharedPreferences
 | 
					import android.content.SharedPreferences
 | 
				
			||||||
import android.content.res.ColorStateList
 | 
					import android.content.res.ColorStateList
 | 
				
			||||||
 | 
					import android.content.res.TypedArray
 | 
				
			||||||
 | 
					import android.graphics.Bitmap
 | 
				
			||||||
 | 
					import android.graphics.Typeface
 | 
				
			||||||
import android.graphics.drawable.ColorDrawable
 | 
					import android.graphics.drawable.ColorDrawable
 | 
				
			||||||
import android.os.Build
 | 
					import android.net.Uri
 | 
				
			||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					import androidx.preference.PreferenceManager
 | 
				
			||||||
 | 
					import android.view.*
 | 
				
			||||||
 | 
					import android.webkit.*
 | 
				
			||||||
 | 
					import android.widget.Toast
 | 
				
			||||||
import androidx.browser.customtabs.CustomTabsIntent
 | 
					import androidx.browser.customtabs.CustomTabsIntent
 | 
				
			||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
 | 
					import com.google.android.material.floatingactionbutton.FloatingActionButton
 | 
				
			||||||
import androidx.fragment.app.Fragment
 | 
					import androidx.fragment.app.Fragment
 | 
				
			||||||
import androidx.core.content.ContextCompat
 | 
					import androidx.core.content.ContextCompat
 | 
				
			||||||
import androidx.core.widget.NestedScrollView
 | 
					import androidx.core.widget.NestedScrollView
 | 
				
			||||||
import android.view.LayoutInflater
 | 
					import androidx.appcompat.app.AlertDialog
 | 
				
			||||||
import android.view.MenuItem
 | 
					import androidx.core.content.res.ResourcesCompat
 | 
				
			||||||
import android.view.View
 | 
					import androidx.room.Room
 | 
				
			||||||
import android.view.ViewGroup
 | 
					import apps.amine.bou.readerforselfoss.ImageActivity
 | 
				
			||||||
import android.webkit.WebSettings
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
 | 
					import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
 | 
					import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
					import apps.amine.bou.readerforselfoss.databinding.FragmentArticleBinding
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_3_4
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
					import apps.amine.bou.readerforselfoss.themes.AppColors
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.Config
 | 
					import apps.amine.bou.readerforselfoss.utils.*
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
 | 
					import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
 | 
					import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
 | 
					import apps.amine.bou.readerforselfoss.utils.glide.getBitmapInputStream
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.shareLink
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.succeeded
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.toPx
 | 
					 | 
				
			||||||
import com.bumptech.glide.Glide
 | 
					import com.bumptech.glide.Glide
 | 
				
			||||||
 | 
					import com.bumptech.glide.load.engine.DiskCacheStrategy
 | 
				
			||||||
import com.bumptech.glide.request.RequestOptions
 | 
					import com.bumptech.glide.request.RequestOptions
 | 
				
			||||||
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
 | 
					import com.github.rubensousa.floatingtoolbar.FloatingToolbar
 | 
				
			||||||
import kotlinx.android.synthetic.main.fragment_article.view.*
 | 
					 | 
				
			||||||
import org.acra.ACRA
 | 
					 | 
				
			||||||
import retrofit2.Call
 | 
					import retrofit2.Call
 | 
				
			||||||
import retrofit2.Callback
 | 
					import retrofit2.Callback
 | 
				
			||||||
import retrofit2.Response
 | 
					import retrofit2.Response
 | 
				
			||||||
import java.net.MalformedURLException
 | 
					import java.net.MalformedURLException
 | 
				
			||||||
import java.net.URL
 | 
					import java.net.URL
 | 
				
			||||||
 | 
					import java.util.concurrent.ExecutionException
 | 
				
			||||||
 | 
					import kotlin.collections.ArrayList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ArticleFragment : Fragment() {
 | 
					class ArticleFragment : Fragment() {
 | 
				
			||||||
    private lateinit var pageNumber: Number
 | 
					    private lateinit var pageNumber: Number
 | 
				
			||||||
    private var fontSize: Int = 14
 | 
					    private var fontSize: Int = 16
 | 
				
			||||||
    private lateinit var allItems: ArrayList<Item>
 | 
					    private lateinit var allItems: ArrayList<Item>
 | 
				
			||||||
    private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
 | 
					    private var mCustomTabActivityHelper: CustomTabActivityHelper? = null;
 | 
				
			||||||
    private lateinit var url: String
 | 
					    private lateinit var url: String
 | 
				
			||||||
    private lateinit var contentText: String
 | 
					    private lateinit var contentText: String
 | 
				
			||||||
    private lateinit var contentSource: String
 | 
					    private lateinit var contentSource: String
 | 
				
			||||||
    private lateinit var contentImage: String
 | 
					    private lateinit var contentImage: String
 | 
				
			||||||
    private lateinit var contentTitle: String
 | 
					    private lateinit var contentTitle: String
 | 
				
			||||||
 | 
					    private lateinit var allImages : ArrayList<String>
 | 
				
			||||||
    private lateinit var editor: SharedPreferences.Editor
 | 
					    private lateinit var editor: SharedPreferences.Editor
 | 
				
			||||||
    private lateinit var fab: FloatingActionButton
 | 
					    private lateinit var fab: FloatingActionButton
 | 
				
			||||||
    private lateinit var appColors: AppColors
 | 
					    private lateinit var appColors: AppColors
 | 
				
			||||||
 | 
					    private lateinit var db: AppDatabase
 | 
				
			||||||
 | 
					    private lateinit var textAlignment: String
 | 
				
			||||||
 | 
					    private lateinit var config: Config
 | 
				
			||||||
 | 
					    private var _binding: FragmentArticleBinding? = null
 | 
				
			||||||
 | 
					    private val binding get() = _binding!!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lateinit var prefs: SharedPreferences
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var typeface: Typeface? = null
 | 
				
			||||||
 | 
					    private var resId: Int = 0
 | 
				
			||||||
 | 
					    private var font = ""
 | 
				
			||||||
 | 
					    private var staticBar = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onStop() {
 | 
					    override fun onStop() {
 | 
				
			||||||
        super.onStop()
 | 
					        super.onStop()
 | 
				
			||||||
        mCustomTabActivityHelper.unbindCustomTabsService(activity)
 | 
					        if (mCustomTabActivityHelper != null) {
 | 
				
			||||||
 | 
					            mCustomTabActivityHelper!!.unbindCustomTabsService(activity)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
        appColors = AppColors(activity!!)
 | 
					        appColors = AppColors(requireActivity())
 | 
				
			||||||
 | 
					        config = Config(requireActivity())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super.onCreate(savedInstanceState)
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pageNumber = arguments!!.getInt(ARG_POSITION)
 | 
					        pageNumber = requireArguments().getInt(ARG_POSITION)
 | 
				
			||||||
        allItems = arguments!!.getParcelableArrayList(ARG_ITEMS)
 | 
					        allItems = requireArguments().getParcelableArrayList<Item>(ARG_ITEMS) as ArrayList<Item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        db = Room.databaseBuilder(
 | 
				
			||||||
 | 
					            requireContext(),
 | 
				
			||||||
 | 
					            AppDatabase::class.java, "selfoss-database"
 | 
				
			||||||
 | 
					        ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private lateinit var rootView: ViewGroup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun onCreateView(
 | 
					    override fun onCreateView(
 | 
				
			||||||
        inflater: LayoutInflater,
 | 
					        inflater: LayoutInflater,
 | 
				
			||||||
        container: ViewGroup?,
 | 
					        container: ViewGroup?,
 | 
				
			||||||
        savedInstanceState: Bundle?
 | 
					        savedInstanceState: Bundle?
 | 
				
			||||||
    ): View? {
 | 
					    ): View? {
 | 
				
			||||||
        rootView = inflater
 | 
					        try {
 | 
				
			||||||
            .inflate(R.layout.fragment_article, container, false) as ViewGroup
 | 
					            _binding = FragmentArticleBinding.inflate(inflater, container, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        url = allItems[pageNumber.toInt()].getLinkDecoded()
 | 
					            url = allItems[pageNumber.toInt()].getLinkDecoded()
 | 
				
			||||||
        contentText = allItems[pageNumber.toInt()].content
 | 
					            contentText = allItems[pageNumber.toInt()].content
 | 
				
			||||||
        contentTitle = allItems[pageNumber.toInt()].title
 | 
					            contentTitle = allItems[pageNumber.toInt()].getTitleDecoded()
 | 
				
			||||||
        contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!)
 | 
					            contentImage = allItems[pageNumber.toInt()].getThumbnail(requireActivity())
 | 
				
			||||||
        contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
 | 
					            contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
 | 
				
			||||||
 | 
					            allImages = allItems[pageNumber.toInt()].getImages()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
 | 
					            prefs = PreferenceManager.getDefaultSharedPreferences(activity)
 | 
				
			||||||
        editor = prefs.edit()
 | 
					            editor = prefs.edit()
 | 
				
			||||||
        fontSize = prefs.getString("reader_font_size", "14").toInt()
 | 
					            fontSize = prefs.getString("reader_font_size", "16")!!.toInt()
 | 
				
			||||||
 | 
					            staticBar = prefs.getBoolean("reader_static_bar", false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val settings = activity!!.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
					            font = prefs.getString("reader_font", "")!!
 | 
				
			||||||
        val debugReadingItems = prefs.getBoolean("read_debug", false)
 | 
					            if (font.isNotEmpty()) {
 | 
				
			||||||
 | 
					                resId = requireContext().resources.getIdentifier(font, "font", requireContext().packageName)
 | 
				
			||||||
 | 
					                typeface = try {
 | 
				
			||||||
 | 
					                    ResourcesCompat.getFont(requireContext(), resId)!!
 | 
				
			||||||
 | 
					                } catch (e: java.lang.Exception) {
 | 
				
			||||||
 | 
					                    // ACRA.getErrorReporter().maybeHandleSilentException(Throwable("Font loading issue: ${e.message}"), requireContext())
 | 
				
			||||||
 | 
					                    // Just to be sure
 | 
				
			||||||
 | 
					                    null
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val api = SelfossApi(
 | 
					            refreshAlignment()
 | 
				
			||||||
            context!!,
 | 
					 | 
				
			||||||
            activity!!,
 | 
					 | 
				
			||||||
            settings.getBoolean("isSelfSignedCert", false),
 | 
					 | 
				
			||||||
            prefs.getBoolean("should_log_everything", false)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fab = rootView.fab
 | 
					            val settings = requireActivity().getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
 | 
					            val api = SelfossApi(
 | 
				
			||||||
 | 
					                requireContext(),
 | 
				
			||||||
 | 
					                requireActivity(),
 | 
				
			||||||
 | 
					                settings.getBoolean("isSelfSignedCert", false),
 | 
				
			||||||
 | 
					                prefs.getString("api_timeout", "-1")!!.toLong()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fab.rippleColor = appColors.colorAccentDark
 | 
					            fab = binding.fab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val floatingToolbar: FloatingToolbar = rootView.floatingToolbar
 | 
					            fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
 | 
				
			||||||
        floatingToolbar.attachFab(fab)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        floatingToolbar.background = ColorDrawable(appColors.colorAccent)
 | 
					            fab.rippleColor = appColors.colorAccentDark
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val customTabsIntent = activity!!.buildCustomTabsIntent()
 | 
					            val floatingToolbar: FloatingToolbar = binding.floatingToolbar
 | 
				
			||||||
        mCustomTabActivityHelper = CustomTabActivityHelper()
 | 
					            floatingToolbar.attachFab(fab)
 | 
				
			||||||
        mCustomTabActivityHelper.bindCustomTabsService(activity)
 | 
					
 | 
				
			||||||
 | 
					            floatingToolbar.background = ColorDrawable(appColors.colorAccent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val customTabsIntent = requireActivity().buildCustomTabsIntent()
 | 
				
			||||||
 | 
					            mCustomTabActivityHelper = CustomTabActivityHelper()
 | 
				
			||||||
 | 
					            mCustomTabActivityHelper!!.bindCustomTabsService(activity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        floatingToolbar.setClickListener(
 | 
					            floatingToolbar.setClickListener(
 | 
				
			||||||
            object : FloatingToolbar.ItemClickListener {
 | 
					                object : FloatingToolbar.ItemClickListener {
 | 
				
			||||||
                override fun onItemClick(item: MenuItem) {
 | 
					                    override fun onItemClick(item: MenuItem) {
 | 
				
			||||||
                    when (item.itemId) {
 | 
					                        when (item.itemId) {
 | 
				
			||||||
                        R.id.more_action -> getContentFromMercury(customTabsIntent, prefs)
 | 
					                            R.id.more_action -> getContentFromMercury(customTabsIntent, prefs)
 | 
				
			||||||
                        R.id.share_action -> activity!!.shareLink(url)
 | 
					                            R.id.share_action -> requireActivity().shareLink(url, contentTitle)
 | 
				
			||||||
                        R.id.open_action -> activity!!.openItemUrl(
 | 
					                            R.id.open_action -> requireActivity().openItemUrl(
 | 
				
			||||||
                            allItems,
 | 
					                                allItems,
 | 
				
			||||||
                            pageNumber.toInt(),
 | 
					                                pageNumber.toInt(),
 | 
				
			||||||
                            url,
 | 
					                                url,
 | 
				
			||||||
                            customTabsIntent,
 | 
					                                customTabsIntent,
 | 
				
			||||||
                            false,
 | 
					                                false,
 | 
				
			||||||
                            false,
 | 
					                                false,
 | 
				
			||||||
                            activity!!
 | 
					                                requireActivity()
 | 
				
			||||||
                        )
 | 
					                            )
 | 
				
			||||||
                        R.id.unread_action -> api.unmarkItem(allItems[pageNumber.toInt()].id).enqueue(
 | 
					                            R.id.unread_action -> if (context != null) {
 | 
				
			||||||
                            object : Callback<SuccessResponse> {
 | 
					                                if (allItems[pageNumber.toInt()].unread) {
 | 
				
			||||||
                                override fun onResponse(
 | 
					                                    SharedItems.readItem(
 | 
				
			||||||
                                    call: Call<SuccessResponse>,
 | 
					                                        context!!,
 | 
				
			||||||
                                    response: Response<SuccessResponse>
 | 
					                                        api,
 | 
				
			||||||
                                ) {
 | 
					                                        db,
 | 
				
			||||||
                                    if (!response.succeeded() && debugReadingItems) {
 | 
					                                        allItems[pageNumber.toInt()]
 | 
				
			||||||
                                        val message =
 | 
					                                    )
 | 
				
			||||||
                                            "message: ${response.message()} " +
 | 
					                                    allItems[pageNumber.toInt()].unread = false
 | 
				
			||||||
                                                    "response isSuccess: ${response.isSuccessful} " +
 | 
					                                    Toast.makeText(
 | 
				
			||||||
                                                    "response code: ${response.code()} " +
 | 
					                                        context,
 | 
				
			||||||
                                                    "response message: ${response.message()} " +
 | 
					                                        R.string.marked_as_read,
 | 
				
			||||||
                                                    "response errorBody: ${response.errorBody()?.string()} " +
 | 
					                                        Toast.LENGTH_LONG
 | 
				
			||||||
                                                    "body success: ${response.body()?.success} " +
 | 
					                                    ).show()
 | 
				
			||||||
                                                    "body isSuccess: ${response.body()?.isSuccess}"
 | 
					                                } else {
 | 
				
			||||||
                                        ACRA.getErrorReporter().maybeHandleSilentException(Exception(message), activity!!)
 | 
					                                    SharedItems.unreadItem(
 | 
				
			||||||
                                    }
 | 
					                                        context!!,
 | 
				
			||||||
                                }
 | 
					                                        api,
 | 
				
			||||||
 | 
					                                        db,
 | 
				
			||||||
                                override fun onFailure(
 | 
					                                        allItems[pageNumber.toInt()]
 | 
				
			||||||
                                    call: Call<SuccessResponse>,
 | 
					                                    )
 | 
				
			||||||
                                    t: Throwable
 | 
					                                    allItems[pageNumber.toInt()].unread = true
 | 
				
			||||||
                                ) {
 | 
					                                    Toast.makeText(
 | 
				
			||||||
                                    if (debugReadingItems) {
 | 
					                                        context,
 | 
				
			||||||
                                        ACRA.getErrorReporter().maybeHandleSilentException(t, activity!!)
 | 
					                                        R.string.marked_as_unread,
 | 
				
			||||||
                                    }
 | 
					                                        Toast.LENGTH_LONG
 | 
				
			||||||
 | 
					                                    ).show()
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        )
 | 
					                            else -> Unit
 | 
				
			||||||
                        else -> Unit
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    override fun onItemLongClick(item: MenuItem?) {
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                override fun onItemLongClick(item: MenuItem?) {
 | 
					            if (staticBar) {
 | 
				
			||||||
 | 
					                fab.hide()
 | 
				
			||||||
 | 
					                floatingToolbar.show()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            binding.source.text = contentSource
 | 
				
			||||||
 | 
					            if (typeface != null) {
 | 
				
			||||||
 | 
					                binding.source.typeface = typeface
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (contentText.isEmptyOrNullOrNullString()) {
 | 
				
			||||||
 | 
					                getContentFromMercury(customTabsIntent, prefs)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                binding.titleView.text = contentTitle
 | 
				
			||||||
 | 
					                if (typeface != null) {
 | 
				
			||||||
 | 
					                    binding.titleView.typeface = typeface
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                htmlToWebview()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
 | 
				
			||||||
 | 
					                    binding.imageView.visibility = View.VISIBLE
 | 
				
			||||||
 | 
					                    Glide
 | 
				
			||||||
 | 
					                        .with(requireContext())
 | 
				
			||||||
 | 
					                        .asBitmap()
 | 
				
			||||||
 | 
					                        .loadMaybeBasicAuth(config, contentImage)
 | 
				
			||||||
 | 
					                        .apply(RequestOptions.fitCenterTransform())
 | 
				
			||||||
 | 
					                        .into(binding.imageView)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    binding.imageView.visibility = View.GONE
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rootView.source.text = contentSource
 | 
					            binding.nestedScrollView.setOnScrollChangeListener(
 | 
				
			||||||
 | 
					                NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
 | 
				
			||||||
 | 
					                    if (scrollY > oldScrollY) {
 | 
				
			||||||
 | 
					                        floatingToolbar.hide()
 | 
				
			||||||
 | 
					                        fab.hide()
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        if (staticBar) {
 | 
				
			||||||
 | 
					                            floatingToolbar.show()
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (contentText.isEmptyOrNullOrNullString()) {
 | 
					        } catch (e: InflateException) {
 | 
				
			||||||
            getContentFromMercury(customTabsIntent, prefs)
 | 
					            AlertDialog.Builder(requireContext())
 | 
				
			||||||
        } else {
 | 
					                .setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
 | 
				
			||||||
            rootView.titleView.text = contentTitle
 | 
					                .setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
 | 
				
			||||||
 | 
					                .setPositiveButton(android.R.string.ok
 | 
				
			||||||
            htmlToWebview(contentText, prefs)
 | 
					                ) { dialog, which ->
 | 
				
			||||||
 | 
					                    val sharedPref = PreferenceManager.getDefaultSharedPreferences(requireContext())
 | 
				
			||||||
            if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
 | 
					                    val editor = sharedPref.edit()
 | 
				
			||||||
                rootView.imageView.visibility = View.VISIBLE
 | 
					                    editor.putBoolean("prefer_article_viewer", false)
 | 
				
			||||||
                Glide
 | 
					                    editor.commit()
 | 
				
			||||||
                    .with(context!!)
 | 
					                    requireActivity().finish()
 | 
				
			||||||
                    .asBitmap()
 | 
					                }
 | 
				
			||||||
                    .load(contentImage)
 | 
					                .create()
 | 
				
			||||||
                    .apply(RequestOptions.fitCenterTransform())
 | 
					                .show()
 | 
				
			||||||
                    .into(rootView.imageView)
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                rootView.imageView.visibility = View.GONE
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rootView.nestedScrollView.setOnScrollChangeListener(
 | 
					        return binding.root
 | 
				
			||||||
            NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
 | 
					    }
 | 
				
			||||||
                if (scrollY > oldScrollY) {
 | 
					 | 
				
			||||||
                    fab.hide()
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return rootView
 | 
					    override fun onDestroyView() {
 | 
				
			||||||
 | 
					        super.onDestroyView()
 | 
				
			||||||
 | 
					        _binding = null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun refreshAlignment() {
 | 
				
			||||||
 | 
					        textAlignment = when (prefs.getInt("text_align", 1)) {
 | 
				
			||||||
 | 
					            1 -> "justify"
 | 
				
			||||||
 | 
					            2 -> "left"
 | 
				
			||||||
 | 
					            else -> "justify"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun getContentFromMercury(
 | 
					    private fun getContentFromMercury(
 | 
				
			||||||
        customTabsIntent: CustomTabsIntent,
 | 
					        customTabsIntent: CustomTabsIntent,
 | 
				
			||||||
        prefs: SharedPreferences
 | 
					        prefs: SharedPreferences
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        rootView.progressBar.visibility = View.VISIBLE
 | 
					        if ((context != null && requireContext().isNetworkAccessible(null)) || context == null) {
 | 
				
			||||||
        val parser = MercuryApi(
 | 
					            binding.progressBar.visibility = View.VISIBLE
 | 
				
			||||||
            prefs.getBoolean("should_log_everything", false)
 | 
					            val parser = MercuryApi()
 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        parser.parseUrl(url).enqueue(
 | 
					            parser.parseUrl(url).enqueue(
 | 
				
			||||||
            object : Callback<ParsedContent> {
 | 
					                object : Callback<ParsedContent> {
 | 
				
			||||||
                override fun onResponse(
 | 
					                    override fun onResponse(
 | 
				
			||||||
                    call: Call<ParsedContent>,
 | 
					                        call: Call<ParsedContent>,
 | 
				
			||||||
                    response: Response<ParsedContent>
 | 
					                        response: Response<ParsedContent>
 | 
				
			||||||
                ) {
 | 
					                    ) {
 | 
				
			||||||
                    // TODO: clean all the following after finding the mercury content issue
 | 
					                        // TODO: clean all the following after finding the mercury content issue
 | 
				
			||||||
                    try {
 | 
					                        try {
 | 
				
			||||||
                        if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) {
 | 
					                            if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) {
 | 
				
			||||||
                            try {
 | 
					 | 
				
			||||||
                                rootView.titleView.text = response.body()!!.title
 | 
					 | 
				
			||||||
                                try {
 | 
					                                try {
 | 
				
			||||||
                                    // Note: Mercury may return relative urls... If it does the url val will not be changed.
 | 
					                                    binding.titleView.text = response.body()!!.title
 | 
				
			||||||
                                    URL(response.body()!!.url)
 | 
					                                    if (typeface != null) {
 | 
				
			||||||
                                    url = response.body()!!.url
 | 
					                                        binding.titleView.typeface = typeface
 | 
				
			||||||
                                } catch (e: MalformedURLException) {
 | 
					 | 
				
			||||||
                                    ACRA.getErrorReporter().maybeHandleSilentException(e, activity!!)
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            } catch (e: Exception) {
 | 
					 | 
				
			||||||
                                if (context != null) {
 | 
					 | 
				
			||||||
                                    ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            try {
 | 
					 | 
				
			||||||
                                htmlToWebview(response.body()!!.content.orEmpty(), prefs)
 | 
					 | 
				
			||||||
                            } catch (e: Exception) {
 | 
					 | 
				
			||||||
                                if (context != null) {
 | 
					 | 
				
			||||||
                                    ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            try {
 | 
					 | 
				
			||||||
                                if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) {
 | 
					 | 
				
			||||||
                                    rootView.imageView.visibility = View.VISIBLE
 | 
					 | 
				
			||||||
                                    try {
 | 
					 | 
				
			||||||
                                        Glide
 | 
					 | 
				
			||||||
                                            .with(context!!)
 | 
					 | 
				
			||||||
                                            .asBitmap()
 | 
					 | 
				
			||||||
                                            .load(response.body()!!.lead_image_url)
 | 
					 | 
				
			||||||
                                            .apply(RequestOptions.fitCenterTransform())
 | 
					 | 
				
			||||||
                                            .into(rootView.imageView)
 | 
					 | 
				
			||||||
                                    } catch (e: Exception) {
 | 
					 | 
				
			||||||
                                        ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
 | 
					 | 
				
			||||||
                                    }
 | 
					                                    }
 | 
				
			||||||
                                } else {
 | 
					                                    try {
 | 
				
			||||||
                                    rootView.imageView.visibility = View.GONE
 | 
					                                        // Note: Mercury may return relative urls... If it does the url val will not be changed.
 | 
				
			||||||
 | 
					                                        URL(response.body()!!.url)
 | 
				
			||||||
 | 
					                                        url = response.body()!!.url
 | 
				
			||||||
 | 
					                                    } catch (e: MalformedURLException) {
 | 
				
			||||||
 | 
					                                        // Mercury returned a relative url. We do nothing.
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                } catch (e: Exception) {
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            } catch (e: Exception) {
 | 
					 | 
				
			||||||
                                if (context != null) {
 | 
					 | 
				
			||||||
                                    ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            try {
 | 
					                                try {
 | 
				
			||||||
                                rootView.nestedScrollView.scrollTo(0, 0)
 | 
					                                    contentText = response.body()!!.content.orEmpty()
 | 
				
			||||||
 | 
					                                    htmlToWebview()
 | 
				
			||||||
 | 
					                                } catch (e: Exception) {
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                rootView.progressBar.visibility = View.GONE
 | 
					                                try {
 | 
				
			||||||
                            } catch (e: Exception) {
 | 
					                                    if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) {
 | 
				
			||||||
                                if (context != null) {
 | 
					                                        binding.imageView.visibility = View.VISIBLE
 | 
				
			||||||
                                    ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
 | 
					                                        try {
 | 
				
			||||||
 | 
					                                            Glide
 | 
				
			||||||
 | 
					                                                .with(requireContext())
 | 
				
			||||||
 | 
					                                                .asBitmap()
 | 
				
			||||||
 | 
					                                                .loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty())
 | 
				
			||||||
 | 
					                                                .apply(RequestOptions.fitCenterTransform())
 | 
				
			||||||
 | 
					                                                .into(binding.imageView)
 | 
				
			||||||
 | 
					                                        } catch (e: Exception) {
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    } else {
 | 
				
			||||||
 | 
					                                        binding.imageView.visibility = View.GONE
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                } catch (e: Exception) {
 | 
				
			||||||
 | 
					                                    if (context != null) {
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                try {
 | 
				
			||||||
 | 
					                                    binding.nestedScrollView.scrollTo(0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    binding.progressBar.visibility = View.GONE
 | 
				
			||||||
 | 
					                                } catch (e: Exception) {
 | 
				
			||||||
 | 
					                                    if (context != null) {
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                try {
 | 
				
			||||||
 | 
					                                    openInBrowserAfterFailing(customTabsIntent)
 | 
				
			||||||
 | 
					                                } catch (e: Exception) {
 | 
				
			||||||
 | 
					                                    if (context != null) {
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        } else {
 | 
					                        } catch (e: Exception) {
 | 
				
			||||||
                            try {
 | 
					                            if (context != null) {
 | 
				
			||||||
                                openInBrowserAfterFailing(customTabsIntent)
 | 
					 | 
				
			||||||
                            } catch (e: Exception) {
 | 
					 | 
				
			||||||
                                if (context != null) {
 | 
					 | 
				
			||||||
                                    ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    } catch (e: Exception) {
 | 
					 | 
				
			||||||
                        if (context != null) {
 | 
					 | 
				
			||||||
                            ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                override fun onFailure(
 | 
					                    override fun onFailure(
 | 
				
			||||||
                    call: Call<ParsedContent>,
 | 
					                        call: Call<ParsedContent>,
 | 
				
			||||||
                    t: Throwable
 | 
					                        t: Throwable
 | 
				
			||||||
                ) = openInBrowserAfterFailing(customTabsIntent)
 | 
					                    ) = openInBrowserAfterFailing(customTabsIntent)
 | 
				
			||||||
            }
 | 
					                }
 | 
				
			||||||
        )
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun htmlToWebview(c: String, prefs: SharedPreferences) {
 | 
					    private fun htmlToWebview() {
 | 
				
			||||||
        val stringColor = String.format("#%06X", 0xFFFFFF and appColors.colorAccent)
 | 
					        val stringColor = String.format("#%06X", 0xFFFFFF and appColors.colorAccent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rootView.webcontent.visibility = View.VISIBLE
 | 
					        val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
 | 
				
			||||||
 | 
					        val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        binding.webcontent.settings.standardFontFamily = a.getString(0)
 | 
				
			||||||
 | 
					        binding.webcontent.visibility = View.VISIBLE
 | 
				
			||||||
        val (textColor, backgroundColor) = if (appColors.isDarkTheme) {
 | 
					        val (textColor, backgroundColor) = if (appColors.isDarkTheme) {
 | 
				
			||||||
            if (context != null) {
 | 
					            if (context != null) {
 | 
				
			||||||
                rootView.webcontent.setBackgroundColor(
 | 
					                binding.webcontent.setBackgroundColor(
 | 
				
			||||||
                    ContextCompat.getColor(
 | 
					                    ContextCompat.getColor(
 | 
				
			||||||
                        context!!,
 | 
					                        requireContext(),
 | 
				
			||||||
                        R.color.dark_webview
 | 
					                        R.color.dark_webview
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                Pair(ContextCompat.getColor(context!!, R.color.dark_webview_text), ContextCompat.getColor(context!!, R.color.light_webview_text))
 | 
					                Pair(ContextCompat.getColor(requireContext(), R.color.dark_webview_text), ContextCompat.getColor(requireContext(), R.color.dark_webview))
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                Pair(null, null)
 | 
					                Pair(null, null)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            if (context != null) {
 | 
					            if (context != null) {
 | 
				
			||||||
                rootView.webcontent.setBackgroundColor(
 | 
					                binding.webcontent.setBackgroundColor(
 | 
				
			||||||
                    ContextCompat.getColor(
 | 
					                    ContextCompat.getColor(
 | 
				
			||||||
                        context!!,
 | 
					                        requireContext(),
 | 
				
			||||||
                        R.color.light_webview
 | 
					                        R.color.light_webview
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                Pair(ContextCompat.getColor(context!!, R.color.light_webview_text), ContextCompat.getColor(context!!, R.color.dark_webview_text))
 | 
					                Pair(ContextCompat.getColor(requireContext(), R.color.light_webview_text), ContextCompat.getColor(requireContext(), R.color.light_webview))
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                Pair(null, null)
 | 
					                Pair(null, null)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -346,30 +428,85 @@ class ArticleFragment : Fragment() {
 | 
				
			|||||||
            "#FFFFFF"
 | 
					            "#FFFFFF"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rootView.webcontent.settings.useWideViewPort = true
 | 
					        binding.webcontent.settings.useWideViewPort = true
 | 
				
			||||||
        rootView.webcontent.settings.loadWithOverviewMode = true
 | 
					        binding.webcontent.settings.loadWithOverviewMode = true
 | 
				
			||||||
        rootView.webcontent.settings.javaScriptEnabled = false
 | 
					        binding.webcontent.settings.javaScriptEnabled = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
 | 
					        binding.webcontent.webViewClient = object : WebViewClient() {
 | 
				
			||||||
            rootView.webcontent.settings.layoutAlgorithm =
 | 
					            override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
 | 
				
			||||||
                    WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
 | 
					                if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
 | 
				
			||||||
        } else {
 | 
					                    requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
 | 
				
			||||||
            rootView.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN
 | 
					                }
 | 
				
			||||||
 | 
					                return true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? {
 | 
				
			||||||
 | 
					                val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
 | 
				
			||||||
 | 
					                if (url.toLowerCase().contains(".jpg") || url.toLowerCase().contains(".jpeg")) {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
				
			||||||
 | 
					                        return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
 | 
				
			||||||
 | 
					                    }catch ( e : ExecutionException) {}
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (url.toLowerCase().contains(".png")) {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
				
			||||||
 | 
					                        return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
 | 
				
			||||||
 | 
					                    }catch ( e : ExecutionException) {}
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (url.toLowerCase().contains(".webp")) {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
				
			||||||
 | 
					                        return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
 | 
				
			||||||
 | 
					                    }catch ( e : ExecutionException) {}
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return super.shouldInterceptRequest(view, url)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
 | 
				
			||||||
 | 
					            override fun onSingleTapUp(e: MotionEvent?): Boolean {
 | 
				
			||||||
 | 
					                return performClick()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        binding.webcontent.settings.layoutAlgorithm =
 | 
				
			||||||
 | 
					                WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var baseUrl: String? = null
 | 
					        var baseUrl: String? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            val itemUrl = URL(url)
 | 
					            val itemUrl = URL(url)
 | 
				
			||||||
            baseUrl = itemUrl.protocol + "://" + itemUrl.host
 | 
					            baseUrl = itemUrl.protocol + "://" + itemUrl.host
 | 
				
			||||||
        } catch (e: MalformedURLException) {
 | 
					        } catch (e: MalformedURLException) {
 | 
				
			||||||
            ACRA.getErrorReporter().maybeHandleSilentException(e, activity!!)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rootView.webcontent.loadDataWithBaseURL(
 | 
					        val fontName =  when (font) {
 | 
				
			||||||
 | 
					            getString(R.string.open_sans_font_id) -> "Open Sans"
 | 
				
			||||||
 | 
					            getString(R.string.roboto_font_id) -> "Roboto"
 | 
				
			||||||
 | 
					            else -> ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val fontLinkAndStyle = if (font.isNotEmpty()) {
 | 
				
			||||||
 | 
					            """<link href="https://fonts.googleapis.com/css?family=${fontName.replace(" ", "+")}" rel="stylesheet">
 | 
				
			||||||
 | 
					                |<style>
 | 
				
			||||||
 | 
					                |   * {
 | 
				
			||||||
 | 
					                |       font-family: '$fontName';
 | 
				
			||||||
 | 
					                |   }
 | 
				
			||||||
 | 
					                |</style>
 | 
				
			||||||
 | 
					            """.trimMargin()
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        binding.webcontent.loadDataWithBaseURL(
 | 
				
			||||||
            baseUrl,
 | 
					            baseUrl,
 | 
				
			||||||
            """<html>
 | 
					            """<html>
 | 
				
			||||||
                |<head>
 | 
					                |<head>
 | 
				
			||||||
 | 
					                |   <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
                |   <style>
 | 
					                |   <style>
 | 
				
			||||||
                |      img {
 | 
					                |      img {
 | 
				
			||||||
                |        display: inline-block;
 | 
					                |        display: inline-block;
 | 
				
			||||||
@@ -384,13 +521,21 @@ class ArticleFragment : Fragment() {
 | 
				
			|||||||
                |        color: $stringTextColor;
 | 
					                |        color: $stringTextColor;
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |      * {
 | 
					                |      * {
 | 
				
			||||||
                |        font-size: ${fontSize.toPx}px;
 | 
					                |        font-size: ${fontSize}px;
 | 
				
			||||||
                |        text-align: justify;
 | 
					                |        text-align: $textAlignment;
 | 
				
			||||||
                |        word-break: break-word;
 | 
					                |        word-break: break-word;
 | 
				
			||||||
                |        overflow:hidden;
 | 
					                |        overflow:hidden;
 | 
				
			||||||
 | 
					                |        line-height: 1.5em;
 | 
				
			||||||
 | 
					                |        background-color: $stringBackgroundColor;
 | 
				
			||||||
 | 
					                |      }
 | 
				
			||||||
 | 
					                |      body, html {
 | 
				
			||||||
 | 
					                |        background-color: $stringBackgroundColor !important;
 | 
				
			||||||
 | 
					                |        border-color: $stringBackgroundColor  !important;
 | 
				
			||||||
 | 
					                |        padding: 0 !important;
 | 
				
			||||||
 | 
					                |        margin: 0 !important;
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |      a, pre, code {
 | 
					                |      a, pre, code {
 | 
				
			||||||
                |        text-align: left;
 | 
					                |        text-align: $textAlignment;
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |      pre, code {
 | 
					                |      pre, code {
 | 
				
			||||||
                |        white-space: pre-wrap;
 | 
					                |        white-space: pre-wrap;
 | 
				
			||||||
@@ -398,9 +543,10 @@ class ArticleFragment : Fragment() {
 | 
				
			|||||||
                |        background-color: $stringBackgroundColor;
 | 
					                |        background-color: $stringBackgroundColor;
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |   </style>
 | 
					                |   </style>
 | 
				
			||||||
 | 
					                |   $fontLinkAndStyle
 | 
				
			||||||
                |</head>
 | 
					                |</head>
 | 
				
			||||||
                |<body>
 | 
					                |<body>
 | 
				
			||||||
                |   $c
 | 
					                |   $contentText
 | 
				
			||||||
                |</body>""".trimMargin(),
 | 
					                |</body>""".trimMargin(),
 | 
				
			||||||
            "text/html",
 | 
					            "text/html",
 | 
				
			||||||
            "utf-8",
 | 
					            "utf-8",
 | 
				
			||||||
@@ -409,15 +555,15 @@ class ArticleFragment : Fragment() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) {
 | 
					    private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) {
 | 
				
			||||||
        rootView.progressBar.visibility = View.GONE
 | 
					        binding.progressBar.visibility = View.GONE
 | 
				
			||||||
        activity!!.openItemUrl(
 | 
					        requireActivity().openItemUrl(
 | 
				
			||||||
            allItems,
 | 
					            allItems,
 | 
				
			||||||
            pageNumber.toInt(),
 | 
					            pageNumber.toInt(),
 | 
				
			||||||
            url,
 | 
					            url,
 | 
				
			||||||
            customTabsIntent,
 | 
					            customTabsIntent,
 | 
				
			||||||
            true,
 | 
					            true,
 | 
				
			||||||
            false,
 | 
					            false,
 | 
				
			||||||
            activity!!
 | 
					            requireActivity()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -438,5 +584,20 @@ class ArticleFragment : Fragment() {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun performClick(): Boolean {
 | 
				
			||||||
 | 
					        if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
 | 
				
			||||||
 | 
					                binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val position : Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val intent = Intent(activity, ImageActivity::class.java)
 | 
				
			||||||
 | 
					            intent.putExtra("allImages", allImages)
 | 
				
			||||||
 | 
					            intent.putExtra("position", position)
 | 
				
			||||||
 | 
					            startActivity(intent)
 | 
				
			||||||
 | 
					            return false
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.fragments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.os.Bundle
 | 
				
			||||||
 | 
					import android.view.*
 | 
				
			||||||
 | 
					import androidx.fragment.app.Fragment
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.databinding.FragmentImageBinding
 | 
				
			||||||
 | 
					import com.bumptech.glide.Glide
 | 
				
			||||||
 | 
					import com.bumptech.glide.load.engine.DiskCacheStrategy
 | 
				
			||||||
 | 
					import com.bumptech.glide.request.RequestOptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ImageFragment : Fragment() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lateinit var imageUrl : String
 | 
				
			||||||
 | 
					    private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
 | 
				
			||||||
 | 
					    private var _binding: FragmentImageBinding? = null
 | 
				
			||||||
 | 
					    private val binding get() = _binding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        imageUrl = requireArguments().getString("imageUrl")!!
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
 | 
				
			||||||
 | 
					        _binding = FragmentImageBinding.inflate(inflater, container, false)
 | 
				
			||||||
 | 
					        val view = binding?.root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        binding!!.photoView.visibility = View.VISIBLE
 | 
				
			||||||
 | 
					        Glide.with(activity)
 | 
				
			||||||
 | 
					                .asBitmap()
 | 
				
			||||||
 | 
					                .apply(glideOptions)
 | 
				
			||||||
 | 
					                .load(imageUrl)
 | 
				
			||||||
 | 
					                .into(binding!!.photoView)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return view
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onDestroyView() {
 | 
				
			||||||
 | 
					        super.onDestroyView()
 | 
				
			||||||
 | 
					        _binding = null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        private const val ARG_IMAGE = "imageUrl"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fun newInstance(
 | 
				
			||||||
 | 
					                imageUrl : String
 | 
				
			||||||
 | 
					        ): ImageFragment {
 | 
				
			||||||
 | 
					            val fragment = ImageFragment()
 | 
				
			||||||
 | 
					            val args = Bundle()
 | 
				
			||||||
 | 
					            args.putString(ARG_IMAGE, imageUrl)
 | 
				
			||||||
 | 
					            fragment.arguments = args
 | 
				
			||||||
 | 
					            return fragment
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.persistence.dao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.room.Dao
 | 
				
			||||||
 | 
					import androidx.room.Delete
 | 
				
			||||||
 | 
					import androidx.room.Insert
 | 
				
			||||||
 | 
					import androidx.room.OnConflictStrategy
 | 
				
			||||||
 | 
					import androidx.room.Query
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Dao
 | 
				
			||||||
 | 
					interface ActionsDao {
 | 
				
			||||||
 | 
					    @Query("SELECT * FROM actions order by id asc")
 | 
				
			||||||
 | 
					    suspend fun actions(): List<ActionEntity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
				
			||||||
 | 
					    fun insertAllActions(vararg actions: ActionEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Query("DELETE FROM actions WHERE articleid = :article_id AND read = 1")
 | 
				
			||||||
 | 
					    fun deleteReadActionForArticle(article_id: String)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Delete
 | 
				
			||||||
 | 
					    fun delete(action: ActionEntity)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.persistence.dao
 | 
					package apps.amine.bou.readerforselfoss.persistence.dao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import androidx.room.Dao
 | 
					import androidx.room.Dao
 | 
				
			||||||
 | 
					import androidx.room.Delete
 | 
				
			||||||
import androidx.room.Insert
 | 
					import androidx.room.Insert
 | 
				
			||||||
import androidx.room.OnConflictStrategy
 | 
					import androidx.room.OnConflictStrategy
 | 
				
			||||||
import androidx.room.Query
 | 
					import androidx.room.Query
 | 
				
			||||||
@@ -11,15 +12,18 @@ import androidx.room.Update
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Dao
 | 
					@Dao
 | 
				
			||||||
interface ItemsDao {
 | 
					interface ItemsDao {
 | 
				
			||||||
    @Query("SELECT * FROM items")
 | 
					    @Query("SELECT * FROM items order by id desc")
 | 
				
			||||||
    fun items(): List<ItemEntity>
 | 
					    suspend fun items(): List<ItemEntity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
					    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
				
			||||||
    fun insertAllItems(vararg tags: ItemEntity)
 | 
					    suspend fun insertAllItems(vararg items: ItemEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Query("DELETE FROM items")
 | 
					    @Query("DELETE FROM items")
 | 
				
			||||||
    fun deleteAllItems()
 | 
					    suspend fun deleteAllItems()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Delete
 | 
				
			||||||
 | 
					    suspend fun delete(item: ItemEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Update
 | 
					    @Update
 | 
				
			||||||
    fun updateItem(item: ItemEntity)
 | 
					    suspend fun updateItem(item: ItemEntity)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -2,15 +2,19 @@ package apps.amine.bou.readerforselfoss.persistence.database
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import androidx.room.RoomDatabase
 | 
					import androidx.room.RoomDatabase
 | 
				
			||||||
import androidx.room.Database
 | 
					import androidx.room.Database
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.dao.ActionsDao
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.persistence.dao.DrawerDataDao
 | 
					import apps.amine.bou.readerforselfoss.persistence.dao.DrawerDataDao
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.persistence.dao.ItemsDao
 | 
					import apps.amine.bou.readerforselfoss.persistence.dao.ItemsDao
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Database(entities = [TagEntity::class, SourceEntity::class, ItemEntity::class], version = 2)
 | 
					@Database(entities = [TagEntity::class, SourceEntity::class, ItemEntity::class, ActionEntity::class], version = 4)
 | 
				
			||||||
abstract class AppDatabase : RoomDatabase() {
 | 
					abstract class AppDatabase : RoomDatabase() {
 | 
				
			||||||
    abstract fun drawerDataDao(): DrawerDataDao
 | 
					    abstract fun drawerDataDao(): DrawerDataDao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    abstract fun itemsDao(): ItemsDao
 | 
					    abstract fun itemsDao(): ItemsDao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    abstract fun actionsDao(): ActionsDao
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.persistence.entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.room.ColumnInfo
 | 
				
			||||||
 | 
					import androidx.room.Entity
 | 
				
			||||||
 | 
					import androidx.room.PrimaryKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Entity(tableName = "actions")
 | 
				
			||||||
 | 
					data class ActionEntity(
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "articleid")
 | 
				
			||||||
 | 
					    val articleId: String,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "read")
 | 
				
			||||||
 | 
					    val read: Boolean,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "unread")
 | 
				
			||||||
 | 
					    val unread: Boolean,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "starred")
 | 
				
			||||||
 | 
					    var starred: Boolean,
 | 
				
			||||||
 | 
					    @ColumnInfo(name = "unstarred")
 | 
				
			||||||
 | 
					    var unstarred: Boolean
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    @PrimaryKey(autoGenerate = true)
 | 
				
			||||||
 | 
					    var id: Int = 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -20,9 +20,9 @@ data class ItemEntity(
 | 
				
			|||||||
    @ColumnInfo(name = "starred")
 | 
					    @ColumnInfo(name = "starred")
 | 
				
			||||||
    var starred: Boolean,
 | 
					    var starred: Boolean,
 | 
				
			||||||
    @ColumnInfo(name = "thumbnail")
 | 
					    @ColumnInfo(name = "thumbnail")
 | 
				
			||||||
    val thumbnail: String,
 | 
					    val thumbnail: String?,
 | 
				
			||||||
    @ColumnInfo(name = "icon")
 | 
					    @ColumnInfo(name = "icon")
 | 
				
			||||||
    val icon: String,
 | 
					    val icon: String?,
 | 
				
			||||||
    @ColumnInfo(name = "link")
 | 
					    @ColumnInfo(name = "link")
 | 
				
			||||||
    val link: String,
 | 
					    val link: String,
 | 
				
			||||||
    @ColumnInfo(name = "sourcetitle")
 | 
					    @ColumnInfo(name = "sourcetitle")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,3 +8,27 @@ val MIGRATION_1_2: Migration = object : Migration(1, 2) {
 | 
				
			|||||||
        database.execSQL("CREATE TABLE IF NOT EXISTS `items` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))")
 | 
					        database.execSQL("CREATE TABLE IF NOT EXISTS `items` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					val MIGRATION_2_3: Migration = object : Migration(2, 3) {
 | 
				
			||||||
 | 
					    override fun migrate(database: SupportSQLiteDatabase) {
 | 
				
			||||||
 | 
					        database.execSQL("CREATE TABLE IF NOT EXISTS `actions` (`id` INTEGER NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL, PRIMARY KEY(`id`))")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					val MIGRATION_3_4: Migration = object : Migration(3, 4) {
 | 
				
			||||||
 | 
					    override fun migrate(database: SupportSQLiteDatabase) {
 | 
				
			||||||
 | 
					        // @see https://stackoverflow.com/questions/57392015/how-to-migrate-not-null-table-column-into-null-in-android-room-database
 | 
				
			||||||
 | 
					        // Create the new table
 | 
				
			||||||
 | 
					        database.execSQL("CREATE TABLE IF NOT EXISTS `itemstmp` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT, `icon` TEXT, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Copy the data
 | 
				
			||||||
 | 
					        database.execSQL(
 | 
				
			||||||
 | 
					                "INSERT INTO itemstmp (`id`, `datetime`, `title`, `content`, `unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`, `tags`) SELECT `id`, `datetime`, `title`, `content`, `unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`, `tags` FROM items")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Remove the old table
 | 
				
			||||||
 | 
					        database.execSQL("DROP TABLE items")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Change the table name to the correct one
 | 
				
			||||||
 | 
					        database.execSQL("ALTER TABLE itemstmp RENAME TO items")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,142 +0,0 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.settings;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.content.res.Configuration;
 | 
					 | 
				
			||||||
import android.os.Build;
 | 
					 | 
				
			||||||
import android.os.Bundle;
 | 
					 | 
				
			||||||
import android.preference.PreferenceActivity;
 | 
					 | 
				
			||||||
import androidx.annotation.LayoutRes;
 | 
					 | 
				
			||||||
import androidx.annotation.NonNull;
 | 
					 | 
				
			||||||
import androidx.annotation.Nullable;
 | 
					 | 
				
			||||||
import com.google.android.material.appbar.AppBarLayout;
 | 
					 | 
				
			||||||
import androidx.appcompat.app.ActionBar;
 | 
					 | 
				
			||||||
import androidx.appcompat.app.AppCompatDelegate;
 | 
					 | 
				
			||||||
import androidx.appcompat.widget.Toolbar;
 | 
					 | 
				
			||||||
import android.view.LayoutInflater;
 | 
					 | 
				
			||||||
import android.view.MenuInflater;
 | 
					 | 
				
			||||||
import android.view.View;
 | 
					 | 
				
			||||||
import android.view.ViewGroup;
 | 
					 | 
				
			||||||
import android.widget.LinearLayout;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.ftinc.scoop.Scoop;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R;
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors;
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.Toppings;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A {@link PreferenceActivity} which implements and proxies the necessary calls
 | 
					 | 
				
			||||||
 * to be used with AppCompat.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private AppCompatDelegate mDelegate;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
					 | 
				
			||||||
        new AppColors(this);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        getDelegate().installViewFactory();
 | 
					 | 
				
			||||||
        getDelegate().onCreate(savedInstanceState);
 | 
					 | 
				
			||||||
        super.onCreate(savedInstanceState);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void onPostCreate(Bundle savedInstanceState) {
 | 
					 | 
				
			||||||
        super.onPostCreate(savedInstanceState);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent();
 | 
					 | 
				
			||||||
        AppBarLayout bar = (AppBarLayout) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
 | 
					 | 
				
			||||||
        Toolbar toolbar = bar.findViewById(R.id.toolbar);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Scoop scoop = Scoop.getInstance();
 | 
					 | 
				
			||||||
        scoop.bind(this, Toppings.PRIMARY.getValue(), toolbar);
 | 
					 | 
				
			||||||
        if  (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 | 
					 | 
				
			||||||
            scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.getValue());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        setSupportActionBar(toolbar);
 | 
					 | 
				
			||||||
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
 | 
					 | 
				
			||||||
        getSupportActionBar().setDisplayShowHomeEnabled(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        root.addView(bar, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        getDelegate().onPostCreate(savedInstanceState);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ActionBar getSupportActionBar() {
 | 
					 | 
				
			||||||
        return getDelegate().getSupportActionBar();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void setSupportActionBar(@Nullable Toolbar toolbar) {
 | 
					 | 
				
			||||||
        getDelegate().setSupportActionBar(toolbar);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @NonNull
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public MenuInflater getMenuInflater() {
 | 
					 | 
				
			||||||
        return getDelegate().getMenuInflater();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void setContentView(@LayoutRes int layoutResID) {
 | 
					 | 
				
			||||||
        getDelegate().setContentView(layoutResID);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void setContentView(View view) {
 | 
					 | 
				
			||||||
        getDelegate().setContentView(view);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void setContentView(View view, ViewGroup.LayoutParams params) {
 | 
					 | 
				
			||||||
        getDelegate().setContentView(view, params);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void addContentView(View view, ViewGroup.LayoutParams params) {
 | 
					 | 
				
			||||||
        getDelegate().addContentView(view, params);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void onPostResume() {
 | 
					 | 
				
			||||||
        super.onPostResume();
 | 
					 | 
				
			||||||
        getDelegate().onPostResume();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void onTitleChanged(CharSequence title, int color) {
 | 
					 | 
				
			||||||
        super.onTitleChanged(title, color);
 | 
					 | 
				
			||||||
        getDelegate().setTitle(title);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void onConfigurationChanged(Configuration newConfig) {
 | 
					 | 
				
			||||||
        super.onConfigurationChanged(newConfig);
 | 
					 | 
				
			||||||
        getDelegate().onConfigurationChanged(newConfig);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void onStop() {
 | 
					 | 
				
			||||||
        super.onStop();
 | 
					 | 
				
			||||||
        getDelegate().onStop();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void onDestroy() {
 | 
					 | 
				
			||||||
        super.onDestroy();
 | 
					 | 
				
			||||||
        getDelegate().onDestroy();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void invalidateOptionsMenu() {
 | 
					 | 
				
			||||||
        getDelegate().invalidateOptionsMenu();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private AppCompatDelegate getDelegate() {
 | 
					 | 
				
			||||||
        if (mDelegate == null) {
 | 
					 | 
				
			||||||
            mDelegate = AppCompatDelegate.create(this, null);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return mDelegate;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,397 +0,0 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.settings;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.annotation.TargetApi;
 | 
					 | 
				
			||||||
import android.content.ClipData;
 | 
					 | 
				
			||||||
import android.content.ClipboardManager;
 | 
					 | 
				
			||||||
import android.content.Context;
 | 
					 | 
				
			||||||
import android.content.Intent;
 | 
					 | 
				
			||||||
import android.content.SharedPreferences;
 | 
					 | 
				
			||||||
import android.content.res.Configuration;
 | 
					 | 
				
			||||||
import android.net.Uri;
 | 
					 | 
				
			||||||
import android.os.Build;
 | 
					 | 
				
			||||||
import android.os.Bundle;
 | 
					 | 
				
			||||||
import android.preference.EditTextPreference;
 | 
					 | 
				
			||||||
import android.preference.Preference;
 | 
					 | 
				
			||||||
import android.preference.Preference.OnPreferenceChangeListener;
 | 
					 | 
				
			||||||
import android.preference.Preference.OnPreferenceClickListener;
 | 
					 | 
				
			||||||
import android.preference.PreferenceActivity;
 | 
					 | 
				
			||||||
import android.preference.PreferenceFragment;
 | 
					 | 
				
			||||||
import android.preference.PreferenceManager;
 | 
					 | 
				
			||||||
import android.preference.SwitchPreference;
 | 
					 | 
				
			||||||
import androidx.appcompat.app.ActionBar;
 | 
					 | 
				
			||||||
import android.text.Editable;
 | 
					 | 
				
			||||||
import android.text.InputFilter;
 | 
					 | 
				
			||||||
import android.text.Spanned;
 | 
					 | 
				
			||||||
import android.text.TextWatcher;
 | 
					 | 
				
			||||||
import android.view.Menu;
 | 
					 | 
				
			||||||
import android.view.MenuInflater;
 | 
					 | 
				
			||||||
import android.view.MenuItem;
 | 
					 | 
				
			||||||
import android.widget.Toast;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R;
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.themes.AppColors;
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.Config;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A {@link PreferenceActivity} that presents a set of application settings. On
 | 
					 | 
				
			||||||
 * handset devices, settings are presented as a single list. On tablets,
 | 
					 | 
				
			||||||
 * settings are split by category, with category headers shown to the left of
 | 
					 | 
				
			||||||
 * the list of settings.
 | 
					 | 
				
			||||||
 * <p>
 | 
					 | 
				
			||||||
 * See <a href="http://developer.android.com/design/patterns/settings.html">
 | 
					 | 
				
			||||||
 * Android Design: Settings</a> for design guidelines and the <a
 | 
					 | 
				
			||||||
 * href="http://developer.android.com/guide/topics/ui/settings.html">Settings
 | 
					 | 
				
			||||||
 * API Guide</a> for more information on developing a Settings UI.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class SettingsActivity extends AppCompatPreferenceActivity {
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * A preference value change listener that updates the preference's summary
 | 
					 | 
				
			||||||
     * to reflect its new value.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public boolean onPreferenceChange(Preference preference, Object value) {
 | 
					 | 
				
			||||||
            String stringValue = value.toString();
 | 
					 | 
				
			||||||
            preference.setSummary(stringValue);
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Helper method to determine if the device has an extra-large screen. For
 | 
					 | 
				
			||||||
     * example, 10" tablets are extra-large.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static boolean isXLargeTablet(Context context) {
 | 
					 | 
				
			||||||
        return (context.getResources().getConfiguration().screenLayout
 | 
					 | 
				
			||||||
                & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Binds a preference's summary to its value. More specifically, when the
 | 
					 | 
				
			||||||
     * preference's value is changed, its summary (line of text below the
 | 
					 | 
				
			||||||
     * preference title) is updated to reflect the value. The summary is also
 | 
					 | 
				
			||||||
     * immediately updated upon calling this method. The exact display format is
 | 
					 | 
				
			||||||
     * dependent on the type of preference.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @see #sBindPreferenceSummaryToValueListener
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static void bindPreferenceSummaryToValue(Preference preference) {
 | 
					 | 
				
			||||||
        // Set the listener to watch for value changes.
 | 
					 | 
				
			||||||
        preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Trigger the listener immediately with the preference's
 | 
					 | 
				
			||||||
        // current value.
 | 
					 | 
				
			||||||
        sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
 | 
					 | 
				
			||||||
                PreferenceManager
 | 
					 | 
				
			||||||
                        .getDefaultSharedPreferences(preference.getContext())
 | 
					 | 
				
			||||||
                        .getString(preference.getKey(), ""));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
					 | 
				
			||||||
        new AppColors(this);
 | 
					 | 
				
			||||||
        super.onCreate(savedInstanceState);
 | 
					 | 
				
			||||||
        setupActionBar();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Set up the {@link android.app.ActionBar}, if the API is available.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void setupActionBar() {
 | 
					 | 
				
			||||||
        ActionBar actionBar = getSupportActionBar();
 | 
					 | 
				
			||||||
        if (actionBar != null) {
 | 
					 | 
				
			||||||
            // Show the Up button in the action bar.
 | 
					 | 
				
			||||||
            actionBar.setDisplayHomeAsUpEnabled(true);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * {@inheritDoc}
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public boolean onIsMultiPane() {
 | 
					 | 
				
			||||||
        return isXLargeTablet(this);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * {@inheritDoc}
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 | 
					 | 
				
			||||||
    public void onBuildHeaders(List<Header> target) {
 | 
					 | 
				
			||||||
        loadHeadersFromResource(R.xml.pref_headers, target);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * This method stops fragment injection in malicious applications.
 | 
					 | 
				
			||||||
     * Make sure to deny any unknown fragments here.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected boolean isValidFragment(String fragmentName) {
 | 
					 | 
				
			||||||
        return PreferenceFragment.class.getName().equals(fragmentName)
 | 
					 | 
				
			||||||
                || GeneralPreferenceFragment.class.getName().equals(fragmentName)
 | 
					 | 
				
			||||||
                || ArticleViewerPreferenceFragment.class.getName().equals(fragmentName)
 | 
					 | 
				
			||||||
                || OfflinePreferenceFragment.class.getName().equals(fragmentName)
 | 
					 | 
				
			||||||
                || DebugPreferenceFragment.class.getName().equals(fragmentName)
 | 
					 | 
				
			||||||
                || LinksPreferenceFragment.class.getName().equals(fragmentName)
 | 
					 | 
				
			||||||
                || ThemePreferenceFragment.class.getName().equals(fragmentName);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * This fragment shows general preferences only. It is used when the
 | 
					 | 
				
			||||||
     * activity is showing a two-pane settings UI.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 | 
					 | 
				
			||||||
    public static class GeneralPreferenceFragment extends PreferenceFragment {
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public void onCreate(Bundle savedInstanceState) {
 | 
					 | 
				
			||||||
            super.onCreate(savedInstanceState);
 | 
					 | 
				
			||||||
            addPreferencesFromResource(R.xml.pref_general);
 | 
					 | 
				
			||||||
            setHasOptionsMenu(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            EditTextPreference itemsNumber = (EditTextPreference) findPreference("prefer_api_items_number");
 | 
					 | 
				
			||||||
            itemsNumber.getEditText().setFilters(new InputFilter[]{
 | 
					 | 
				
			||||||
                    new InputFilter() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        @Override
 | 
					 | 
				
			||||||
                        public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
 | 
					 | 
				
			||||||
                            try {
 | 
					 | 
				
			||||||
                                int input = Integer.parseInt(dest.toString() + source.toString());
 | 
					 | 
				
			||||||
                                if (input <= 200 && input > 0)
 | 
					 | 
				
			||||||
                                    return null;
 | 
					 | 
				
			||||||
                            } catch (NumberFormatException nfe) {
 | 
					 | 
				
			||||||
                                Toast.makeText(getActivity(), R.string.items_number_should_be_number, Toast.LENGTH_LONG).show();
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            return "";
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public boolean onOptionsItemSelected(MenuItem item) {
 | 
					 | 
				
			||||||
            int id = item.getItemId();
 | 
					 | 
				
			||||||
            if (id == android.R.id.home) {
 | 
					 | 
				
			||||||
                getActivity().finish();
 | 
					 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return super.onOptionsItemSelected(item);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 | 
					 | 
				
			||||||
    public static class ArticleViewerPreferenceFragment extends PreferenceFragment {
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public void onCreate(Bundle savedInstanceState) {
 | 
					 | 
				
			||||||
            super.onCreate(savedInstanceState);
 | 
					 | 
				
			||||||
            addPreferencesFromResource(R.xml.pref_viewer);
 | 
					 | 
				
			||||||
            setHasOptionsMenu(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            final EditTextPreference fontSize = (EditTextPreference) findPreference("reader_font_size");
 | 
					 | 
				
			||||||
            fontSize.getEditText().addTextChangedListener(new TextWatcher() {
 | 
					 | 
				
			||||||
                @Override
 | 
					 | 
				
			||||||
                public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                @Override
 | 
					 | 
				
			||||||
                public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                @Override
 | 
					 | 
				
			||||||
                public void afterTextChanged(Editable editable) {
 | 
					 | 
				
			||||||
                    try {
 | 
					 | 
				
			||||||
                        fontSize.getEditText().setTextSize(Integer.parseInt(editable.toString()));
 | 
					 | 
				
			||||||
                    } catch (NumberFormatException e) {}
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            fontSize.getEditText().setFilters(new InputFilter[]{
 | 
					 | 
				
			||||||
                    new InputFilter() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        @Override
 | 
					 | 
				
			||||||
                        public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
 | 
					 | 
				
			||||||
                            try {
 | 
					 | 
				
			||||||
                                int input = Integer.parseInt(dest.toString() + source.toString());
 | 
					 | 
				
			||||||
                                if (input > 0)
 | 
					 | 
				
			||||||
                                    return null;
 | 
					 | 
				
			||||||
                            } catch (NumberFormatException nfe) {}
 | 
					 | 
				
			||||||
                            return "";
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public boolean onOptionsItemSelected(MenuItem item) {
 | 
					 | 
				
			||||||
            int id = item.getItemId();
 | 
					 | 
				
			||||||
            if (id == android.R.id.home) {
 | 
					 | 
				
			||||||
                getActivity().finish();
 | 
					 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return super.onOptionsItemSelected(item);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 | 
					 | 
				
			||||||
    public static class DebugPreferenceFragment extends PreferenceFragment {
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public void onCreate(Bundle savedInstanceState) {
 | 
					 | 
				
			||||||
            super.onCreate(savedInstanceState);
 | 
					 | 
				
			||||||
            addPreferencesFromResource(R.xml.pref_debug);
 | 
					 | 
				
			||||||
            setHasOptionsMenu(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            SharedPreferences pref = getActivity().getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE);
 | 
					 | 
				
			||||||
            final String id = pref.getString("unique_id", "...");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            final Preference identifier = findPreference("debug_identifier");
 | 
					 | 
				
			||||||
            final ClipboardManager clipboard = (ClipboardManager)
 | 
					 | 
				
			||||||
                    getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            identifier.setOnPreferenceClickListener(new OnPreferenceClickListener() {
 | 
					 | 
				
			||||||
                @Override
 | 
					 | 
				
			||||||
                public boolean onPreferenceClick(Preference preference) {
 | 
					 | 
				
			||||||
                    if (clipboard != null) {
 | 
					 | 
				
			||||||
                        ClipData clip = ClipData.newPlainText("Selfoss unique id", id);
 | 
					 | 
				
			||||||
                        clipboard.setPrimaryClip(clip);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        Toast.makeText(getActivity(), R.string.unique_id_to_clipboard, Toast.LENGTH_LONG).show();
 | 
					 | 
				
			||||||
                        return true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    return false;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            identifier.setTitle(id);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public boolean onOptionsItemSelected(MenuItem item) {
 | 
					 | 
				
			||||||
            int id = item.getItemId();
 | 
					 | 
				
			||||||
            if (id == android.R.id.home) {
 | 
					 | 
				
			||||||
                getActivity().finish();
 | 
					 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return super.onOptionsItemSelected(item);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * This fragment shows general preferences only. It is used when the
 | 
					 | 
				
			||||||
     * activity is showing a two-pane settings UI.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 | 
					 | 
				
			||||||
    public static class LinksPreferenceFragment extends PreferenceFragment {
 | 
					 | 
				
			||||||
        public void openUrl(Uri uri) {
 | 
					 | 
				
			||||||
            Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri);
 | 
					 | 
				
			||||||
            startActivity(browserIntent);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public void onCreate(Bundle savedInstanceState) {
 | 
					 | 
				
			||||||
            super.onCreate(savedInstanceState);
 | 
					 | 
				
			||||||
            addPreferencesFromResource(R.xml.pref_links);
 | 
					 | 
				
			||||||
            setHasOptionsMenu(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            findPreference("trackerLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
 | 
					 | 
				
			||||||
                @Override
 | 
					 | 
				
			||||||
                public boolean onPreferenceClick(Preference preference) {
 | 
					 | 
				
			||||||
                    openUrl(Uri.parse(Config.trackerUrl));
 | 
					 | 
				
			||||||
                    return true;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
 | 
					 | 
				
			||||||
                @Override
 | 
					 | 
				
			||||||
                public boolean onPreferenceClick(Preference preference) {
 | 
					 | 
				
			||||||
                    openUrl(Uri.parse(Config.sourceUrl));
 | 
					 | 
				
			||||||
                    return false;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            findPreference("translation").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
 | 
					 | 
				
			||||||
                @Override
 | 
					 | 
				
			||||||
                public boolean onPreferenceClick(Preference preference) {
 | 
					 | 
				
			||||||
                    openUrl(Uri.parse(Config.translationUrl));
 | 
					 | 
				
			||||||
                    return false;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public boolean onOptionsItemSelected(MenuItem item) {
 | 
					 | 
				
			||||||
            int id = item.getItemId();
 | 
					 | 
				
			||||||
            if (id == android.R.id.home) {
 | 
					 | 
				
			||||||
                getActivity().finish();
 | 
					 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return super.onOptionsItemSelected(item);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 | 
					 | 
				
			||||||
    public static class ThemePreferenceFragment extends PreferenceFragment {
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public void onCreate(Bundle savedInstanceState) {
 | 
					 | 
				
			||||||
            super.onCreate(savedInstanceState);
 | 
					 | 
				
			||||||
            addPreferencesFromResource(R.xml.pref_theme);
 | 
					 | 
				
			||||||
            setHasOptionsMenu(true);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public boolean onOptionsItemSelected(MenuItem item) {
 | 
					 | 
				
			||||||
            int id = item.getItemId();
 | 
					 | 
				
			||||||
            if (id == android.R.id.home) {
 | 
					 | 
				
			||||||
                getActivity().finish();
 | 
					 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            } else if (id == R.id.clear) {
 | 
					 | 
				
			||||||
                SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getActivity());
 | 
					 | 
				
			||||||
                SharedPreferences.Editor editor = pref.edit();
 | 
					 | 
				
			||||||
                editor.remove("color_primary");
 | 
					 | 
				
			||||||
                editor.remove("color_primary_dark");
 | 
					 | 
				
			||||||
                editor.remove("color_accent");
 | 
					 | 
				
			||||||
                editor.remove("color_accent_dark");
 | 
					 | 
				
			||||||
                editor.remove("dark_theme");
 | 
					 | 
				
			||||||
                editor.apply();
 | 
					 | 
				
			||||||
                getActivity().finish();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return super.onOptionsItemSelected(item);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
 | 
					 | 
				
			||||||
            inflater.inflate(R.menu.settings_theme, menu);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 | 
					 | 
				
			||||||
    public static class OfflinePreferenceFragment extends PreferenceFragment {
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public void onCreate(Bundle savedInstanceState) {
 | 
					 | 
				
			||||||
            super.onCreate(savedInstanceState);
 | 
					 | 
				
			||||||
            addPreferencesFromResource(R.xml.pref_offline);
 | 
					 | 
				
			||||||
            setHasOptionsMenu(true);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @Override
 | 
					 | 
				
			||||||
        public boolean onOptionsItemSelected(MenuItem item) {
 | 
					 | 
				
			||||||
            int id = item.getItemId();
 | 
					 | 
				
			||||||
            if (id == android.R.id.home) {
 | 
					 | 
				
			||||||
                getActivity().finish();
 | 
					 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return super.onOptionsItemSelected(item);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public boolean onOptionsItemSelected(MenuItem item) {
 | 
					 | 
				
			||||||
        int id = item.getItemId();
 | 
					 | 
				
			||||||
        if (id == android.R.id.home) {
 | 
					 | 
				
			||||||
            finish();
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return super.onOptionsItemSelected(item);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -0,0 +1,212 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Intent
 | 
				
			||||||
 | 
					import android.net.Uri
 | 
				
			||||||
 | 
					import android.os.Bundle
 | 
				
			||||||
 | 
					import android.text.*
 | 
				
			||||||
 | 
					import androidx.preference.EditTextPreference
 | 
				
			||||||
 | 
					import androidx.preference.PreferenceManager
 | 
				
			||||||
 | 
					import android.view.*
 | 
				
			||||||
 | 
					import android.widget.Toast
 | 
				
			||||||
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
 | 
					import androidx.core.widget.addTextChangedListener
 | 
				
			||||||
 | 
					import androidx.preference.Preference
 | 
				
			||||||
 | 
					import androidx.preference.PreferenceFragmentCompat
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
 | 
					import java.lang.NumberFormatException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private const val TITLE_TAG = "settingsActivityTitle"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SettingsActivity : AppCompatActivity(),
 | 
				
			||||||
 | 
					        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
 | 
					        if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("dark_theme", false)) {
 | 
				
			||||||
 | 
					            setTheme(R.style.NoBarDark)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        setContentView(R.layout.activity_settings)
 | 
				
			||||||
 | 
					        if (savedInstanceState == null) {
 | 
				
			||||||
 | 
					            supportFragmentManager
 | 
				
			||||||
 | 
					                    .beginTransaction()
 | 
				
			||||||
 | 
					                    .replace(R.id.settings, MainPreferenceFragment())
 | 
				
			||||||
 | 
					                    .commit()
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            title = savedInstanceState.getCharSequence(TITLE_TAG)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        supportFragmentManager.addOnBackStackChangedListener {
 | 
				
			||||||
 | 
					            if (supportFragmentManager.backStackEntryCount == 0) {
 | 
				
			||||||
 | 
					                setTitle(R.string.title_activity_settings)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setSupportActionBar(findViewById(R.id.toolbar))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
				
			||||||
 | 
					        supportActionBar?.setDisplayShowHomeEnabled(true)
 | 
				
			||||||
 | 
					        supportActionBar?.title = title
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onSaveInstanceState(outState: Bundle) {
 | 
				
			||||||
 | 
					        super.onSaveInstanceState(outState)
 | 
				
			||||||
 | 
					        // Save current activity title so we can set it again after a configuration change
 | 
				
			||||||
 | 
					        outState.putCharSequence(TITLE_TAG, title)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onSupportNavigateUp(): Boolean {
 | 
				
			||||||
 | 
					        if (supportFragmentManager.popBackStackImmediate()) {
 | 
				
			||||||
 | 
					            supportActionBar?.title = getText(R.string.title_activity_settings)
 | 
				
			||||||
 | 
					            return true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return super.onSupportNavigateUp()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onPreferenceStartFragment(
 | 
				
			||||||
 | 
					            caller: PreferenceFragmentCompat,
 | 
				
			||||||
 | 
					            pref: Preference
 | 
				
			||||||
 | 
					    ): Boolean {
 | 
				
			||||||
 | 
					        // Instantiate the new Fragment
 | 
				
			||||||
 | 
					        val args = pref.extras
 | 
				
			||||||
 | 
					        val fragment = supportFragmentManager.fragmentFactory.instantiate(
 | 
				
			||||||
 | 
					                classLoader,
 | 
				
			||||||
 | 
					                pref.fragment
 | 
				
			||||||
 | 
					        ).apply {
 | 
				
			||||||
 | 
					            arguments = args
 | 
				
			||||||
 | 
					            setTargetFragment(caller, 0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // Replace the existing Fragment with the new Fragment
 | 
				
			||||||
 | 
					        supportFragmentManager.beginTransaction()
 | 
				
			||||||
 | 
					                .replace(R.id.settings, fragment)
 | 
				
			||||||
 | 
					                .addToBackStack(null)
 | 
				
			||||||
 | 
					                .commit()
 | 
				
			||||||
 | 
					        title = pref.title
 | 
				
			||||||
 | 
					        supportActionBar?.title = title
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class MainPreferenceFragment : PreferenceFragmentCompat() {
 | 
				
			||||||
 | 
					        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
 | 
				
			||||||
 | 
					            setPreferencesFromResource(R.xml.pref_main, rootKey)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class GeneralPreferenceFragment : PreferenceFragmentCompat() {
 | 
				
			||||||
 | 
					        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
 | 
				
			||||||
 | 
					            setPreferencesFromResource(R.xml.pref_general, rootKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val editTextPreference = preferenceManager.findPreference<EditTextPreference>("prefer_api_items_number")
 | 
				
			||||||
 | 
					            editTextPreference?.setOnBindEditTextListener { editText ->
 | 
				
			||||||
 | 
					                editText.inputType = InputType.TYPE_CLASS_NUMBER
 | 
				
			||||||
 | 
					                editText.filters = arrayOf(
 | 
				
			||||||
 | 
					                        InputFilter { source, _, _, dest, _, _ ->
 | 
				
			||||||
 | 
					                            try {
 | 
				
			||||||
 | 
					                                val input: Int = (dest.toString() + source.toString()).toInt()
 | 
				
			||||||
 | 
					                                if (input in 1..200) return@InputFilter null
 | 
				
			||||||
 | 
					                            } catch (nfe: NumberFormatException) {
 | 
				
			||||||
 | 
					                                Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show()
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            ""
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class ArticleViewerPreferenceFragment : PreferenceFragmentCompat() {
 | 
				
			||||||
 | 
					        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
 | 
				
			||||||
 | 
					            setPreferencesFromResource(R.xml.pref_viewer, rootKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val fontSize = preferenceManager.findPreference<EditTextPreference>("reader_font_size")
 | 
				
			||||||
 | 
					            fontSize?.setOnBindEditTextListener { editText ->
 | 
				
			||||||
 | 
					                editText.inputType = InputType.TYPE_CLASS_NUMBER
 | 
				
			||||||
 | 
					                editText.addTextChangedListener { object : TextWatcher {
 | 
				
			||||||
 | 
					                    override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
 | 
				
			||||||
 | 
					                    override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
 | 
				
			||||||
 | 
					                    override fun afterTextChanged(editable: Editable) {
 | 
				
			||||||
 | 
					                        try {
 | 
				
			||||||
 | 
					                            editText.textSize = editable.toString().toInt().toFloat()
 | 
				
			||||||
 | 
					                        } catch (e: NumberFormatException) {
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } }
 | 
				
			||||||
 | 
					                editText.filters = arrayOf(
 | 
				
			||||||
 | 
					                        InputFilter { source, _, _, dest, _, _ ->
 | 
				
			||||||
 | 
					                            try {
 | 
				
			||||||
 | 
					                                val input = (dest.toString() + source.toString()).toInt()
 | 
				
			||||||
 | 
					                                if (input > 0) return@InputFilter null
 | 
				
			||||||
 | 
					                            } catch (nfe: NumberFormatException) {
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            ""
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class OfflinePreferenceFragment : PreferenceFragmentCompat() {
 | 
				
			||||||
 | 
					        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
 | 
				
			||||||
 | 
					            setPreferencesFromResource(R.xml.pref_offline, rootKey)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class ThemePreferenceFragment : PreferenceFragmentCompat() {
 | 
				
			||||||
 | 
					        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
 | 
				
			||||||
 | 
					            setPreferencesFromResource(R.xml.pref_theme, rootKey)
 | 
				
			||||||
 | 
					            setHasOptionsMenu(true)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
 | 
				
			||||||
 | 
					            super.onCreateOptionsMenu(menu, inflater)
 | 
				
			||||||
 | 
					            inflater.inflate(R.menu.settings_theme, menu)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
				
			||||||
 | 
					            val id = item.itemId
 | 
				
			||||||
 | 
					            if (id == R.id.clear) {
 | 
				
			||||||
 | 
					                val pref = PreferenceManager.getDefaultSharedPreferences(activity)
 | 
				
			||||||
 | 
					                val editor = pref.edit()
 | 
				
			||||||
 | 
					                editor.remove("color_primary")
 | 
				
			||||||
 | 
					                editor.remove("color_primary_dark")
 | 
				
			||||||
 | 
					                editor.remove("color_accent")
 | 
				
			||||||
 | 
					                editor.remove("color_accent_dark")
 | 
				
			||||||
 | 
					                editor.remove("dark_theme")
 | 
				
			||||||
 | 
					                editor.apply()
 | 
				
			||||||
 | 
					                requireActivity().recreate()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return super.onOptionsItemSelected(item)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class LinksPreferenceFragment : PreferenceFragmentCompat() {
 | 
				
			||||||
 | 
					        private fun openUrl(uri: Uri?) {
 | 
				
			||||||
 | 
					            val browserIntent = Intent(Intent.ACTION_VIEW, uri)
 | 
				
			||||||
 | 
					            startActivity(browserIntent)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
 | 
				
			||||||
 | 
					            setPreferencesFromResource(R.xml.pref_links, rootKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
 | 
				
			||||||
 | 
					                openUrl(Uri.parse(Config.trackerUrl))
 | 
				
			||||||
 | 
					                true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
 | 
				
			||||||
 | 
					                openUrl(Uri.parse(Config.sourceUrl))
 | 
				
			||||||
 | 
					                false
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
 | 
				
			||||||
 | 
					                openUrl(Uri.parse(Config.translationUrl))
 | 
				
			||||||
 | 
					                false
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class ExperimentalPreferenceFragment : PreferenceFragmentCompat() {
 | 
				
			||||||
 | 
					        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
 | 
				
			||||||
 | 
					            setPreferencesFromResource(R.xml.pref_experimental, rootKey)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,7 +2,7 @@ package apps.amine.bou.readerforselfoss.themes
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					import androidx.preference.PreferenceManager
 | 
				
			||||||
import androidx.annotation.ColorInt
 | 
					import androidx.annotation.ColorInt
 | 
				
			||||||
import androidx.appcompat.view.ContextThemeWrapper
 | 
					import androidx.appcompat.view.ContextThemeWrapper
 | 
				
			||||||
import android.util.TypedValue
 | 
					import android.util.TypedValue
 | 
				
			||||||
@@ -18,6 +18,7 @@ class AppColors(a: Activity) {
 | 
				
			|||||||
    @ColorInt val colorAccentDark: Int
 | 
					    @ColorInt val colorAccentDark: Int
 | 
				
			||||||
    @ColorInt val cardBackgroundColor: Int
 | 
					    @ColorInt val cardBackgroundColor: Int
 | 
				
			||||||
    @ColorInt val colorBackground: Int
 | 
					    @ColorInt val colorBackground: Int
 | 
				
			||||||
 | 
					    @ColorInt val textColor: Int
 | 
				
			||||||
    val isDarkTheme: Boolean
 | 
					    val isDarkTheme: Boolean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    init {
 | 
					    init {
 | 
				
			||||||
@@ -57,6 +58,12 @@ class AppColors(a: Activity) {
 | 
				
			|||||||
            android.R.color.background_light
 | 
					            android.R.color.background_light
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        textColor = if (isDarkTheme) {
 | 
				
			||||||
 | 
					            R.color.white
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            R.color.grey_900
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val wrapper = Context::class.java
 | 
					        val wrapper = Context::class.java
 | 
				
			||||||
        val method = wrapper!!.getMethod("getThemeResId")
 | 
					        val method = wrapper!!.getMethod("getThemeResId")
 | 
				
			||||||
        method.isAccessible = true
 | 
					        method.isAccessible = true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.utils
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.content.Context
 | 
					 | 
				
			||||||
import android.preference.PreferenceManager
 | 
					 | 
				
			||||||
import org.acra.ErrorReporter
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fun ErrorReporter.maybeHandleSilentException(throwable: Throwable, ctx: Context) {
 | 
					 | 
				
			||||||
    val sharedPref = PreferenceManager.getDefaultSharedPreferences(ctx)
 | 
					 | 
				
			||||||
    if (sharedPref.getBoolean("acra_should_log", false)) {
 | 
					 | 
				
			||||||
        this.handleSilentException(throwable)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -25,11 +25,12 @@ fun String.toStringUriWithHttp(): String =
 | 
				
			|||||||
        this
 | 
					        this
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun Context.shareLink(itemUrl: String) {
 | 
					fun Context.shareLink(itemUrl: String, itemTitle: String) {
 | 
				
			||||||
    val sendIntent = Intent()
 | 
					    val sendIntent = Intent()
 | 
				
			||||||
    sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
 | 
					    sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
 | 
				
			||||||
    sendIntent.action = Intent.ACTION_SEND
 | 
					    sendIntent.action = Intent.ACTION_SEND
 | 
				
			||||||
    sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
 | 
					    sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
 | 
				
			||||||
 | 
					    sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
 | 
				
			||||||
    sendIntent.type = "text/plain"
 | 
					    sendIntent.type = "text/plain"
 | 
				
			||||||
    startActivity(
 | 
					    startActivity(
 | 
				
			||||||
        Intent.createChooser(
 | 
					        Intent.createChooser(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,19 +11,19 @@ class Config(c: Context) {
 | 
				
			|||||||
    val settings: SharedPreferences = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
 | 
					    val settings: SharedPreferences = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val baseUrl: String
 | 
					    val baseUrl: String
 | 
				
			||||||
        get() = settings.getString("url", "")
 | 
					        get() = settings.getString("url", "")!!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val userLogin: String
 | 
					    val userLogin: String
 | 
				
			||||||
        get() = settings.getString("login", "")
 | 
					        get() = settings.getString("login", "")!!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val userPassword: String
 | 
					    val userPassword: String
 | 
				
			||||||
        get() = settings.getString("password", "")
 | 
					        get() = settings.getString("password", "")!!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val httpUserLogin: String
 | 
					    val httpUserLogin: String
 | 
				
			||||||
        get() = settings.getString("httpUserName", "")
 | 
					        get() = settings.getString("httpUserName", "")!!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val httpUserPassword: String
 | 
					    val httpUserPassword: String
 | 
				
			||||||
        get() = settings.getString("httpPassword", "")
 | 
					        get() = settings.getString("httpPassword", "")!!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
        const val settingsName = "paramsselfoss"
 | 
					        const val settingsName = "paramsselfoss"
 | 
				
			||||||
@@ -36,6 +36,12 @@ class Config(c: Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const val trackerUrl = "https://github.com/aminecmi/ReaderforSelfoss/issues"
 | 
					        const val trackerUrl = "https://github.com/aminecmi/ReaderforSelfoss/issues"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const val syncChannelId = "sync-channel-id"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const val newItemsChannelId = "new-items-channel-id"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var apiVersion = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fun logoutAndRedirect(
 | 
					        fun logoutAndRedirect(
 | 
				
			||||||
            c: Context,
 | 
					            c: Context,
 | 
				
			||||||
            callingActivity: Activity,
 | 
					            callingActivity: Activity,
 | 
				
			||||||
@@ -45,6 +51,7 @@ class Config(c: Context) {
 | 
				
			|||||||
            editor.remove("url")
 | 
					            editor.remove("url")
 | 
				
			||||||
            editor.remove("login")
 | 
					            editor.remove("login")
 | 
				
			||||||
            editor.remove("password")
 | 
					            editor.remove("password")
 | 
				
			||||||
 | 
					            editor.remove("apiVersionMajor")
 | 
				
			||||||
            editor.apply()
 | 
					            editor.apply()
 | 
				
			||||||
            val intent = Intent(c, LoginActivity::class.java)
 | 
					            val intent = Intent(c, LoginActivity::class.java)
 | 
				
			||||||
            if (baseUrlFail) {
 | 
					            if (baseUrlFail) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.text.format.DateUtils
 | 
				
			||||||
 | 
					import java.time.Instant
 | 
				
			||||||
 | 
					import java.time.LocalDateTime
 | 
				
			||||||
 | 
					import java.time.OffsetDateTime
 | 
				
			||||||
 | 
					import java.time.ZoneOffset
 | 
				
			||||||
 | 
					import java.time.format.DateTimeFormatter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun parseDate(dateString: String): Instant {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return if (Config.apiVersion >= 4) {
 | 
				
			||||||
 | 
					        OffsetDateTime.parse(dateString).toInstant()
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(ZoneOffset.UTC)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun parseRelativeDate(dateString: String): String {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val date = parseDate(dateString)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return " " + DateUtils.getRelativeTimeSpanString(
 | 
				
			||||||
 | 
					            date.toEpochMilli(),
 | 
				
			||||||
 | 
					            Instant.now().toEpochMilli(),
 | 
				
			||||||
 | 
					            DateUtils.MINUTE_IN_MILLIS,
 | 
				
			||||||
 | 
					            DateUtils.FORMAT_ABBREV_RELATIVE
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,39 +1,24 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.utils
 | 
					package apps.amine.bou.readerforselfoss.utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.text.format.DateUtils
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
				
			||||||
import org.acra.ACRA
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossTagType
 | 
				
			||||||
import java.text.ParseException
 | 
					 | 
				
			||||||
import java.text.SimpleDateFormat
 | 
					 | 
				
			||||||
import java.util.*
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun String.toTextDrawableString(c: Context): String {
 | 
					fun String.toTextDrawableString(c: Context): String {
 | 
				
			||||||
    val textDrawable = StringBuilder()
 | 
					    val textDrawable = StringBuilder()
 | 
				
			||||||
    for (s in this.split(" ".toRegex()).filter { !it.isEmpty() }.toTypedArray()) {
 | 
					    for (s in this.split(" ".toRegex()).filter { it.isNotEmpty() }.toTypedArray()) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            textDrawable.append(s[0])
 | 
					            textDrawable.append(s[0])
 | 
				
			||||||
        } catch (e: StringIndexOutOfBoundsException) {
 | 
					        } catch (e: StringIndexOutOfBoundsException) {
 | 
				
			||||||
            ACRA.getErrorReporter().maybeHandleSilentException(e, c)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return textDrawable.toString()
 | 
					    return textDrawable.toString()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun Item.sourceAndDateText(): String {
 | 
					fun Item.sourceAndDateText(): String {
 | 
				
			||||||
    val formattedDate: String = try {
 | 
					    val formattedDate = parseRelativeDate(this.datetime)
 | 
				
			||||||
        " " + DateUtils.getRelativeTimeSpanString(
 | 
					 | 
				
			||||||
            SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time,
 | 
					 | 
				
			||||||
            Date().time,
 | 
					 | 
				
			||||||
            DateUtils.MINUTE_IN_MILLIS,
 | 
					 | 
				
			||||||
            DateUtils.FORMAT_ABBREV_RELATIVE
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    } catch (e: ParseException) {
 | 
					 | 
				
			||||||
        e.printStackTrace()
 | 
					 | 
				
			||||||
        ""
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.sourcetitle + formattedDate
 | 
					    return this.getSourceTitle() + formattedDate
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun Item.toggleStar(): Item {
 | 
					fun Item.toggleStar(): Item {
 | 
				
			||||||
@@ -44,8 +29,8 @@ fun Item.toggleStar(): Item {
 | 
				
			|||||||
fun List<Item>.flattenTags(): List<Item> =
 | 
					fun List<Item>.flattenTags(): List<Item> =
 | 
				
			||||||
    this.flatMap {
 | 
					    this.flatMap {
 | 
				
			||||||
        val item = it
 | 
					        val item = it
 | 
				
			||||||
        val tags: List<String> = it.tags.split(",")
 | 
					        val tags: List<String> = it.tags.tags.split(",")
 | 
				
			||||||
        tags.map {
 | 
					        tags.map { t ->
 | 
				
			||||||
            item.copy(tags = it.trim())
 | 
					            item.copy(tags = SelfossTagType(t.trim()))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -2,29 +2,42 @@ package apps.amine.bou.readerforselfoss.utils
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import android.app.PendingIntent
 | 
					import android.app.PendingIntent
 | 
				
			||||||
 | 
					import android.content.ActivityNotFoundException
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.graphics.BitmapFactory
 | 
					import android.graphics.BitmapFactory
 | 
				
			||||||
import android.net.Uri
 | 
					import android.net.Uri
 | 
				
			||||||
 | 
					import android.os.Build
 | 
				
			||||||
 | 
					import android.text.Spannable
 | 
				
			||||||
 | 
					import android.text.style.ClickableSpan
 | 
				
			||||||
import androidx.browser.customtabs.CustomTabsIntent
 | 
					import androidx.browser.customtabs.CustomTabsIntent
 | 
				
			||||||
import android.util.Patterns
 | 
					import android.util.Patterns
 | 
				
			||||||
 | 
					import android.view.MotionEvent
 | 
				
			||||||
 | 
					import android.view.View
 | 
				
			||||||
 | 
					import android.widget.TextView
 | 
				
			||||||
import android.widget.Toast
 | 
					import android.widget.Toast
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.ReaderActivity
 | 
					import apps.amine.bou.readerforselfoss.ReaderActivity
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
 | 
					import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
 | 
				
			||||||
import okhttp3.HttpUrl
 | 
					import okhttp3.HttpUrl
 | 
				
			||||||
 | 
					import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun Context.buildCustomTabsIntent(): CustomTabsIntent {
 | 
					fun Context.buildCustomTabsIntent(): CustomTabsIntent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val actionIntent = Intent(Intent.ACTION_SEND)
 | 
					    val actionIntent = Intent(Intent.ACTION_SEND)
 | 
				
			||||||
    actionIntent.type = "text/plain"
 | 
					    actionIntent.type = "text/plain"
 | 
				
			||||||
    val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(
 | 
					    val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
				
			||||||
        this,
 | 
					        PendingIntent.FLAG_IMMUTABLE
 | 
				
			||||||
        0,
 | 
					    } else {
 | 
				
			||||||
        actionIntent,
 | 
					 | 
				
			||||||
        0
 | 
					        0
 | 
				
			||||||
    )
 | 
					    }
 | 
				
			||||||
 | 
					    val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(
 | 
				
			||||||
 | 
					            this,
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            actionIntent,
 | 
				
			||||||
 | 
					            pflags
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val intentBuilder = CustomTabsIntent.Builder()
 | 
					    val intentBuilder = CustomTabsIntent.Builder()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -68,6 +81,7 @@ fun Context.openItemUrlInternally(
 | 
				
			|||||||
) {
 | 
					) {
 | 
				
			||||||
    if (articleViewer) {
 | 
					    if (articleViewer) {
 | 
				
			||||||
        ReaderActivity.allItems = allItems
 | 
					        ReaderActivity.allItems = allItems
 | 
				
			||||||
 | 
					        SharedItems.position = currentItem
 | 
				
			||||||
        val intent = Intent(this, ReaderActivity::class.java)
 | 
					        val intent = Intent(this, ReaderActivity::class.java)
 | 
				
			||||||
        intent.putExtra("currentItem", currentItem)
 | 
					        intent.putExtra("currentItem", currentItem)
 | 
				
			||||||
        app.startActivity(intent)
 | 
					        app.startActivity(intent)
 | 
				
			||||||
@@ -123,17 +137,21 @@ fun Context.openItemUrl(
 | 
				
			|||||||
private fun openInBrowser(linkDecoded: String, app: Activity) {
 | 
					private fun openInBrowser(linkDecoded: String, app: Activity) {
 | 
				
			||||||
    val intent = Intent(Intent.ACTION_VIEW)
 | 
					    val intent = Intent(Intent.ACTION_VIEW)
 | 
				
			||||||
    intent.data = Uri.parse(linkDecoded)
 | 
					    intent.data = Uri.parse(linkDecoded)
 | 
				
			||||||
    app.startActivity(intent)
 | 
					    try {
 | 
				
			||||||
 | 
					        app.startActivity(intent)
 | 
				
			||||||
 | 
					    } catch (e: ActivityNotFoundException) {
 | 
				
			||||||
 | 
					        Toast.makeText(app.baseContext, e.message, Toast.LENGTH_LONG).show()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun String.isUrlValid(): Boolean =
 | 
					fun String.isUrlValid(): Boolean =
 | 
				
			||||||
    HttpUrl.parse(this) != null && Patterns.WEB_URL.matcher(this).matches()
 | 
					    this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun String.isBaseUrlValid(): Boolean {
 | 
					fun String.isBaseUrlValid(ctx: Context): Boolean {
 | 
				
			||||||
    val baseUrl = HttpUrl.parse(this)
 | 
					    val baseUrl = this.toHttpUrlOrNull()
 | 
				
			||||||
    var existsAndEndsWithSlash = false
 | 
					    var existsAndEndsWithSlash = false
 | 
				
			||||||
    if (baseUrl != null) {
 | 
					    if (baseUrl != null) {
 | 
				
			||||||
        val pathSegments = baseUrl.pathSegments()
 | 
					        val pathSegments = baseUrl.pathSegments
 | 
				
			||||||
        existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1]
 | 
					        existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -146,3 +164,40 @@ fun Context.openInBrowserAsNewTask(i: Item) {
 | 
				
			|||||||
    intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
 | 
					    intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
 | 
				
			||||||
    startActivity(intent)
 | 
					    startActivity(intent)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LinkOnTouchListener: View.OnTouchListener {
 | 
				
			||||||
 | 
					    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
 | 
				
			||||||
 | 
					        var ret = false
 | 
				
			||||||
 | 
					        val widget: TextView = v as TextView
 | 
				
			||||||
 | 
					        val text: CharSequence = widget.text
 | 
				
			||||||
 | 
					        val stext = Spannable.Factory.getInstance().newSpannable(text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val action = event!!.action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (action == MotionEvent.ACTION_UP ||
 | 
				
			||||||
 | 
					            action == MotionEvent.ACTION_DOWN) {
 | 
				
			||||||
 | 
					            var x: Float = event.x
 | 
				
			||||||
 | 
					            var y: Float = event.y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            x -= widget.totalPaddingLeft
 | 
				
			||||||
 | 
					            y -= widget.totalPaddingTop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            x += widget.scrollX
 | 
				
			||||||
 | 
					            y += widget.scrollY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val layout = widget.layout
 | 
				
			||||||
 | 
					            val line = layout.getLineForVertical(y.toInt())
 | 
				
			||||||
 | 
					            val off = layout.getOffsetForHorizontal(line, x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val link = stext.getSpans(off, off, ClickableSpan::class.java)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (link.isNotEmpty()) {
 | 
				
			||||||
 | 
					                if (action == MotionEvent.ACTION_UP) {
 | 
				
			||||||
 | 
					                    link[0].onClick(widget)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                ret = true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return ret
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,404 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.widget.Toast
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.persistence.toEntity
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.persistence.toView
 | 
				
			||||||
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
 | 
					import retrofit2.Call
 | 
				
			||||||
 | 
					import retrofit2.Callback
 | 
				
			||||||
 | 
					import retrofit2.Response
 | 
				
			||||||
 | 
					import java.text.SimpleDateFormat
 | 
				
			||||||
 | 
					import kotlin.concurrent.thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					* Singleton class that contains the articles fetched from Selfoss, it allows sharing the items list
 | 
				
			||||||
 | 
					* between Activities and Fragments
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					object SharedItems {
 | 
				
			||||||
 | 
					    var items: ArrayList<Item> = arrayListOf<Item>()
 | 
				
			||||||
 | 
					        get() {
 | 
				
			||||||
 | 
					            return ArrayList(field)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        set(value) {
 | 
				
			||||||
 | 
					            field = ArrayList(value)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    var focusedItems: ArrayList<Item> = arrayListOf<Item>()
 | 
				
			||||||
 | 
					        get() {
 | 
				
			||||||
 | 
					            return ArrayList(field)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        set(value) {
 | 
				
			||||||
 | 
					            field = ArrayList(value)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    var position = 0
 | 
				
			||||||
 | 
					        set(value) {
 | 
				
			||||||
 | 
					            field = when {
 | 
				
			||||||
 | 
					                value < 0 -> 0
 | 
				
			||||||
 | 
					                value > items.size -> items.size
 | 
				
			||||||
 | 
					                else -> value
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    var displayedItems: String = "unread"
 | 
				
			||||||
 | 
					        set(value) {
 | 
				
			||||||
 | 
					            field = when (value) {
 | 
				
			||||||
 | 
					                "all" -> "all"
 | 
				
			||||||
 | 
					                "unread" -> "unread"
 | 
				
			||||||
 | 
					                "read" -> "read"
 | 
				
			||||||
 | 
					                "starred" -> "starred"
 | 
				
			||||||
 | 
					                else -> "all"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var searchFilter: String? = null
 | 
				
			||||||
 | 
					    var sourceIDFilter: Long? = null
 | 
				
			||||||
 | 
					    var sourceFilter: String? = null
 | 
				
			||||||
 | 
					    var tagFilter: String? = null
 | 
				
			||||||
 | 
					    var itemsCaching = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var fetchedUnread = false
 | 
				
			||||||
 | 
					    var fetchedAll = false
 | 
				
			||||||
 | 
					    var fetchedStarred = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var badgeUnread = -1
 | 
				
			||||||
 | 
					    var badgeAll = -1
 | 
				
			||||||
 | 
					    var badgeStarred = -1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Add new items to the SharedItems list
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * The new items are considered more updated than the ones already in the list.
 | 
				
			||||||
 | 
					     * The old items present in the new list are discarded and replaced by the new ones.
 | 
				
			||||||
 | 
					     * Items are compared according to the selfoss id, which should always be unique.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    fun appendNewItems(newItems: ArrayList<Item>) {
 | 
				
			||||||
 | 
					        var tmpItems = items
 | 
				
			||||||
 | 
					        if (tmpItems != newItems) {
 | 
				
			||||||
 | 
					            tmpItems = tmpItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList<Item>
 | 
				
			||||||
 | 
					            tmpItems.addAll(newItems)
 | 
				
			||||||
 | 
					            items = tmpItems
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            sortItems()
 | 
				
			||||||
 | 
					            getFocusedItems()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun refreshFocusedItems(newItems: ArrayList<Item>) {
 | 
				
			||||||
 | 
					        val tmpItems = items
 | 
				
			||||||
 | 
					        tmpItems.removeAll(focusedItems)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        appendNewItems(newItems)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun clearDBItems(db: AppDatabase) {
 | 
				
			||||||
 | 
					        db.itemsDao().deleteAllItems()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun updateDatabase(db: AppDatabase) {
 | 
				
			||||||
 | 
					        if (itemsCaching) {
 | 
				
			||||||
 | 
					            if (items.isEmpty()) {
 | 
				
			||||||
 | 
					                getFromDB(db)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            db.itemsDao().deleteAllItems()
 | 
				
			||||||
 | 
					            db.itemsDao().insertAllItems(*(items.map { it.toEntity() }).toTypedArray())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun filter() {
 | 
				
			||||||
 | 
					        fun filterSearch(item: Item): Boolean {
 | 
				
			||||||
 | 
					            return if (!searchFilter.isEmptyOrNullOrNullString()) {
 | 
				
			||||||
 | 
					                var matched = item.title.contains(searchFilter.toString(), true)
 | 
				
			||||||
 | 
					                matched = matched || item.content.contains(searchFilter.toString(), true)
 | 
				
			||||||
 | 
					                matched = matched || item.sourcetitle.contains(searchFilter.toString(), true)
 | 
				
			||||||
 | 
					                matched
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var tmpItems = focusedItems
 | 
				
			||||||
 | 
					        if (tagFilter != null) {
 | 
				
			||||||
 | 
					            tmpItems = tmpItems.filter { it.tags.tags.contains(tagFilter.toString()) } as ArrayList<Item>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (searchFilter != null) {
 | 
				
			||||||
 | 
					            tmpItems = tmpItems.filter { filterSearch(it) } as ArrayList<Item>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (sourceFilter != null) {
 | 
				
			||||||
 | 
					            tmpItems = tmpItems.filter { it.sourcetitle == sourceFilter } as ArrayList<Item>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        focusedItems = tmpItems
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getFocusedItems() {
 | 
				
			||||||
 | 
					        when (displayedItems) {
 | 
				
			||||||
 | 
					            "all" -> getAll()
 | 
				
			||||||
 | 
					            "unread" -> getUnRead()
 | 
				
			||||||
 | 
					            "read" -> getRead()
 | 
				
			||||||
 | 
					            "starred" -> getStarred()
 | 
				
			||||||
 | 
					            else -> getUnRead()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getUnRead() {
 | 
				
			||||||
 | 
					        displayedItems = "unread"
 | 
				
			||||||
 | 
					        focusedItems = items.filter { item -> item.unread } as ArrayList<Item>
 | 
				
			||||||
 | 
					        filter()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getRead() {
 | 
				
			||||||
 | 
					        displayedItems = "read"
 | 
				
			||||||
 | 
					        focusedItems = items.filter { item -> !item.unread } as ArrayList<Item>
 | 
				
			||||||
 | 
					        filter()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getStarred() {
 | 
				
			||||||
 | 
					        displayedItems = "starred"
 | 
				
			||||||
 | 
					        focusedItems = items.filter { item -> item.starred } as ArrayList<Item>
 | 
				
			||||||
 | 
					        filter()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAll() {
 | 
				
			||||||
 | 
					        displayedItems = "all"
 | 
				
			||||||
 | 
					        focusedItems = items
 | 
				
			||||||
 | 
					        filter()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun getFromDB(db: AppDatabase) {
 | 
				
			||||||
 | 
					        if (itemsCaching) {
 | 
				
			||||||
 | 
					                    val dbItems = db.itemsDao().items().map { it.toView() } as ArrayList<Item>
 | 
				
			||||||
 | 
					                    appendNewItems(dbItems)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun removeItemAtIndex(index: Int) {
 | 
				
			||||||
 | 
					        val i = focusedItems[index]
 | 
				
			||||||
 | 
					        val tmpItems = focusedItems
 | 
				
			||||||
 | 
					        tmpItems.remove(i)
 | 
				
			||||||
 | 
					        focusedItems = tmpItems
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun addItemAtIndex(newItem: Item, index: Int) {
 | 
				
			||||||
 | 
					        val tmpItems = focusedItems
 | 
				
			||||||
 | 
					        tmpItems.add(index, newItem)
 | 
				
			||||||
 | 
					        focusedItems = tmpItems
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun readItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) {
 | 
				
			||||||
 | 
					        if (items.contains(item)) {
 | 
				
			||||||
 | 
					            position = items.indexOf(item)
 | 
				
			||||||
 | 
					            readItemAtPosition(app, api, db)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun readItems(db: AppDatabase, ids: List<String>) {
 | 
				
			||||||
 | 
					        for (id in ids) {
 | 
				
			||||||
 | 
					            val match = items.filter { it -> it.id == id }
 | 
				
			||||||
 | 
					            if (match.isNotEmpty() && match.size == 1) {
 | 
				
			||||||
 | 
					                position = items.indexOf(match[0])
 | 
				
			||||||
 | 
					                val tmpItems = items
 | 
				
			||||||
 | 
					                tmpItems[position].unread = false
 | 
				
			||||||
 | 
					                items = tmpItems
 | 
				
			||||||
 | 
					                resetDBItem(db)
 | 
				
			||||||
 | 
					                badgeUnread--
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun readItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) {
 | 
				
			||||||
 | 
					        val i = items[position]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (app.isNetworkAccessible(null)) {
 | 
				
			||||||
 | 
					            api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
 | 
					                override fun onResponse(
 | 
				
			||||||
 | 
					                        call: Call<SuccessResponse>,
 | 
				
			||||||
 | 
					                        response: Response<SuccessResponse>
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    val tmpItems = items
 | 
				
			||||||
 | 
					                    tmpItems[position].unread = false
 | 
				
			||||||
 | 
					                    items = tmpItems
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    resetDBItem(db)
 | 
				
			||||||
 | 
					                    getFocusedItems()
 | 
				
			||||||
 | 
					                    badgeUnread--
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
				
			||||||
 | 
					                    Toast.makeText(
 | 
				
			||||||
 | 
					                            app,
 | 
				
			||||||
 | 
					                            app.getString(R.string.cant_mark_read),
 | 
				
			||||||
 | 
					                            Toast.LENGTH_SHORT
 | 
				
			||||||
 | 
					                    ).show()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        } else if (itemsCaching) {
 | 
				
			||||||
 | 
					            thread {
 | 
				
			||||||
 | 
					                db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (position > items.size) {
 | 
				
			||||||
 | 
					            position -= 1
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun unreadItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) {
 | 
				
			||||||
 | 
					        if (items.contains(item) && !item.unread) {
 | 
				
			||||||
 | 
					            position = items.indexOf(item)
 | 
				
			||||||
 | 
					            unreadItemAtPosition(app, api, db)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun unreadItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) {
 | 
				
			||||||
 | 
					        val i = items[position]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (app.isNetworkAccessible(null)) {
 | 
				
			||||||
 | 
					            api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
 | 
					                override fun onResponse(
 | 
				
			||||||
 | 
					                    call: Call<SuccessResponse>,
 | 
				
			||||||
 | 
					                    response: Response<SuccessResponse>
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    val tmpItems = items
 | 
				
			||||||
 | 
					                    tmpItems[position].unread = true
 | 
				
			||||||
 | 
					                    items = tmpItems
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    resetDBItem(db)
 | 
				
			||||||
 | 
					                    getFocusedItems()
 | 
				
			||||||
 | 
					                    badgeUnread++
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
 | 
				
			||||||
 | 
					                    Toast.makeText(
 | 
				
			||||||
 | 
					                        app,
 | 
				
			||||||
 | 
					                        app.getString(R.string.cant_mark_unread),
 | 
				
			||||||
 | 
					                        Toast.LENGTH_SHORT
 | 
				
			||||||
 | 
					                    ).show()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        } else if (itemsCaching) {
 | 
				
			||||||
 | 
					            thread {
 | 
				
			||||||
 | 
					                db.actionsDao().insertAllActions(ActionEntity(i.id, false, true, false, false))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun starItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) {
 | 
				
			||||||
 | 
					        if (items.contains(item) && !item.starred) {
 | 
				
			||||||
 | 
					            position = items.indexOf(item)
 | 
				
			||||||
 | 
					            starItemAtPosition(app, api, db)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun starItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) {
 | 
				
			||||||
 | 
					        val i = items[position]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (app.isNetworkAccessible(null)) {
 | 
				
			||||||
 | 
					            api.starrItem(i.id).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
 | 
					                override fun onResponse(
 | 
				
			||||||
 | 
					                    call: Call<SuccessResponse>,
 | 
				
			||||||
 | 
					                    response: Response<SuccessResponse>
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    val tmpItems = items
 | 
				
			||||||
 | 
					                    tmpItems[position].starred = true
 | 
				
			||||||
 | 
					                    items = tmpItems
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    resetDBItem(db)
 | 
				
			||||||
 | 
					                    getFocusedItems()
 | 
				
			||||||
 | 
					                    badgeStarred++
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                override fun onFailure(
 | 
				
			||||||
 | 
					                    call: Call<SuccessResponse>,
 | 
				
			||||||
 | 
					                    t: Throwable
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    Toast.makeText(
 | 
				
			||||||
 | 
					                        app,
 | 
				
			||||||
 | 
					                        app.getString(R.string.cant_mark_favortie),
 | 
				
			||||||
 | 
					                        Toast.LENGTH_SHORT
 | 
				
			||||||
 | 
					                    ).show()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            thread {
 | 
				
			||||||
 | 
					                db.actionsDao().insertAllActions(ActionEntity(i.id, false, false, true, false))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun unstarItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) {
 | 
				
			||||||
 | 
					        if (items.contains(item) && item.starred) {
 | 
				
			||||||
 | 
					            position = items.indexOf(item)
 | 
				
			||||||
 | 
					            unstarItemAtPosition(app, api, db)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun unstarItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) {
 | 
				
			||||||
 | 
					        val i = items[position]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (app.isNetworkAccessible(null)) {
 | 
				
			||||||
 | 
					            api.unstarrItem(i.id).enqueue(object : Callback<SuccessResponse> {
 | 
				
			||||||
 | 
					                override fun onResponse(
 | 
				
			||||||
 | 
					                    call: Call<SuccessResponse>,
 | 
				
			||||||
 | 
					                    response: Response<SuccessResponse>
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    val tmpItems = items
 | 
				
			||||||
 | 
					                    tmpItems[position].starred = false
 | 
				
			||||||
 | 
					                    items = tmpItems
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    resetDBItem(db)
 | 
				
			||||||
 | 
					                    getFocusedItems()
 | 
				
			||||||
 | 
					                    badgeStarred--
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                override fun onFailure(
 | 
				
			||||||
 | 
					                    call: Call<SuccessResponse>,
 | 
				
			||||||
 | 
					                    t: Throwable
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    Toast.makeText(
 | 
				
			||||||
 | 
					                        app,
 | 
				
			||||||
 | 
					                        app.getString(R.string.cant_unmark_favortie),
 | 
				
			||||||
 | 
					                        Toast.LENGTH_SHORT
 | 
				
			||||||
 | 
					                    ).show()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            thread {
 | 
				
			||||||
 | 
					                db.actionsDao().insertAllActions(ActionEntity(i.id, false, false, false, true))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun resetDBItem(db: AppDatabase) {
 | 
				
			||||||
 | 
					        if (itemsCaching) {
 | 
				
			||||||
 | 
					            val i = items[position]
 | 
				
			||||||
 | 
					            CoroutineScope(Dispatchers.IO).launch {
 | 
				
			||||||
 | 
					                db.itemsDao().delete(i.toEntity())
 | 
				
			||||||
 | 
					                db.itemsDao().insertAllItems(i.toEntity())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun unreadItemStatusAtIndex(position: Int): Boolean {
 | 
				
			||||||
 | 
					        return focusedItems[position].unread
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun computeBadges() {
 | 
				
			||||||
 | 
					        badgeUnread = items.filter { item -> item.unread }.size
 | 
				
			||||||
 | 
					        badgeStarred = items.filter { item -> item.starred }.size
 | 
				
			||||||
 | 
					        badgeAll = items.size
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun sortItems() {
 | 
				
			||||||
 | 
					        val tmpItems = ArrayList(items.sortedByDescending { parseDate(it.datetime) })
 | 
				
			||||||
 | 
					        items = tmpItems
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,112 +0,0 @@
 | 
				
			|||||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomUrlBasePrimaryDrawerItem.java */
 | 
					 | 
				
			||||||
package apps.amine.bou.readerforselfoss.utils.drawer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.net.Uri
 | 
					 | 
				
			||||||
import androidx.annotation.ColorInt
 | 
					 | 
				
			||||||
import androidx.annotation.ColorRes
 | 
					 | 
				
			||||||
import androidx.annotation.StringRes
 | 
					 | 
				
			||||||
import androidx.recyclerview.widget.RecyclerView
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.mikepenz.materialdrawer.holder.ColorHolder
 | 
					 | 
				
			||||||
import com.mikepenz.materialdrawer.holder.ImageHolder
 | 
					 | 
				
			||||||
import com.mikepenz.materialdrawer.holder.StringHolder
 | 
					 | 
				
			||||||
import com.mikepenz.materialdrawer.model.BaseDrawerItem
 | 
					 | 
				
			||||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
 | 
					 | 
				
			||||||
import com.mikepenz.materialdrawer.util.DrawerUIUtils
 | 
					 | 
				
			||||||
import com.mikepenz.materialize.util.UIUtils
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> :
 | 
					 | 
				
			||||||
    BaseDrawerItem<T, VH>() {
 | 
					 | 
				
			||||||
    fun withIcon(url: String): T {
 | 
					 | 
				
			||||||
        this.icon = ImageHolder(url)
 | 
					 | 
				
			||||||
        return this as T
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun withIcon(uri: Uri): T {
 | 
					 | 
				
			||||||
        this.icon = ImageHolder(uri)
 | 
					 | 
				
			||||||
        return this as T
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var description: StringHolder? = null
 | 
					 | 
				
			||||||
        private set
 | 
					 | 
				
			||||||
    var descriptionTextColor: ColorHolder? = null
 | 
					 | 
				
			||||||
        private set
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun withDescription(description: String): T {
 | 
					 | 
				
			||||||
        this.description = StringHolder(description)
 | 
					 | 
				
			||||||
        return this as T
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun withDescription(@StringRes descriptionRes: Int): T {
 | 
					 | 
				
			||||||
        this.description = StringHolder(descriptionRes)
 | 
					 | 
				
			||||||
        return this as T
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun withDescriptionTextColor(@ColorInt color: Int): T {
 | 
					 | 
				
			||||||
        this.descriptionTextColor = ColorHolder.fromColor(color)
 | 
					 | 
				
			||||||
        return this as T
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun withDescriptionTextColorRes(@ColorRes colorRes: Int): T {
 | 
					 | 
				
			||||||
        this.descriptionTextColor = ColorHolder.fromColorRes(colorRes)
 | 
					 | 
				
			||||||
        return this as T
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * a helper method to have the logic for all secondaryDrawerItems only once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
     * @param viewHolder
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected fun bindViewHelper(viewHolder: CustomBaseViewHolder) {
 | 
					 | 
				
			||||||
        val ctx = viewHolder.itemView.context
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //set the identifier from the drawerItem here. It can be used to run tests
 | 
					 | 
				
			||||||
        viewHolder.itemView.id = hashCode()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //set the item selected if it is
 | 
					 | 
				
			||||||
        viewHolder.itemView.isSelected = isSelected
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //get the correct color for the background
 | 
					 | 
				
			||||||
        val selectedColor = getSelectedColor(ctx)
 | 
					 | 
				
			||||||
        //get the correct color for the text
 | 
					 | 
				
			||||||
        val color = getColor(ctx)
 | 
					 | 
				
			||||||
        val selectedTextColor = getSelectedTextColor(ctx)
 | 
					 | 
				
			||||||
        //get the correct color for the icon
 | 
					 | 
				
			||||||
        val iconColor = getIconColor(ctx)
 | 
					 | 
				
			||||||
        val selectedIconColor = getSelectedIconColor(ctx)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //set the background for the item
 | 
					 | 
				
			||||||
        UIUtils.setBackground(
 | 
					 | 
				
			||||||
            viewHolder.view,
 | 
					 | 
				
			||||||
            UIUtils.getSelectableBackground(ctx, selectedColor, true)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        //set the text for the name
 | 
					 | 
				
			||||||
        StringHolder.applyTo(this.getName(), viewHolder.name)
 | 
					 | 
				
			||||||
        //set the text for the description or hide
 | 
					 | 
				
			||||||
        StringHolder.applyToOrHide(this.description, viewHolder.description)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //set the colors for textViews
 | 
					 | 
				
			||||||
        viewHolder.name.setTextColor(getTextColorStateList(color, selectedTextColor))
 | 
					 | 
				
			||||||
        //set the description text color
 | 
					 | 
				
			||||||
        ColorHolder.applyToOr(
 | 
					 | 
				
			||||||
            descriptionTextColor,
 | 
					 | 
				
			||||||
            viewHolder.description,
 | 
					 | 
				
			||||||
            getTextColorStateList(color, selectedTextColor)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //define the typeface for our textViews
 | 
					 | 
				
			||||||
        if (getTypeface() != null) {
 | 
					 | 
				
			||||||
            viewHolder.name.typeface = getTypeface()
 | 
					 | 
				
			||||||
            viewHolder.description.typeface = getTypeface()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //we make sure we reset the image first before setting the new one in case there is an empty one
 | 
					 | 
				
			||||||
        DrawerImageLoader.getInstance().cancelImage(viewHolder.icon)
 | 
					 | 
				
			||||||
        viewHolder.icon.setImageBitmap(null)
 | 
					 | 
				
			||||||
        //get the drawables for our icon and set it
 | 
					 | 
				
			||||||
        ImageHolder.applyTo(icon, viewHolder.icon, "customUrlItem")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //for android API 17 --> Padding not applied via xml
 | 
					 | 
				
			||||||
        DrawerUIUtils.setDrawerVerticalPadding(viewHolder.view)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,94 +0,0 @@
 | 
				
			|||||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomUrlPrimaryDrawerItem.java */
 | 
					 | 
				
			||||||
package apps.amine.bou.readerforselfoss.utils.drawer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import androidx.annotation.LayoutRes
 | 
					 | 
				
			||||||
import androidx.annotation.StringRes
 | 
					 | 
				
			||||||
import android.view.View
 | 
					 | 
				
			||||||
import android.widget.TextView
 | 
					 | 
				
			||||||
import apps.amine.bou.readerforselfoss.R
 | 
					 | 
				
			||||||
import com.mikepenz.materialdrawer.holder.BadgeStyle
 | 
					 | 
				
			||||||
import com.mikepenz.materialdrawer.holder.StringHolder
 | 
					 | 
				
			||||||
import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CustomUrlPrimaryDrawerItem :
 | 
					 | 
				
			||||||
    CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(),
 | 
					 | 
				
			||||||
    ColorfulBadgeable<CustomUrlPrimaryDrawerItem> {
 | 
					 | 
				
			||||||
    protected var mBadge: StringHolder = StringHolder("")
 | 
					 | 
				
			||||||
    protected var mBadgeStyle = BadgeStyle()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun withBadge(badge: StringHolder): CustomUrlPrimaryDrawerItem {
 | 
					 | 
				
			||||||
        this.mBadge = badge
 | 
					 | 
				
			||||||
        return this
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun withBadge(badge: String): CustomUrlPrimaryDrawerItem {
 | 
					 | 
				
			||||||
        this.mBadge = StringHolder(badge)
 | 
					 | 
				
			||||||
        return this
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun withBadge(@StringRes badgeRes: Int): CustomUrlPrimaryDrawerItem {
 | 
					 | 
				
			||||||
        this.mBadge = StringHolder(badgeRes)
 | 
					 | 
				
			||||||
        return this
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun withBadgeStyle(badgeStyle: BadgeStyle): CustomUrlPrimaryDrawerItem {
 | 
					 | 
				
			||||||
        this.mBadgeStyle = badgeStyle
 | 
					 | 
				
			||||||
        return this
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun getBadge(): StringHolder {
 | 
					 | 
				
			||||||
        return mBadge
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun getBadgeStyle(): BadgeStyle {
 | 
					 | 
				
			||||||
        return mBadgeStyle
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun getType(): Int {
 | 
					 | 
				
			||||||
        return R.id.material_drawer_item_custom_url_item
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @LayoutRes
 | 
					 | 
				
			||||||
    override fun getLayoutRes(): Int {
 | 
					 | 
				
			||||||
        return R.layout.material_drawer_item_primary
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun bindView(viewHolder: ViewHolder, payloads: List<*>?) {
 | 
					 | 
				
			||||||
        super.bindView(viewHolder, payloads)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        val ctx = viewHolder.itemView.context
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //bind the basic view parts
 | 
					 | 
				
			||||||
        bindViewHelper(viewHolder)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //set the text for the badge or hide
 | 
					 | 
				
			||||||
        val badgeVisible = StringHolder.applyToOrHide(mBadge, viewHolder.badge)
 | 
					 | 
				
			||||||
        //style the badge if it is visible
 | 
					 | 
				
			||||||
        if (badgeVisible) {
 | 
					 | 
				
			||||||
            mBadgeStyle.style(
 | 
					 | 
				
			||||||
                viewHolder.badge,
 | 
					 | 
				
			||||||
                getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx))
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            viewHolder.badgeContainer.visibility = View.VISIBLE
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            viewHolder.badgeContainer.visibility = View.GONE
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //define the typeface for our textViews
 | 
					 | 
				
			||||||
        if (getTypeface() != null) {
 | 
					 | 
				
			||||||
            viewHolder.badge.typeface = getTypeface()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required)
 | 
					 | 
				
			||||||
        onPostBindView(this, viewHolder.itemView)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun getViewHolder(v: View): ViewHolder {
 | 
					 | 
				
			||||||
        return ViewHolder(v)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class ViewHolder(view: View) : CustomBaseViewHolder(view) {
 | 
					 | 
				
			||||||
        val badgeContainer: View = view.findViewById(R.id.material_drawer_badge_container)
 | 
					 | 
				
			||||||
        val badge: TextView = view.findViewById(R.id.material_drawer_badge)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -2,30 +2,33 @@ package apps.amine.bou.readerforselfoss.utils.glide
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.graphics.Bitmap
 | 
					import android.graphics.Bitmap
 | 
				
			||||||
 | 
					import android.graphics.drawable.Drawable
 | 
				
			||||||
 | 
					import android.util.Base64
 | 
				
			||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
 | 
					import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
 | 
				
			||||||
import android.widget.ImageView
 | 
					import android.widget.ImageView
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.utils.Config
 | 
				
			||||||
import com.bumptech.glide.Glide
 | 
					import com.bumptech.glide.Glide
 | 
				
			||||||
 | 
					import com.bumptech.glide.RequestBuilder
 | 
				
			||||||
 | 
					import com.bumptech.glide.RequestManager
 | 
				
			||||||
 | 
					import com.bumptech.glide.load.model.GlideUrl
 | 
				
			||||||
 | 
					import com.bumptech.glide.load.model.LazyHeaders
 | 
				
			||||||
import com.bumptech.glide.request.RequestOptions
 | 
					import com.bumptech.glide.request.RequestOptions
 | 
				
			||||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
 | 
					import com.bumptech.glide.request.target.BitmapImageViewTarget
 | 
				
			||||||
 | 
					import java.io.ByteArrayInputStream
 | 
				
			||||||
 | 
					import java.io.ByteArrayOutputStream
 | 
				
			||||||
 | 
					import java.io.InputStream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
 | 
					fun Context.bitmapCenterCrop(config: Config, url: String, iv: ImageView) =
 | 
				
			||||||
    Glide.with(this)
 | 
					    Glide.with(this)
 | 
				
			||||||
        .asBitmap()
 | 
					        .asBitmap()
 | 
				
			||||||
        .load(url)
 | 
					        .loadMaybeBasicAuth(config, url)
 | 
				
			||||||
        .apply(RequestOptions.centerCropTransform())
 | 
					        .apply(RequestOptions.centerCropTransform())
 | 
				
			||||||
        .into(iv)
 | 
					        .into(iv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun Context.bitmapFitCenter(url: String, iv: ImageView) =
 | 
					fun Context.circularBitmapDrawable(config: Config, url: String, iv: ImageView) =
 | 
				
			||||||
    Glide.with(this)
 | 
					    Glide.with(this)
 | 
				
			||||||
        .asBitmap()
 | 
					        .asBitmap()
 | 
				
			||||||
        .load(url)
 | 
					        .loadMaybeBasicAuth(config, url)
 | 
				
			||||||
        .apply(RequestOptions.fitCenterTransform())
 | 
					 | 
				
			||||||
        .into(iv)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
 | 
					 | 
				
			||||||
    Glide.with(this)
 | 
					 | 
				
			||||||
        .asBitmap()
 | 
					 | 
				
			||||||
        .load(url)
 | 
					 | 
				
			||||||
        .apply(RequestOptions.centerCropTransform())
 | 
					        .apply(RequestOptions.centerCropTransform())
 | 
				
			||||||
        .into(object : BitmapImageViewTarget(iv) {
 | 
					        .into(object : BitmapImageViewTarget(iv) {
 | 
				
			||||||
            override fun setResource(resource: Bitmap?) {
 | 
					            override fun setResource(resource: Bitmap?) {
 | 
				
			||||||
@@ -37,3 +40,30 @@ fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
 | 
				
			|||||||
                iv.setImageDrawable(circularBitmapDrawable)
 | 
					                iv.setImageDrawable(circularBitmapDrawable)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun RequestBuilder<Bitmap>.loadMaybeBasicAuth(config: Config, url: String): RequestBuilder<Bitmap> {
 | 
				
			||||||
 | 
					    val builder: LazyHeaders.Builder = LazyHeaders.Builder()
 | 
				
			||||||
 | 
					    if (config.httpUserLogin.isNotEmpty() || config.httpUserPassword.isNotEmpty()) {
 | 
				
			||||||
 | 
					        val basicAuth = "Basic " + Base64.encodeToString("${config.httpUserLogin}:${config.httpUserPassword}".toByteArray(), Base64.NO_WRAP)
 | 
				
			||||||
 | 
					        builder.addHeader("Authorization", basicAuth)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    val glideUrl = GlideUrl(url, builder.build())
 | 
				
			||||||
 | 
					    return this.load(glideUrl)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun RequestManager.loadMaybeBasicAuth(config: Config, url: String): RequestBuilder<Drawable> {
 | 
				
			||||||
 | 
					    val builder: LazyHeaders.Builder = LazyHeaders.Builder()
 | 
				
			||||||
 | 
					    if (config.httpUserLogin.isNotEmpty() || config.httpUserPassword.isNotEmpty()) {
 | 
				
			||||||
 | 
					        val basicAuth = "Basic " + Base64.encodeToString("${config.httpUserLogin}:${config.httpUserPassword}".toByteArray(), Base64.NO_WRAP)
 | 
				
			||||||
 | 
					        builder.addHeader("Authorization", basicAuth)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    val glideUrl = GlideUrl(url, builder.build())
 | 
				
			||||||
 | 
					    return this.load(glideUrl)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun getBitmapInputStream(bitmap:Bitmap,compressFormat: Bitmap.CompressFormat): InputStream {
 | 
				
			||||||
 | 
					    val byteArrayOutputStream = ByteArrayOutputStream()
 | 
				
			||||||
 | 
					    bitmap.compress(compressFormat, 80, byteArrayOutputStream)
 | 
				
			||||||
 | 
					    val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
 | 
				
			||||||
 | 
					    return ByteArrayInputStream(bitmapData)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					package apps.amine.bou.readerforselfoss.utils.network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.graphics.Color
 | 
				
			||||||
 | 
					import android.net.ConnectivityManager
 | 
				
			||||||
 | 
					import android.net.NetworkCapabilities
 | 
				
			||||||
 | 
					import android.os.Build
 | 
				
			||||||
 | 
					import android.view.View
 | 
				
			||||||
 | 
					import android.widget.TextView
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.R
 | 
				
			||||||
 | 
					import com.google.android.material.snackbar.Snackbar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var snackBarShown = false
 | 
				
			||||||
 | 
					var view: View? = null
 | 
				
			||||||
 | 
					lateinit var s: Snackbar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun Context.isNetworkAccessible(v: View?, overrideOffline: Boolean = false): Boolean {
 | 
				
			||||||
 | 
					    val networkIsAccessible = isNetworkAvailable(this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (v != null && (!networkIsAccessible || overrideOffline) && (!snackBarShown || v != view)) {
 | 
				
			||||||
 | 
					        view = v
 | 
				
			||||||
 | 
					        s = Snackbar
 | 
				
			||||||
 | 
					            .make(
 | 
				
			||||||
 | 
					                v,
 | 
				
			||||||
 | 
					                R.string.no_network_connectivity,
 | 
				
			||||||
 | 
					                Snackbar.LENGTH_INDEFINITE
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        s.setAction(android.R.string.ok) {
 | 
				
			||||||
 | 
					            snackBarShown = false
 | 
				
			||||||
 | 
					            s.dismiss()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val view = s.view
 | 
				
			||||||
 | 
					        val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
 | 
				
			||||||
 | 
					        tv.setTextColor(Color.WHITE)
 | 
				
			||||||
 | 
					        s.show()
 | 
				
			||||||
 | 
					        snackBarShown = true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (snackBarShown && networkIsAccessible && !overrideOffline) {
 | 
				
			||||||
 | 
					        s.dismiss()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return if(overrideOffline) overrideOffline else networkIsAccessible
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun isNetworkAvailable(context: Context): Boolean {
 | 
				
			||||||
 | 
					    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
				
			||||||
 | 
					         val network = connectivityManager.activeNetwork ?: return false
 | 
				
			||||||
 | 
					         val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					         return when {
 | 
				
			||||||
 | 
					             networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
 | 
				
			||||||
 | 
					             networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
 | 
				
			||||||
 | 
					             networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
 | 
				
			||||||
 | 
					             networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
 | 
				
			||||||
 | 
					             else -> false
 | 
				
			||||||
 | 
					         }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        val network = connectivityManager.activeNetworkInfo ?: return false
 | 
				
			||||||
 | 
					         return network.isConnectedOrConnecting
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package apps.amine.bou.readerforselfoss.utils.persistence
 | 
					package apps.amine.bou.readerforselfoss.utils.persistence
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Item
 | 
				
			||||||
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.SelfossTagType
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Source
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Source
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Tag
 | 
					import apps.amine.bou.readerforselfoss.api.selfoss.Tag
 | 
				
			||||||
import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity
 | 
					import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity
 | 
				
			||||||
@@ -18,7 +19,7 @@ fun SourceEntity.toView(): Source =
 | 
				
			|||||||
        Source(
 | 
					        Source(
 | 
				
			||||||
            this.id,
 | 
					            this.id,
 | 
				
			||||||
            this.title,
 | 
					            this.title,
 | 
				
			||||||
            this.tags,
 | 
					            SelfossTagType(this.tags),
 | 
				
			||||||
            this.spout,
 | 
					            this.spout,
 | 
				
			||||||
            this.error,
 | 
					            this.error,
 | 
				
			||||||
            this.icon
 | 
					            this.icon
 | 
				
			||||||
@@ -27,8 +28,8 @@ fun SourceEntity.toView(): Source =
 | 
				
			|||||||
fun Source.toEntity(): SourceEntity =
 | 
					fun Source.toEntity(): SourceEntity =
 | 
				
			||||||
        SourceEntity(
 | 
					        SourceEntity(
 | 
				
			||||||
            this.id,
 | 
					            this.id,
 | 
				
			||||||
            this.title,
 | 
					            this.getTitleDecoded(),
 | 
				
			||||||
            this.tags,
 | 
					            this.tags.tags,
 | 
				
			||||||
            this.spout,
 | 
					            this.spout,
 | 
				
			||||||
            this.error,
 | 
					            this.error,
 | 
				
			||||||
            this.icon.orEmpty()
 | 
					            this.icon.orEmpty()
 | 
				
			||||||
@@ -53,20 +54,20 @@ fun ItemEntity.toView(): Item =
 | 
				
			|||||||
            this.icon,
 | 
					            this.icon,
 | 
				
			||||||
            this.link,
 | 
					            this.link,
 | 
				
			||||||
            this.sourcetitle,
 | 
					            this.sourcetitle,
 | 
				
			||||||
            this.tags
 | 
					            SelfossTagType(this.tags)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun Item.toEntity(): ItemEntity =
 | 
					fun Item.toEntity(): ItemEntity =
 | 
				
			||||||
    ItemEntity(
 | 
					    ItemEntity(
 | 
				
			||||||
        this.id,
 | 
					        this.id,
 | 
				
			||||||
        this.datetime,
 | 
					        this.datetime,
 | 
				
			||||||
        this.title,
 | 
					        this.getTitleDecoded(),
 | 
				
			||||||
        this.content,
 | 
					        this.content,
 | 
				
			||||||
        this.unread,
 | 
					        this.unread,
 | 
				
			||||||
        this.starred,
 | 
					        this.starred,
 | 
				
			||||||
        this.thumbnail,
 | 
					        this.thumbnail,
 | 
				
			||||||
        this.icon,
 | 
					        this.icon,
 | 
				
			||||||
        this.link,
 | 
					        this.link,
 | 
				
			||||||
        this.sourcetitle,
 | 
					        this.getSourceTitle(),
 | 
				
			||||||
        this.tags
 | 
					        this.tags.tags
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
							
								
								
									
										8
									
								
								app/src/main/res/color/ic_menu_heart_color.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<selector xmlns:android="http://schemas.android.com/apk/res/android">
 | 
				
			||||||
 | 
					    <item android:state_selected="true"
 | 
				
			||||||
 | 
					        android:color="@color/red"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <item android:state_selected="false"
 | 
				
			||||||
 | 
					        android:color="?android:attr/textColorPrimary" />
 | 
				
			||||||
 | 
					</selector>
 | 
				
			||||||
@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
 | 
					        android:width="24dp"
 | 
				
			||||||
 | 
					        android:height="24dp"
 | 
				
			||||||
 | 
					        android:viewportWidth="24.0"
 | 
				
			||||||
 | 
					        android:viewportHeight="24.0">
 | 
				
			||||||
 | 
					    <path
 | 
				
			||||||
 | 
					        android:fillColor="#FFFFFFFF"
 | 
				
			||||||
 | 
					        android:pathData="M3,21h18v-2L3,19v2zM3,17h18v-2L3,15v2zM3,13h18v-2L3,11v2zM3,9h18L21,7L3,7v2zM3,3v2h18L21,3L3,3z"/>
 | 
				
			||||||
 | 
					</vector>
 | 
				
			||||||
@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
 | 
					        android:width="24dp"
 | 
				
			||||||
 | 
					        android:height="24dp"
 | 
				
			||||||
 | 
					        android:viewportWidth="24.0"
 | 
				
			||||||
 | 
					        android:viewportHeight="24.0">
 | 
				
			||||||
 | 
					    <path
 | 
				
			||||||
 | 
					        android:fillColor="#FFFFFFFF"
 | 
				
			||||||
 | 
					        android:pathData="M15,15L3,15v2h12v-2zM15,7L3,7v2h12L15,7zM3,13h18v-2L3,11v2zM3,21h18v-2L3,19v2zM3,3v2h18L21,3L3,3z"/>
 | 
				
			||||||
 | 
					</vector>
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 680 B  | 
| 
		 Before Width: | Height: | Size: 134 B  | 
| 
		 Before Width: | Height: | Size: 239 B  | 
| 
		 Before Width: | Height: | Size: 271 B  | 
| 
		 Before Width: | Height: | Size: 216 B  | 
| 
		 Before Width: | Height: | Size: 206 B  | 
| 
		 Before Width: | Height: | Size: 221 B  | 
| 
		 Before Width: | Height: | Size: 458 B  | 
| 
		 Before Width: | Height: | Size: 275 B  | 
| 
		 Before Width: | Height: | Size: 361 B  | 
| 
		 Before Width: | Height: | Size: 324 B  | 
| 
		 Before Width: | Height: | Size: 301 B  | 
| 
		 Before Width: | Height: | Size: 551 B  | 
| 
		 Before Width: | Height: | Size: 355 B  | 
| 
		 Before Width: | Height: | Size: 551 B  | 
| 
		 Before Width: | Height: | Size: 204 B  | 
| 
		 Before Width: | Height: | Size: 187 B  | 
| 
		 Before Width: | Height: | Size: 422 B  | 
| 
		 Before Width: | Height: | Size: 473 B  | 
| 
		 Before Width: | Height: | Size: 498 B  | 
| 
		 Before Width: | Height: | Size: 453 B  | 
| 
		 Before Width: | Height: | Size: 398 B  | 
| 
		 Before Width: | Height: | Size: 397 B  | 
| 
		 Before Width: | Height: | Size: 523 B  | 
| 
		 Before Width: | Height: | Size: 442 B  | 
| 
		 Before Width: | Height: | Size: 116 B  | 
| 
		 Before Width: | Height: | Size: 174 B  | 
| 
		 Before Width: | Height: | Size: 212 B  | 
| 
		 Before Width: | Height: | Size: 136 B  | 
| 
		 Before Width: | Height: | Size: 134 B  | 
| 
		 Before Width: | Height: | Size: 175 B  | 
| 
		 Before Width: | Height: | Size: 268 B  | 
| 
		 Before Width: | Height: | Size: 213 B  | 
| 
		 Before Width: | Height: | Size: 247 B  | 
| 
		 Before Width: | Height: | Size: 215 B  | 
| 
		 Before Width: | Height: | Size: 208 B  | 
| 
		 Before Width: | Height: | Size: 352 B  | 
| 
		 Before Width: | Height: | Size: 241 B  | 
| 
		 Before Width: | Height: | Size: 355 B  | 
| 
		 Before Width: | Height: | Size: 157 B  | 
| 
		 Before Width: | Height: | Size: 144 B  | 
| 
		 Before Width: | Height: | Size: 276 B  | 
| 
		 Before Width: | Height: | Size: 309 B  | 
| 
		 Before Width: | Height: | Size: 339 B  |