diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 7cd3814..0000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,73 +0,0 @@ -# Introduction - -### Hey you ! - -Thank you for wanting to help. Even the smallest things can help this project become better. - -Please read the guidelines before contributing, and follow them (or try to) when contributing. - -### What you can do to help. - -There are many ways to contribute to this project, you could [translate the app](https://crowdin.com/project/readerforselfoss), report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users. - -You can fork the repository, and [help me solve some issues](https://github.com/aminecmi/ReaderforSelfoss/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://github.com/aminecmi/ReaderforSelfoss/issues) - -### What I can't help you with. - -Please, don't use the issue tracker for anything related to [Selfoss itself](https://github.com/SSilence/selfoss). The app calls the api provided by Selfoss, and can't help with solving issues with your Selfoss instance. - -Always check if the web version of your instance is working. - -# Some rules -### Bug reports/Feature request - -* Always search before reporting an issue or asking for a feature to avoid duplicates. -* Include your unique user id. It's displayed on the debug settings page. (You can tap it, it'll be copied to your clipboard) -* Include every other useful details (app version, phone model, Android version and screenshots when possible). -* Avoid bumping non-fatal issues, or feature requests. I'll try to fix them as soon as possible, and try to prioritize the requests. (You may wan to use the [reactions](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments) for that) - -### Pull requests - -* Don't create a PR for translations. See [here](https://github.com/aminecmi/ReaderforSelfoss/pull/170#issuecomment-355715654) for an explanation why. -* Please ask before starting to work on an issue. I may be working on it, or someone else could be doing so. -* Each pull request should implement **ONE** feature or bugfix. Keep in mind that you can submit as many PR as you want. -* Your code must be simple and clear enough to avoid using comments to explain what it does. -* Follow the used coding style [the android koding style](https://android.github.io/kotlin-guides/style.html) ([some idoms for reference](http://kotlinlang.org/docs/reference/idioms.html)) with more to come. -* Try as much as possible to write a test for your feature, and if you do so, run it, and make it work. -* Always check your changes and discard the ones that are irrelevant to your feature or bugfix. -* Have meaningful commit messages. -* Always reference the issue you are working on in your PR description. -* Be willing to accept criticism on your PRs (as I am on mine). -* 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 - -You can directly import this project into IntellIJ/Android Studio. - -You'll have to: - -- Define some parameters either in `~/.gradle/gradle.properties` or as gradle parameters (see the examples) - - - appLoginUrl, appLoginUsername and appLoginPassword: url, username and password of a selfoss instance. **These are only used for tests. They can be empty if you don't test API calls.** - -### Examples: -#### Inside ~/.gradle/gradle.properties - -``` -appLoginUrl="URL" # It can be empty. -appLoginUsername="LOGIN" # It can be empty. -appLoginPassword="PASS" # It can be empty. -``` - -#### As gradle parameters - -``` -./gradlew .... -P appLoginUrl="URL" -P appLoginUsername="LOGIN" -P appLoginPassword="PASS" -``` diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index a457db0..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,32 +0,0 @@ -### Prerequisites - -* [ ] Are you running the latest version? -* [ ] Did you check for an existing issue ? -* [ ] Are you reporting to the correct repository? -* [ ] Did you perform a cursory search? -* [ ] Did you read the `CONTRIBUTING` guide ? - -### Description - -[Description of the bug or feature] - -### Steps to Reproduce - -1. [First Step] -2. [Second Step] -3. [and so on...] - -**Expected behavior:** [What you expected to happen] - -**Actual behavior:** [What actually happened] - - -### Screenshots (optional) - -`...` - -### Device - -- Device (manufacturer, model ...) -- OS (Android Version, ROM/Stock, Rooted/not, mods...) -- App version _(See Prerequisites)_ \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 76b8ec1..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,14 +0,0 @@ -## Types of changes - -- [ ] I have read the **CONTRIBUTING** document. -- [ ] My code follows the code style of this project. -- [ ] I have updated the documentation accordingly. -- [ ] I have added tests to cover my changes. -- [ ] All new and existing tests passed. -- [ ] This is **NOT** translation related. (See [here](https://github.com/aminecmi/ReaderforSelfoss/pull/170#issuecomment-355715654)) - -This closes issue #XXX - -This is implements feature #YYY - -This finishes chore #ZZZ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index a87b03b..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,562 +0,0 @@ -**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. - -- Closing #397 and #355. Tag and Sources filters are now exclusive. - -- Dropped support for android 4, the last version supporting it is v1721030811 - -- Added ability to scroll articles up and down using the volume keys #400 - -**1.6.x** - -- Handling hidden tags. - -- Fixed pre-lolipop issue with automatic theme changes. - -- Removed all Build config things. - -- Removed firebase and fabric. - -- Added Acra for optional crash reporting and error logging. - -- Dynamic themes ! - -- Strings cleaning. - -- Versions updates. - -- Fixes #215, #208. - -- Fixes #328. - -**1.5.7.x** - -- Added confirmation to the mark as read and update menues. - -- Add to favorites from article viewer. - -- Added an option to use a webview in the article viewer (see #149) - -- Fixes (#151 #152 #155 #157 #160 #174) and more. - -- New year fixes !!! - -- Changed page indicator position as it was overlaping content. - -- Now using slack instead of gitter. - -- Moved completely to a webview to fix #161. - -- Fixed typos in French ( Thanks @aancel ) - -- Updated the Contribution guide about translations. - -- Better handling for articles update. (See #169) - -- Ability to change the article viewer content font size (see #153) - -- Versions updates * 2. - -- Added padding to the recyclerview. - -**1.5.5.x (didn't last long) AND 1.5.6.x** - -- Toolbar in reader activity. - -- Marking items as read on scroll (with settings to enable/disable). - -- Swapped the title and subtitle in the article viewer. - -- Added an animation to the viewpager. - -- Completed Dutch, Indonesian and Portuguese translations ! - -- Fixed #142, #144, #147. - -- Changed versions handling. - -- Removed indonesian english as it was causing issues with the english version of the app. - -**1.5.4.22** - -- You can now scroll through the loaded articles ! - -**1.5.4.21** - -- Spanish translation and some Indonesian ! - -**1.5.4.20** - -- Turkish translation ! - -**1.5.4.19** - -- Fixed an issue with crowdin configuration (and its translations) - -**1.5.4.18** - -- Typo fix. - -- The real last infinite scroll bug fix. - -- Simplified Chinese translation ! - -**1.5.4.17** - -- Fixed the last bug with infinite scroll. - -**1.5.4.16** - -- Fixing list view displaying issues. - -- Endless scroll is not in beta anymore. - -**1.5.4.15** - -- Fixed an issue with the sources list. - -**1.5.4.14** - -- Fixing infinite scroll trying to load more items when there are no more. - -**1.5.4.13** - -- Displaying the right number of items. - -- Fixing infinite scroll remaining issues. Should be stable enough. - -**1.5.4.12** - -- Fixed fab and toolbar issue (#113) - -- Fixed links clickable (#114) - -- Changed the link colors in the article viewer - -**1.5.4.11** - -- Hiding FABs on scroll. - -- Closing #109 (code cleaning) - -- Hiding fabs on scroll (#101) - -**1.5.4.10** - -- Displaying a loader when "reading more" in the article viewer. - -- Displaying the thumbnail instead of icon on the article viewer. - -- Scrolling to top when loading content with the "read more" button. - -**1.5.4.09** - -- Using the kotlin wrapper for the material drawer (see #98 for more details). - -- Updated support libraries - -- Changed the Floating Action Button to the support library version. - -- New reader activity action bar #103. - -**1.5.4.08** - -- Thanks @jrafaelsantana for translating the whole app in Brazilian Portuguese. - -**1.5.4.07** - -- Loading more items on swipe too. - -- Fixed popup menu style. User may need to reselect the theme. - -- Disabled reporting marking items as read if there isn't an issue. - -**1.5.4.05/06** - -- Translation fix. - -**1.5.4.04** - -- Fixing an issue with marking items as read (something related to an old version of selfoss). - -**1.5.4.03** - -- Trying to fix some issue with pre-launch reports. Reverted because it seems to be related to the dev console side. - -**1.5.4.02** - -- Fixing full height cards issue. - -**1.5.4.01** - -- Removed the "apk downloaded from outside of playstore" message. - -- Versions update. - -- HTML viewer version update. It should fix an issue with images. - -- Some code cleaning. - -**1.5.4.00** - -- Added issue reporting from within the app. - -**1.5.3.06** - -- Fixed infinite scroll not working. - -- Fixed logs not working. - -- Temporary workaround handling opening invalid urls. Waiting to solve #83. - -**1.5.3.05** - -- Fixed an issue on older versions of Android. - -- Libs update. - -**1.5.3.04** - -- Crowdin translations - -**1.5.3.03** - -- Libs updates. - -- Translation fix. - -**1.5.3.01/02** - -- Added translation link to the settings page. - -- Added the translation link to the README. - -**1.5.3.00** - -- (BETA) Added pull from bottom to load more pages of results. May be buggy. - -**1.5.2.18/19** - -- APK minification finally working. That means less space taken ! -- Added an option to log every API call. - -**1.5.2.17** - -- Source code and tracker links weren't being set, and updated the contributing doc. - -**1.5.2.15/16** - -- Adding an account header on the lateral drawer. - -- The account header is only displayed when the setting is enabled. - -**1.5.2.13/14** - -- Updated glide. - -- Loading images from self signed certificate now working. - -**1.5.2.12** - -- Self signed certificates are now working for loading data. Image are not loading yet. - -**1.5.2.11** - -- Added a random unique identifier to be used in the logs. - -**1.5.2.08/09/10** - -- Added settable logs for reading articles problems. - -**1.5.2.07** - -- Added the ability to choose the number of items loaded (the maximum value is 200 and is imposed by the selfoss api) - -**1.5.2.06** - -- Fix problem introduced in 1.5.2.04. SVG file not working on older versions of android. - -**1.5.2.05** - -- Versions updates - -**1.5.2.04** - -- Reverted to the old icon. - -- Better icon for the intro activity. - -- Updated gradle version. - -**1.5.2.03** - -- Added the ability to accept self signed certificates. (Needs more testing) - -**1.5.2.02** - -- Added optional login option. - -**1.5.2.01** - -- New (Better) Icon ! - -**1.5.2.0** - -- New Icon ! - -**1.5.1.9/10/11** - -- Hiding the unread badge when marking all items as read. - -**1.5.1.8** - -- Fixes and libs updates. - -**1.5.1.7** - -- Bug fixes. - -- Code cleaning - -**1.5.1.6** - -- Added back the badges after it was fixed on the library side. - -**1.5.1.5** - -- THEMES !!!! For now, the app has predefined themes. You can ask for new ones until I make them dynamic. - -**1.5.1.3/4** - -- Fixes introduces by the previous alpha (1.5.1.2) - -**1.5.1.2** - -- Added testing to the CI. - -- Code cleaning - -- Display the pull to refresh loader on api call - -- Fixes : - - - Can't pull down to refresh on first launch - - - Recurring crash because of the url - - - Couldn't open some urls because of missing "http" - - - Adding a source with invalid url would crash - - -**1.5.1.1** - -- Fixed an issue when trying to add a source without being logged in. - -- Reloading drawer tags badges on slide to refresh. - -**1.5.1** - -- Added a drawer for filtering sources and tags. - -- You can now search for items from the toolbar. - -**1.5.0.2** - -- If the content in the article viewer is empty, the article will open in a custom tab. - -- Added a share button, and an "open in browser" button to the bottom of the article viewer. - -- Updated custom tab code. - -**1.5.0.1** - -- The release APK wasn't working at all. - -**1.5.0.0** - -_New_ - -- The app is now open source ! And rewritten in Kotlin ! - -**1.4.0.9** - -_Fixes_ - -- Fixes and missing translations. - -**1.4.0.8** - -_New_ - -- Added setting for full height and fixed height cards size. - -_Fixed_ - -- Action Bar color now matches the primary color on the recent apps screen. - -- Added a bottom margin to de article viewer content - -- Multiple fixes for the new article viewer. - -**1.4.0.7** - -_Fixed_ - -- Disable swipe to hide from other "tabs" and avoid badges problems - -- Fixed a bug with the new Article viewer with some displaying fixes - - -**1.4.0.6** - -_New_ - -- Added the ability to use http authentication (Basic and Digest) - -_Fixed_ - -- Fixed gitter link - -- Change the article viewer because the other was causing crashes - -**1.4.0.5** - -_New_ - -- Added an intro to the app. - -- Added the ability to test the app without a Selfoss instance. - -**1.4.0.4** - -_New_ - -- Added the ability to have a github build. If the apk is a Github build, check for update and ask the user to download it (directly from the github page). - -_Changes_ - -- The apk stating that the app wasn't installed from the store is only displayed on start. - -**1.4.0.3** - -_Fixed_ - -- Fixed boolean problem. - -**1.4.0.2** - -_New_ - -- The app is available in Dutch ! - -_Fixed_ - -- Fixed a bug with the articles states. - -**1.4.0.1** - -_New_ - -- You can now help me translate the app ! There will be a dialog displayed the first time you open the app, and the link will still be available from the settings page. - -_Changes_ - -- Changed the custom tabs color to dark orange to fix the wrong title color. - -_Fixes_ - -- The badges now are shown even if the tab is selected. - -- Fixed feeds not reloading on app resume (caused by 1.4.0.0 changes). - -**1.4.0.0** - -_New_ - -- Added a setting to enable/disable the article viewer when the internal browser is enabled. - -- Added peek to the card view. - -- Text drawable if no icon. - - -_Changes_ - -- Changed the external browser setting to internal browser and handled the change on first open. - -- Some text changes. - -- Better animations handling on slow networks. - -... - -**1.3.3.5** - -_New_ - -- Added tab bar badges with settings to display them. - -- Added invites. - -_Fixes_ - -- Fixed a typo. - -_Updates_ - -- Updated support library to 10.2.0. - -- Updated firebase to 10.2.0. - -- Updated article_viewer to 0.20.1. - -- Updated bottom-bar to 2.1.1. - - -**1.3.3.4** - -... diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 30ace6a..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. \ No newline at end of file diff --git a/README.md b/README.md index f6d46f7..f978238 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# Project moved to https://github.com/aminecmi/ReaderforSelfoss-multiplatform +# Multiplatform version [here](https://gitea.amine-louveau.fr/Amine_L/ReaderForSelfoss-multiplatform) + +# Original moved [here](https://gitea.amine-louveau.fr/Amine_L/ReaderforSelfoss) diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 8b2ff97..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,172 +0,0 @@ -buildscript { -} - -def gitVersion() { - def process - 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() { - println "version code " + gitVersion() - return gitVersion().toInteger() -} - -def versionNameFromGit() { - println "version name " + gitVersion() - return gitVersion() -} - -apply plugin: 'com.android.application' - -apply plugin: 'kotlin-android' - -apply plugin: 'kotlin-kapt' - -android { - compileOptions { - // Flag to enable support for the new language APIs - coreLibraryDesugaringEnabled true - - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - compileSdkVersion 31 - buildToolsVersion '31.0.0' - buildFeatures { - viewBinding true - } - defaultConfig { - applicationId "apps.amine.bou.readerforselfoss" - minSdkVersion 21 - targetSdkVersion 31 - versionCode versionCodeFromGit() - versionName versionNameFromGit() - - // Enabling multidex support. - multiDexEnabled true - lintOptions { - abortOnError true - disable 'InvalidPackage' - } - vectorDrawables.useSupportLibrary = true - - // tests - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - - javaCompileOptions { - annotationProcessorOptions { - arguments = ["room.schemaLocation": - "$projectDir/schemas".toString()] - } - } - } - buildTypes { - release { - minifyEnabled true - shrinkResources true - proguardFiles getDefaultProguardFile('proguard-android.txt'), - 'proguard-rules.pro' - } - debug { - buildConfigField "String", "LOGIN_URL", appLoginUrl - buildConfigField "String", "LOGIN_PASSWORD", appLoginPassword - buildConfigField "String", "LOGIN_USERNAME", appLoginUsername - } - } - flavorDimensions "build" - productFlavors { - githubConfig { - versionNameSuffix '-github' - dimension "build" - } - } - kotlinOptions { - jvmTarget = '1.8' - } -} - -dependencies { - implementation 'androidx.preference:preference-ktx:1.1.1' - - // Testing - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha02' - androidTestImplementation 'androidx.test:runner:1.3.1-alpha02' - // Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource - androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0-alpha02' - // Espresso-intents for validation and stubbing of Intents - androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0-alpha02' - implementation fileTree(include: ['*.jar'], dir: 'libs') - - // Android Support - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.5.0' - implementation 'androidx.recyclerview:recyclerview:1.3.0-alpha01' - implementation "androidx.legacy:legacy-support-v4:$android_version" - implementation 'androidx.vectordrawable:vectordrawable:1.2.0-alpha02' - implementation 'androidx.browser:browser:1.4.0' - implementation "androidx.cardview:cardview:$android_version" - implementation 'androidx.annotation:annotation:1.3.0' - implementation 'androidx.work:work-runtime-ktx:2.7.1' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation 'org.jsoup:jsoup:1.14.3' - - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") - - //multidex - implementation 'androidx.multidex:multidex:2.0.1' - - // About - implementation 'com.mikepenz:aboutlibraries-core:8.9.4' - implementation 'com.mikepenz:aboutlibraries:8.9.4' - implementation "com.mikepenz:aboutlibraries-definitions:8.9.4" - - // Async - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0' - - // Retrofit + http logging + okhttp - implementation 'com.squareup.retrofit2:retrofit:2.9.0' - implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3' - implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - implementation 'com.burgstaller:okhttp-digest:2.5' - - // Material-ish things - implementation 'com.ashokvarma.android:bottom-navigation-bar:2.2.0' - implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' - - // glide - kapt 'com.github.bumptech.glide:compiler:4.11.0' - implementation 'com.github.bumptech.glide:okhttp3-integration:4.1.1' - - // Drawer - implementation 'com.mikepenz:materialdrawer:8.4.5' - - // Themes - implementation 'com.52inc:scoops:1.0.0' - implementation 'com.jaredrummler:colorpicker:1.1.0' - implementation 'com.github.rubensousa:floatingtoolbar:1.5.1' - - // Pager - implementation 'me.relex:circleindicator:2.1.6' - implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" - - //PhotoView - implementation 'com.github.chrisbanes:PhotoView:2.3.0' - - implementation 'androidx.core:core-ktx:1.7.0' - - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' - implementation 'androidx.lifecycle:lifecycle-common-java8:2.4.0' - - implementation "androidx.room:room-ktx:2.4.0-beta01" - kapt "androidx.room:room-compiler:2.4.0-beta01" - - implementation "android.arch.work:work-runtime-ktx:$work_version" -} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index 0b3ee7b..0000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,65 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /home/amine/apps/android-sdk-linux/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - -#About libraries --keep class .R --keep class **.R$* { - ; -} - --dontwarn okio.** --dontwarn retrofit2.Platform$Java8 --keep class retrofit.** { *; } --keepclasseswithmembers class * { - @retrofit.http.* ; -} --keepattributes *Annotation*,Signature --keepattributes Exceptions --dontwarn okio.** --dontwarn javax.annotation.Nullable --dontwarn javax.annotation.ParametersAreNonnullByDefault - - -#Bottom bar lib --dontwarn com.roughike.bottombar.** - - -# self signed glidemodule --keep public class * implements com.bumptech.glide.module.GlideModule --keep public class * extends com.bumptech.glide.AppGlideModule --keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { - **[] $VALUES; - public *; -} - --dontwarn com.anupcowkur.reservoir.** - --dontwarn javax.annotation.** - --keep class android.support.v7.widget.SearchView { *; } - -# maybe remove later ? --keep class * extends androidx.fragment.app.Fragment diff --git a/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/1.json b/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/1.json deleted file mode 100644 index b478c9e..0000000 --- a/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/1.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 1, - "identityHash": "08ca537d7ac9d4dd216e8e395d70801a", - "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": [] - } - ], - "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, \"08ca537d7ac9d4dd216e8e395d70801a\")" - ] - } -} \ No newline at end of file diff --git a/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/2.json b/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/2.json deleted file mode 100644 index c9d43bc..0000000 --- a/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/2.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 2, - "identityHash": "6fa6944b04100d68eab61039876a8804", - "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": [] - } - ], - "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, \"6fa6944b04100d68eab61039876a8804\")" - ] - } -} \ No newline at end of file diff --git a/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/3.json b/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/3.json deleted file mode 100644 index 70d1621..0000000 --- a/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/3.json +++ /dev/null @@ -1,226 +0,0 @@ -{ - "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\")" - ] - } -} \ No newline at end of file diff --git a/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/4.json b/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/4.json deleted file mode 100644 index 051cd37..0000000 --- a/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/4.json +++ /dev/null @@ -1,226 +0,0 @@ -{ - "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\")" - ] - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/AddSourceEspressoTest.kt b/app/src/androidTest/java/apps/amine/bou/readerforselfoss/AddSourceEspressoTest.kt deleted file mode 100644 index fca53f4..0000000 --- a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/AddSourceEspressoTest.kt +++ /dev/null @@ -1,3 +0,0 @@ -package apps.amine.bou.readerforselfoss - -// TODO: test source adding \ No newline at end of file diff --git a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/HomeActivityEspressoTest.kt b/app/src/androidTest/java/apps/amine/bou/readerforselfoss/HomeActivityEspressoTest.kt deleted file mode 100644 index 793d88d..0000000 --- a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/HomeActivityEspressoTest.kt +++ /dev/null @@ -1,100 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import android.content.Context -import android.content.Intent -import androidx.test.InstrumentationRegistry -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.action.ViewActions.closeSoftKeyboard -import androidx.test.espresso.action.ViewActions.pressKey -import androidx.test.espresso.action.ViewActions.typeText -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.intent.Intents -import androidx.test.espresso.intent.Intents.intended -import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withContentDescription -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withText -import androidx.test.rule.ActivityTestRule -import androidx.test.runner.AndroidJUnit4 -import android.view.KeyEvent -import androidx.test.espresso.matcher.RootMatchers.isDialog -import apps.amine.bou.readerforselfoss.utils.Config -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class HomeActivityEspressoTest { - lateinit var context: Context - - @Rule @JvmField - val rule = ActivityTestRule(HomeActivity::class.java, true, false) - - @Before - fun clearData() { - context = InstrumentationRegistry.getInstrumentation().targetContext - - val editor = - context - .getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) - .edit() - editor.clear() - - editor.putString("url", BuildConfig.LOGIN_URL) - editor.putString("login", BuildConfig.LOGIN_USERNAME) - editor.putString("password", BuildConfig.LOGIN_PASSWORD) - - editor.commit() - - Intents.init() - } - - @Test - fun menuItems() { - - rule.launchActivity(Intent()) - - onView( - withMenu( - id = R.id.action_search, - titleId = R.string.menu_home_search - ) - ).perform(click()) - - onView(withId(R.id.search_bar)).check(matches(isDisplayed())) - - onView(withId(R.id.search_src_text)).perform( - typeText("android"), - pressKey(KeyEvent.KEYCODE_SEARCH), - closeSoftKeyboard() - ) - - onView(withContentDescription(R.string.abc_toolbar_collapse_description)).perform(click()) - - openActionBarOverflowOrOptionsMenu(context) - - onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh)) - .perform(click()) - - onView(withText(android.R.string.ok)) - .inRoot(isDialog()).check(matches(isDisplayed())).perform(click()) - - openActionBarOverflowOrOptionsMenu(context) - - onView(withText(R.string.action_disconnect)).perform(click()) - - intended(hasComponent(LoginActivity::class.java.name)) - } - - // TODO: test articles opening and actions for cards and lists - - @After - fun releaseIntents() { - Intents.release() - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/LoginActivityEspressoTest.kt b/app/src/androidTest/java/apps/amine/bou/readerforselfoss/LoginActivityEspressoTest.kt deleted file mode 100644 index 6a5f5b4..0000000 --- a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/LoginActivityEspressoTest.kt +++ /dev/null @@ -1,178 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import android.content.Context -import android.content.Intent -import androidx.test.InstrumentationRegistry -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.action.ViewActions.closeSoftKeyboard -import androidx.test.espresso.action.ViewActions.pressBack -import androidx.test.espresso.action.ViewActions.typeText -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.intent.Intents -import androidx.test.espresso.intent.Intents.intended -import androidx.test.espresso.intent.Intents.times -import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.espresso.matcher.ViewMatchers.isRoot -import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withText -import androidx.test.rule.ActivityTestRule -import androidx.test.runner.AndroidJUnit4 -import apps.amine.bou.readerforselfoss.utils.Config -import com.mikepenz.aboutlibraries.ui.LibsActivity -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class LoginActivityEspressoTest { - - @Rule @JvmField - val rule = ActivityTestRule(LoginActivity::class.java, true, false) - - private lateinit var context: Context - private lateinit var url: String - private lateinit var username: String - private lateinit var password: String - - @Before - fun setUp() { - context = InstrumentationRegistry.getInstrumentation().targetContext - val editor = - context - .getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) - .edit() - editor.clear() - editor.commit() - - - url = BuildConfig.LOGIN_URL - username = BuildConfig.LOGIN_USERNAME - password = BuildConfig.LOGIN_PASSWORD - - Intents.init() - } - - @Test - fun menuItems() { - - rule.launchActivity(Intent()) - - openActionBarOverflowOrOptionsMenu(context) - - onView(withText(R.string.action_about)).perform(click()) - - intended(hasComponent(LibsActivity::class.java.name), times(1)) - - onView(isRoot()).perform(pressBack()) - - intended(hasComponent(LoginActivity::class.java.name)) - } - - @Test - fun wrongLoginUrl() { - rule.launchActivity(Intent()) - - onView(withId(R.id.loginProgress)) - .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) - - onView(withId(R.id.urlView)).perform(click()).perform(typeText("WRONGURL")) - - onView(withId(R.id.signInButton)).perform(click()) - - onView(withId(R.id.urlView)).check(matches(isHintOrErrorEnabled())) - } - - // TODO: Add tests for multiple false urls with dialog - - @Test - fun emptyAuthData() { - - rule.launchActivity(Intent()) - - onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard()) - - onView(withId(R.id.withLogin)).perform(click()) - - onView(withId(R.id.signInButton)).perform(click()) - - onView(withId(R.id.loginView)).check(matches(isHintOrErrorEnabled())) - onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled())) - - onView(withId(R.id.loginView)).perform(click()).perform( - typeText(username), - closeSoftKeyboard() - ) - - onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled())) - - onView(withId(R.id.signInButton)).perform(click()) - - onView(withId(R.id.passwordView)).check( - matches( - isHintOrErrorEnabled() - ) - ) - } - - @Test - fun wrongAuthData() { - - rule.launchActivity(Intent()) - - onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard()) - - onView(withId(R.id.withLogin)).perform(click()) - - onView(withId(R.id.loginView)).perform(click()).perform( - typeText(username), - closeSoftKeyboard() - ) - - onView(withId(R.id.passwordView)).perform(click()).perform( - typeText("WRONGPASS"), - closeSoftKeyboard() - ) - - onView(withId(R.id.signInButton)).perform(click()) - - onView(withId(R.id.urlView)).check(matches(isHintOrErrorEnabled())) - onView(withId(R.id.loginView)).check(matches(isHintOrErrorEnabled())) - onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled())) - } - - @Test - fun workingAuth() { - - rule.launchActivity(Intent()) - - onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard()) - - onView(withId(R.id.withLogin)).perform(click()) - - onView(withId(R.id.loginView)).perform(click()).perform( - typeText(username), - closeSoftKeyboard() - ) - - onView(withId(R.id.passwordView)).perform(click()).perform( - typeText(password), - closeSoftKeyboard() - ) - - onView(withId(R.id.signInButton)).perform(click()) - - Thread.sleep(2000) - intended(hasComponent(HomeActivity::class.java.name)) - } - - @After - fun releaseIntents() { - Intents.release() - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/MainActivityEspressoTest.kt b/app/src/androidTest/java/apps/amine/bou/readerforselfoss/MainActivityEspressoTest.kt deleted file mode 100644 index 341593a..0000000 --- a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/MainActivityEspressoTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.preference.PreferenceManager -import androidx.test.InstrumentationRegistry.getInstrumentation -import androidx.test.espresso.intent.Intents -import androidx.test.espresso.intent.Intents.intended -import androidx.test.espresso.intent.Intents.times -import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent -import androidx.test.rule.ActivityTestRule -import androidx.test.runner.AndroidJUnit4 -import apps.amine.bou.readerforselfoss.utils.Config -import org.junit.After - -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class MainActivityEspressoTest { - - lateinit var intent: Intent - lateinit var preferencesEditor: SharedPreferences.Editor - private lateinit var url: String - private lateinit var username: String - private lateinit var password: String - - @Rule @JvmField - val rule = ActivityTestRule(MainActivity::class.java, true, false) - - @Before - fun setUp() { - intent = Intent() - val context = getInstrumentation().targetContext - - // create a SharedPreferences editor - preferencesEditor = context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE).edit() - - url = BuildConfig.LOGIN_URL - username = BuildConfig.LOGIN_USERNAME - password = BuildConfig.LOGIN_PASSWORD - - Intents.init() - } - - @Test - fun checkFirstOpenLaunchesIntro() { - preferencesEditor.putString("url", "") - preferencesEditor.putString("password", "") - preferencesEditor.putString("login", "") - preferencesEditor.commit() - - rule.launchActivity(intent) - - intended(hasComponent(LoginActivity::class.java.name)) - intended(hasComponent(HomeActivity::class.java.name), times(0)) - } - - @Test - fun checkNotFirstOpenLaunchesLogin() { - preferencesEditor.putString("url", url) - preferencesEditor.putString("password", password) - preferencesEditor.putString("login", username) - preferencesEditor.commit() - - rule.launchActivity(intent) - - intended(hasComponent(MainActivity::class.java.name)) - intended(hasComponent(HomeActivity::class.java.name)) - } - - @After - fun releaseIntents() { - Intents.release() - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/Utils.kt b/app/src/androidTest/java/apps/amine/bou/readerforselfoss/Utils.kt deleted file mode 100644 index 6c748a3..0000000 --- a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/Utils.kt +++ /dev/null @@ -1,29 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import androidx.test.espresso.matcher.ViewMatchers -import android.view.View -import android.widget.EditText -import org.hamcrest.Description -import org.hamcrest.Matcher -import org.hamcrest.Matchers -import org.hamcrest.TypeSafeMatcher - -fun isHintOrErrorEnabled(): Matcher = - object : TypeSafeMatcher() { - override fun describeTo(description: Description?) { - } - - override fun matchesSafely(item: View?): Boolean { - if (item !is EditText) { - return false - } - - return item.error.isNotEmpty() - } - } - -fun withMenu(id: Int, titleId: Int): Matcher = - Matchers.anyOf( - ViewMatchers.withId(id), - ViewMatchers.withText(titleId) - ) diff --git a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/utils/DateUtilsTest.kt b/app/src/androidTest/java/apps/amine/bou/readerforselfoss/utils/DateUtilsTest.kt deleted file mode 100644 index 7b59599..0000000 --- a/app/src/androidTest/java/apps/amine/bou/readerforselfoss/utils/DateUtilsTest.kt +++ /dev/null @@ -1,29 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils - -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) - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml deleted file mode 100644 index f42790b..0000000 --- a/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png deleted file mode 100644 index a149479..0000000 Binary files a/app/src/main/ic_launcher-web.png and /dev/null differ diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/AddSourceActivity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/AddSourceActivity.kt deleted file mode 100644 index 0517f2e..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/AddSourceActivity.kt +++ /dev/null @@ -1,268 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.preference.PreferenceManager -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.appcompat.app.AppCompatActivity -import android.view.View -import android.widget.AdapterView -import android.widget.ArrayAdapter -import android.widget.EditText -import android.widget.ProgressBar -import android.widget.Spinner -import android.widget.TextView -import android.widget.Toast -import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi -import apps.amine.bou.readerforselfoss.api.selfoss.Spout -import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse -import apps.amine.bou.readerforselfoss.themes.AppColors -import apps.amine.bou.readerforselfoss.themes.Toppings -import apps.amine.bou.readerforselfoss.utils.Config -import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid -import com.ftinc.scoop.Scoop -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response -import android.graphics.PorterDuff -import apps.amine.bou.readerforselfoss.databinding.ActivityAddSourceBinding - - - -class AddSourceActivity : AppCompatActivity() { - - private var mSpoutsValue: String? = null - private lateinit var api: SelfossApi - - private lateinit var appColors: AppColors - private lateinit var binding: ActivityAddSourceBinding - - override fun onCreate(savedInstanceState: Bundle?) { - appColors = AppColors(this@AddSourceActivity) - - super.onCreate(savedInstanceState) - binding = ActivityAddSourceBinding.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) - - val drawable = binding.nameInput.background - drawable.setTint(appColors.colorAccent) - - - // TODO: clean - binding.nameInput.background = drawable - - val drawable1 = binding.sourceUri.background - drawable1.setTint(appColors.colorAccent) - - binding.sourceUri.background = drawable1 - - val drawable2 = binding.tags.background - drawable2.setTint(appColors.colorAccent) - - binding.tags.background = drawable2 - - setSupportActionBar(binding.toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setDisplayShowHomeEnabled(true) - - try { - val prefs = PreferenceManager.getDefaultSharedPreferences(this) - val settings = - getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) - api = SelfossApi( - this, - this@AddSourceActivity, - settings.getBoolean("isSelfSignedCert", false), - prefs.getString("api_timeout", "-1")!!.toLong() - ) - } catch (e: IllegalArgumentException) { - mustLoginToAddSource() - } - - maybeGetDetailsFromIntentSharing(intent, binding.sourceUri, binding.nameInput) - - binding.saveBtn.setTextColor(appColors.colorAccent) - - binding.saveBtn.setOnClickListener { - handleSaveSource(binding.tags, binding.nameInput.text.toString(), binding.sourceUri.text.toString(), api) - } - } - - override fun onResume() { - super.onResume() - val config = Config(this) - - if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid(this@AddSourceActivity)) { - mustLoginToAddSource() - } else { - handleSpoutsSpinner(binding.spoutsSpinner, api, binding.progress, binding.formContainer) - } - } - - private fun handleSpoutsSpinner( - spoutsSpinner: Spinner, - api: SelfossApi?, - mProgress: ProgressBar, - formContainer: ConstraintLayout - ) { - val spoutsKV = HashMap() - spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) { - if (view != null) { - val spoutName = (view as TextView).text.toString() - mSpoutsValue = spoutsKV[spoutName] - } - } - - override fun onNothingSelected(adapterView: AdapterView<*>) { - mSpoutsValue = null - } - } - - var items: Map - api!!.spouts().enqueue(object : Callback> { - override fun onResponse( - call: Call>, - response: Response> - ) { - if (response.body() != null) { - items = response.body()!! - - val itemsStrings = items.map { it.value.name } - for ((key, value) in items) { - spoutsKV[value.name] = key - } - - mProgress.visibility = View.GONE - formContainer.visibility = View.VISIBLE - - val spinnerArrayAdapter = - ArrayAdapter( - this@AddSourceActivity, - android.R.layout.simple_spinner_item, - itemsStrings - ) - spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - spoutsSpinner.adapter = spinnerArrayAdapter - } else { - handleProblemWithSpouts() - } - } - - override fun onFailure(call: Call>, t: Throwable) { - handleProblemWithSpouts() - } - - private fun handleProblemWithSpouts() { - Toast.makeText( - this@AddSourceActivity, - R.string.cant_get_spouts, - Toast.LENGTH_SHORT - ).show() - mProgress.visibility = View.GONE - } - }) - } - - private fun maybeGetDetailsFromIntentSharing( - intent: Intent, - sourceUri: EditText, - nameInput: EditText - ) { - if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) { - sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT)) - nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE)) - } - } - - private fun mustLoginToAddSource() { - Toast.makeText(this, getString(R.string.addStringNoUrl), Toast.LENGTH_SHORT).show() - val i = Intent(this, LoginActivity::class.java) - startActivity(i) - finish() - } - - private fun handleSaveSource(tags: EditText, title: String, url: String, api: SelfossApi) { - - val sourceDetailsUnavailable = - title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty() - - when { - sourceDetailsUnavailable -> { - Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show() - } - PreferenceManager.getDefaultSharedPreferences(this).getInt("apiVersionMajor", 0) > 1 -> { - val tagList = tags.text.toString().split(",").map { it.trim() } - api.createSourceApi2( - title, - url, - mSpoutsValue!!, - tagList, - "" - ).enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - 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, t: Throwable) { - Toast.makeText( - this@AddSourceActivity, - R.string.cant_create_source, - Toast.LENGTH_SHORT - ).show() - } - }) - } - else -> { - api.createSource( - title, - url, - mSpoutsValue!!, - tags.text.toString(), - "" - ).enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - 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, t: Throwable) { - Toast.makeText( - this@AddSourceActivity, - R.string.cant_create_source, - Toast.LENGTH_SHORT - ).show() - } - }) - } - } - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/HomeActivity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/HomeActivity.kt deleted file mode 100644 index 89ff52e..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/HomeActivity.kt +++ /dev/null @@ -1,1293 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.graphics.Color -import android.graphics.drawable.Drawable -import android.graphics.drawable.GradientDrawable -import android.net.Uri -import android.os.Bundle -import androidx.preference.PreferenceManager -import android.view.Menu -import android.view.MenuItem -import android.view.View -import android.widget.ImageView -import android.widget.Toast -import androidx.appcompat.app.ActionBarDrawerToggle -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.SearchView -import androidx.core.view.doOnNextLayout -import androidx.drawerlayout.widget.DrawerLayout -import androidx.recyclerview.widget.* -import androidx.room.Room -import androidx.work.Constraints -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkManager -import apps.amine.bou.readerforselfoss.adapters.ItemCardAdapter -import apps.amine.bou.readerforselfoss.adapters.ItemListAdapter -import apps.amine.bou.readerforselfoss.adapters.ItemsAdapter -import apps.amine.bou.readerforselfoss.api.selfoss.* -import apps.amine.bou.readerforselfoss.background.LoadingWorker -import apps.amine.bou.readerforselfoss.databinding.ActivityHomeBinding -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.settings.SettingsActivity -import apps.amine.bou.readerforselfoss.themes.AppColors -import apps.amine.bou.readerforselfoss.themes.Toppings -import apps.amine.bou.readerforselfoss.utils.Config -import apps.amine.bou.readerforselfoss.utils.SharedItems -import apps.amine.bou.readerforselfoss.utils.bottombar.maybeShow -import apps.amine.bou.readerforselfoss.utils.bottombar.removeBadge -import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper -import apps.amine.bou.readerforselfoss.utils.longHash -import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible -import apps.amine.bou.readerforselfoss.utils.persistence.toEntity -import apps.amine.bou.readerforselfoss.utils.persistence.toView -import com.ashokvarma.bottomnavigation.BottomNavigationBar -import com.ashokvarma.bottomnavigation.BottomNavigationItem -import com.ashokvarma.bottomnavigation.TextBadgeItem -import com.bumptech.glide.Glide -import com.bumptech.glide.request.RequestOptions -import com.ftinc.scoop.Scoop -import com.mikepenz.aboutlibraries.LibsBuilder -import com.mikepenz.materialdrawer.holder.BadgeStyle -import com.mikepenz.materialdrawer.holder.ColorHolder -import com.mikepenz.materialdrawer.holder.StringHolder -import com.mikepenz.materialdrawer.model.DividerDrawerItem -import com.mikepenz.materialdrawer.model.PrimaryDrawerItem -import com.mikepenz.materialdrawer.model.ProfileDrawerItem -import com.mikepenz.materialdrawer.model.SecondaryDrawerItem -import com.mikepenz.materialdrawer.model.interfaces.* -import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader -import com.mikepenz.materialdrawer.util.DrawerImageLoader -import com.mikepenz.materialdrawer.util.addStickyFooterItem -import com.mikepenz.materialdrawer.util.updateBadge -import com.mikepenz.materialdrawer.widget.AccountHeaderView -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response -import java.util.concurrent.TimeUnit -import kotlin.concurrent.thread - -class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { - - private val MENU_PREFERENCES = 12302 - private val DRAWER_ID_TAGS = 100101L - private val DRAWER_ID_HIDDEN_TAGS = 101100L - private val DRAWER_ID_SOURCES = 100110L - private val DRAWER_ID_FILTERS = 100111L - private val UNREAD_SHOWN = 1 - private val READ_SHOWN = 2 - private val FAV_SHOWN = 3 - - private var items: ArrayList = ArrayList() - private var allItems: ArrayList = ArrayList() - - private var internalBrowser = false - private var articleViewer = false - private var shouldBeCardView = false - private var displayUnreadCount = false - private var displayAllCount = false - private var fullHeightCards: Boolean = false - private var itemsNumber: Int = 200 - private var elementsShown: Int = 1 - private var userIdentifier: String = "" - private var displayAccountHeader: Boolean = false - private var infiniteScroll: Boolean = false - private var lastFetchDone: Boolean = false - private var itemsCaching: Boolean = false - private var updateSources: Boolean = true - private var markOnScroll: Boolean = false - private var hiddenTags: List = emptyList() - private var apiVersionMajor: Int = 0 - - private var periodicRefresh = false - private var refreshMinutes: Long = 360L - private var refreshWhenChargingOnly = false - - private lateinit var tabNewBadge: TextBadgeItem - private lateinit var tabArchiveBadge: TextBadgeItem - private lateinit var tabStarredBadge: TextBadgeItem - private lateinit var api: SelfossApi - private lateinit var customTabActivityHelper: CustomTabActivityHelper - private lateinit var editor: SharedPreferences.Editor - private lateinit var sharedPref: SharedPreferences - private lateinit var appColors: AppColors - private var offset: Int = 0 - private var firstVisible: Int = 0 - private lateinit var recyclerViewScrollListener: RecyclerView.OnScrollListener - private lateinit var settings: SharedPreferences - private lateinit var binding: ActivityHomeBinding - - private var recyclerAdapter: RecyclerView.Adapter<*>? = null - - private var fromTabShortcut: Boolean = false - private var offlineShortcut: Boolean = false - - private lateinit var tagsBadge: Map - - private lateinit var db: AppDatabase - - private lateinit var config: Config - - data class DrawerData(val tags: List?, val sources: List?) - - override fun onStart() { - super.onStart() - customTabActivityHelper.bindCustomTabsService(this) - } - - override fun onCreate(savedInstanceState: Bundle?) { - appColors = AppColors(this@HomeActivity) - config = Config(this@HomeActivity) - - super.onCreate(savedInstanceState) - binding = ActivityHomeBinding.inflate(layoutInflater) - val view = binding.root - - fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1 - offlineShortcut = intent.getBooleanExtra("startOffline", false) - - if (fromTabShortcut) { - elementsShown = intent.getIntExtra("shortcutTab", UNREAD_SHOWN) - } - - setContentView(view) - - handleThemeBinding() - - setSupportActionBar(binding.toolBar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setHomeButtonEnabled(true) - val mDrawerToggle = ActionBarDrawerToggle(this, binding.drawerContainer, binding.toolBar, R.string.material_drawer_open, R.string.material_drawer_close) - binding.drawerContainer.addDrawerListener(mDrawerToggle) - mDrawerToggle.syncState() - - db = Room.databaseBuilder( - applicationContext, - AppDatabase::class.java, "selfoss-database" - ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build() - - - customTabActivityHelper = CustomTabActivityHelper() - - sharedPref = PreferenceManager.getDefaultSharedPreferences(this) - settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) - - api = SelfossApi( - this, - this@HomeActivity, - settings.getBoolean("isSelfSignedCert", false), - sharedPref.getString("api_timeout", "-1")!!.toLong() - ) - items = ArrayList() - allItems = ArrayList() - - handleBottomBar() - handleDrawer() - - handleSwipeRefreshLayout() - - handleSharedPrefs() - - getApiMajorVersion() - - getElementsAccordingToTab() - } - - private fun handleSwipeRefreshLayout() { - binding.swipeRefreshLayout.setColorSchemeResources( - R.color.refresh_progress_1, - R.color.refresh_progress_2, - R.color.refresh_progress_3 - ) - binding.swipeRefreshLayout.setOnRefreshListener { - offlineShortcut = false - allItems = ArrayList() - lastFetchDone = false - handleDrawerItems() - CoroutineScope(Dispatchers.Main).launch { - refreshFocusedItems(applicationContext, api, db, itemsNumber) - getElementsAccordingToTab() - binding.swipeRefreshLayout.isRefreshing = false - } - } - - val simpleItemTouchCallback = - object : ItemTouchHelper.SimpleCallback( - 0, - ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT - ) { - override fun getSwipeDirs( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder - ): Int = - if (elementsShown == FAV_SHOWN) { - 0 - } else { - super.getSwipeDirs( - recyclerView, - viewHolder - ) - } - - override fun onMove( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder - ): Boolean = false - - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) { - val position = viewHolder.bindingAdapterPosition - val i = items.elementAtOrNull(position) - - if (i != null) { - val adapter = binding.recyclerView.adapter as ItemsAdapter<*> - - adapter.handleItemAtIndex(position) - - reloadBadgeContent() - - val tagHashes = i.tags.tags.split(",").map { it.longHash() } - tagsBadge = tagsBadge.map { - if (tagHashes.contains(it.key)) { - (it.key to (it.value - 1)) - } else { - (it.key to it.value) - } - }.toMap() - reloadTagsBadges() - - // Just load everythin - if (items.size <= 0) { - getElementsAccordingToTab() - } - } else { - Toast.makeText( - this@HomeActivity, - "Found null when swiping at positon $position.", - Toast.LENGTH_LONG - ).show() - } - } - } - - ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView) - } - - private fun handleBottomBar() { - - tabNewBadge = TextBadgeItem() - .setText("") - .setHideOnSelect(false).hide(false) - .setBackgroundColor(appColors.colorPrimary) - tabArchiveBadge = TextBadgeItem() - .setText("") - .setHideOnSelect(false).hide(false) - .setBackgroundColor(appColors.colorPrimary) - tabStarredBadge = TextBadgeItem() - .setText("") - .setHideOnSelect(false).hide(false) - .setBackgroundColor(appColors.colorPrimary) - - val tabNew = - BottomNavigationItem( - R.drawable.ic_tab_fiber_new_black_24dp, - getString(R.string.tab_new) - ).setActiveColor(appColors.colorAccent) - .setBadgeItem(tabNewBadge) - val tabArchive = - BottomNavigationItem( - R.drawable.ic_tab_archive_black_24dp, - getString(R.string.tab_read) - ).setActiveColor(appColors.colorAccentDark) - .setBadgeItem(tabArchiveBadge) - val tabStarred = - BottomNavigationItem( - R.drawable.ic_tab_favorite_black_24dp, - getString(R.string.tab_favs) - ).setActiveColorResource(R.color.pink) - .setBadgeItem(tabStarredBadge) - - binding.bottomBar - .addItem(tabNew) - .addItem(tabArchive) - .addItem(tabStarred) - .setFirstSelectedPosition(0) - .initialise() - binding.bottomBar.setMode(BottomNavigationBar.MODE_SHIFTING) - binding.bottomBar.setBackgroundStyle(BottomNavigationBar.BACKGROUND_STYLE_STATIC) - - if (fromTabShortcut) { - binding.bottomBar.selectTab(elementsShown - 1) - } - } - - private fun getApiMajorVersion() { - api.apiVersion.enqueue(object : Callback { - override fun onFailure(call: Call, t: Throwable) { - Config.apiVersion = apiVersionMajor - } - - override fun onResponse(call: Call, response: Response) { - if(response.body() != null) { - val version = response.body() as ApiVersion - apiVersionMajor = version.getApiMajorVersion() - sharedPref.edit().putInt("apiVersionMajor", apiVersionMajor).apply() - - Config.apiVersion = apiVersionMajor - } - } - }) - } - - override fun onResume() { - super.onResume() - - // TODO: Make this the only appcolors init - appColors = AppColors(this@HomeActivity) - - sharedPref = PreferenceManager.getDefaultSharedPreferences(this) - - editor = settings.edit() - - handleDrawerItems() - - handleThemeUpdate() - - reloadLayoutManager() - - if (!infiniteScroll) { - binding.recyclerView.setHasFixedSize(true) - } else { - handleInfiniteScroll() - } - - handleBottomBarActions() - - handleRecurringTask() - - handleOfflineActions() - - getElementsAccordingToTab() - } - - private fun getAndStoreAllItems() { - CoroutineScope(Dispatchers.Main).launch { - binding.swipeRefreshLayout.isRefreshing = true - getAndStoreAllItems(applicationContext ,api, db) - this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout), offlineShortcut) - handleListResult() - binding.swipeRefreshLayout.isRefreshing = false - SharedItems.updateDatabase(db) - } - } - - override fun onStop() { - super.onStop() - customTabActivityHelper.unbindCustomTabsService(this) - } - - private fun handleSharedPrefs() { - internalBrowser = sharedPref.getBoolean("prefer_internal_browser", true) - articleViewer = sharedPref.getBoolean("prefer_article_viewer", true) - shouldBeCardView = sharedPref.getBoolean("card_view_active", false) - displayUnreadCount = sharedPref.getBoolean("display_unread_count", true) - displayAllCount = sharedPref.getBoolean("display_other_count", false) - fullHeightCards = sharedPref.getBoolean("full_height_cards", false) - itemsNumber = sharedPref.getString("prefer_api_items_number", "200")!!.toInt() - userIdentifier = sharedPref.getString("unique_id", "")!! - displayAccountHeader = sharedPref.getBoolean("account_header_displaying", false) - infiniteScroll = sharedPref.getBoolean("infinite_loading", false) - itemsCaching = sharedPref.getBoolean("items_caching", false) - SharedItems.itemsCaching = itemsCaching - updateSources = sharedPref.getBoolean("update_sources", true) - markOnScroll = sharedPref.getBoolean("mark_on_scroll", false) - hiddenTags = if (sharedPref.getString("hidden_tags", "")!!.isNotEmpty()) { - sharedPref.getString("hidden_tags", "")!!.replace("\\s".toRegex(), "").split(",") - } else { - emptyList() - } - periodicRefresh = sharedPref.getBoolean("periodic_refresh", false) - refreshWhenChargingOnly = sharedPref.getBoolean("refresh_when_charging", false) - refreshMinutes = sharedPref.getString("periodic_refresh_minutes", "360")!!.toLong() - - if (refreshMinutes <= 15) { - refreshMinutes = 15 - } - - apiVersionMajor = sharedPref.getInt("apiVersionMajor", 0) - } - - private fun handleThemeBinding() { - val scoop = Scoop.getInstance() - scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar) - scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value) - } - - private fun handleThemeUpdate() { - - val scoop = Scoop.getInstance() - scoop.update(Toppings.PRIMARY.value, appColors.colorPrimary) - - scoop.update(Toppings.PRIMARY_DARK.value, appColors.colorPrimaryDark) - } - - private fun handleDrawer() { - DrawerImageLoader.init(object : AbstractDrawerImageLoader() { - override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) { - Glide.with(this@HomeActivity) - .asBitmap() - .load(uri) - .apply(RequestOptions() - .placeholder(R.mipmap.ic_launcher) - .fallback(R.mipmap.ic_launcher) - .fitCenter()) - .into(imageView) - } - - override fun cancel(imageView: ImageView) { - Glide.with(this@HomeActivity).clear(imageView) - } - }) - - val drawerListener = object : DrawerLayout.DrawerListener { - override fun onDrawerSlide(drawerView: View, slideOffset: Float) { - } - - override fun onDrawerOpened(drawerView: View) { - binding.bottomBar.hide() - } - - override fun onDrawerClosed(drawerView: View) { - binding.bottomBar.show() - } - - override fun onDrawerStateChanged(newState: Int) { - } - - } - - binding.drawerContainer.addDrawerListener(drawerListener) - - displayAccountHeader = - PreferenceManager.getDefaultSharedPreferences(this) - .getBoolean("account_header_displaying", false) - - binding.mainDrawer.addStickyFooterItem( - PrimaryDrawerItem().apply { - nameRes = R.string.drawer_report_bug - iconRes = R.drawable.ic_bug_report_black_24dp - isIconTinted = true - onDrawerItemClickListener = { _, _, _ -> - val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(Config.trackerUrl)) - startActivity(browserIntent) - false - } - }) - - binding.mainDrawer.addStickyFooterItem( - PrimaryDrawerItem().apply { - nameRes = R.string.title_activity_settings - iconRes = R.drawable.ic_settings_black_24dp - isIconTinted = true - onDrawerItemClickListener = { _, _, _ -> - startActivity(Intent(this@HomeActivity, SettingsActivity::class.java)) - false - } - }) - - if (displayAccountHeader) { - AccountHeaderView(this).apply { - attachToSliderView(binding.mainDrawer) - addProfiles( - ProfileDrawerItem().apply { - nameText = settings.getString("url", "").toString() - setBackgroundResource(R.drawable.bg) - iconRes = R.mipmap.ic_launcher - selectionListEnabledForSingleProfile = false - } - ) - } - } - } - - private fun handleDrawerItems() { - tagsBadge = emptyMap() - fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) { - fun handleTags(maybeTags: List?) { - if (maybeTags == null) { - if (loadedFromCache) { - binding.mainDrawer.itemAdapter.add( - SecondaryDrawerItem() - .apply { nameRes = R.string.drawer_error_loading_tags; isSelectable = false } - ) - } - } else { - val filteredTags = maybeTags - .filterNot { hiddenTags.contains(it.tag) } - .sortedBy { it.unread == 0 } - tagsBadge = filteredTags.map { - val gd = GradientDrawable() - val gdColor = try { - Color.parseColor(it.color) - } catch (e: IllegalArgumentException) { - appColors.colorPrimary - } - - gd.setColor(gdColor) - gd.shape = GradientDrawable.RECTANGLE - gd.setSize(30, 30) - gd.cornerRadius = 30F - val drawerItem = - PrimaryDrawerItem() - .apply { - nameText = it.getTitleDecoded() - identifier = it.tag.longHash() - iconDrawable = gd - badgeStyle = BadgeStyle().apply { - textColor = ColorHolder.fromColor(Color.WHITE) - color = ColorHolder.fromColor(appColors.colorAccent) } - onDrawerItemClickListener = { _,_,_ -> - allItems = ArrayList() - SharedItems.tagFilter = it.tag - SharedItems.sourceFilter = null - SharedItems.sourceIDFilter = null - getElementsAccordingToTab() - fetchOnEmptyList() - false - } } - if (it.unread > 0) { - drawerItem.badgeText = it.unread.toString() - } - - binding.mainDrawer.itemAdapter.add(drawerItem) - - (it.tag.longHash() to it.unread) - }.toMap() - } - } - - fun handleHiddenTags(maybeTags: List?) { - if (maybeTags == null) { - if (loadedFromCache) { - binding.mainDrawer.itemAdapter.add( - SecondaryDrawerItem().apply { - nameRes = R.string.drawer_error_loading_tags - isSelectable = false - } - ) - } - } else { - val filteredHiddenTags: List = - maybeTags.filter { hiddenTags.contains(it.tag) } - tagsBadge = filteredHiddenTags.map { - val gd = GradientDrawable() - val gdColor = try { - Color.parseColor(it.color) - } catch (e: IllegalArgumentException) { - appColors.colorPrimary - } - - gd.setColor(gdColor) - gd.shape = GradientDrawable.RECTANGLE - gd.setSize(30, 30) - gd.cornerRadius = 30F - val drawerItem = - PrimaryDrawerItem().apply { - nameText = it.getTitleDecoded() - identifier = it.tag.longHash() - iconDrawable = gd - badgeStyle = BadgeStyle().apply { - textColor = ColorHolder.fromColor(Color.WHITE) - color = ColorHolder.fromColor(appColors.colorAccent) } - onDrawerItemClickListener = { _,_,_ -> - allItems = ArrayList() - SharedItems.tagFilter = it.tag - SharedItems.sourceFilter = null - SharedItems.sourceIDFilter = null - getElementsAccordingToTab() - fetchOnEmptyList() - false - } } - - if (it.unread > 0) { - drawerItem.badgeText = it.unread.toString() - } - binding.mainDrawer.itemAdapter.add(drawerItem) - - (it.tag.longHash() to it.unread) - }.toMap() - } - } - - fun handleSources(maybeSources: List?) { - if (maybeSources == null) { - if (loadedFromCache) { - binding.mainDrawer.itemAdapter.add( - SecondaryDrawerItem().apply { - nameRes = R.string.drawer_error_loading_sources - isSelectable = false - } - ) - } - } else { - for (source in maybeSources) { - val item = PrimaryDrawerItem().apply { - nameText = source.getTitleDecoded() - identifier = source.id.toLong() - iconUrl = source.getIcon(this@HomeActivity) - onDrawerItemClickListener = { _,_,_ -> - allItems = ArrayList() - SharedItems.sourceIDFilter = source.id.toLong() - SharedItems.sourceFilter = source.title - SharedItems.tagFilter = null - getElementsAccordingToTab() - fetchOnEmptyList() - false - } - } - binding.mainDrawer.itemAdapter.add(item) - } - } - } - - binding.mainDrawer.itemAdapter.clear() - if (maybeDrawerData != null) { - binding.mainDrawer.itemAdapter.add( - SecondaryDrawerItem().apply { - nameRes = R.string.drawer_item_filters - isSelectable = false - identifier = DRAWER_ID_FILTERS - badgeRes = R.string.drawer_action_clear - onDrawerItemClickListener = { _,_,_ -> - allItems = ArrayList() - SharedItems.sourceFilter = null - SharedItems.sourceIDFilter = null - SharedItems.tagFilter = null - binding.mainDrawer.setSelectionAtPosition(-1) - getElementsAccordingToTab() - fetchOnEmptyList() - false - } - } - ) - if (hiddenTags.isNotEmpty()) { - binding.mainDrawer.itemAdapter.add( - DividerDrawerItem(), - SecondaryDrawerItem().apply { - nameRes = R.string.drawer_item_hidden_tags - identifier = DRAWER_ID_HIDDEN_TAGS - isSelectable = false - } - ) - handleHiddenTags(maybeDrawerData.tags) - } - binding.mainDrawer.itemAdapter.add( - DividerDrawerItem(), - SecondaryDrawerItem().apply { - nameRes = R.string.drawer_item_tags - identifier = DRAWER_ID_TAGS - isSelectable = false - } - ) - handleTags(maybeDrawerData.tags) - binding.mainDrawer.itemAdapter.add( - DividerDrawerItem(), - SecondaryDrawerItem().apply { - nameRes = R.string.drawer_item_sources - identifier = DRAWER_ID_SOURCES - isSelectable = false - badgeRes = R.string.drawer_action_edit - onDrawerItemClickListener = { v,_,_ -> - startActivity(Intent(v!!.context, SourcesActivity::class.java)) - false - } - } - ) - handleSources(maybeDrawerData.sources) - binding.mainDrawer.itemAdapter.add( - DividerDrawerItem(), - PrimaryDrawerItem().apply { - nameRes = R.string.action_about - isSelectable = false - iconRes = R.drawable.ic_info_outline_white_24dp - isIconTinted = true - onDrawerItemClickListener = { _,_,_ -> - LibsBuilder() - .withAboutIconShown(true) - .withAboutVersionShown(true) - .start(this@HomeActivity) - false - } - } - ) - - if (!loadedFromCache) { - if (maybeDrawerData.tags != null) { - thread { - val tagEntities = maybeDrawerData.tags.map { it.toEntity() } - db.drawerDataDao().deleteAllTags() - db.drawerDataDao().insertAllTags(*tagEntities.toTypedArray()) - } - } - if (maybeDrawerData.sources != null) { - thread { - val sourceEntities = - maybeDrawerData.sources.map { it.toEntity() } - db.drawerDataDao().deleteAllSources() - db.drawerDataDao().insertAllSources(*sourceEntities.toTypedArray()) - } - } - } - } else { - if (!loadedFromCache) { - binding.mainDrawer.itemAdapter.add( - PrimaryDrawerItem().apply { - nameRes = R.string.no_tags_loaded - identifier = DRAWER_ID_TAGS - isSelectable = false - }, - PrimaryDrawerItem().apply { - nameRes = R.string.no_sources_loaded - identifier = DRAWER_ID_SOURCES - isSelectable = false - } - ) - } - } - } - - fun drawerApiCalls(maybeDrawerData: DrawerData?) { - var tags: List? = null - var sources: List? - - fun sourcesApiCall() { - if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut) && updateSources) { - api.sources.enqueue(object : Callback> { - override fun onResponse( - call: Call>?, - response: Response> - ) { - sources = response.body() - val apiDrawerData = DrawerData(tags, sources) - if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) { - handleDrawerData(apiDrawerData) - } - } - - override fun onFailure(call: Call>?, t: Throwable?) { - val apiDrawerData = DrawerData(tags, null) - if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) { - handleDrawerData(apiDrawerData) - } - } - }) - } - } - - if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut) && updateSources) { - api.tags.enqueue(object : Callback> { - override fun onResponse( - call: Call>, - response: Response> - ) { - tags = response.body() - sourcesApiCall() - } - - override fun onFailure(call: Call>?, t: Throwable?) { - sourcesApiCall() - } - }) - } - } - - binding.mainDrawer.itemAdapter.add( - PrimaryDrawerItem().apply { - nameRes = R.string.drawer_loading - isSelectable = false - } - ) - - thread { - val drawerData = DrawerData(db.drawerDataDao().tags().map { it.toView() }, - db.drawerDataDao().sources().map { it.toView() }) - runOnUiThread { - handleDrawerData(drawerData, loadedFromCache = true) - drawerApiCalls(drawerData) - } - } - } - - private fun reloadLayoutManager() { - val currentManager = binding.recyclerView.layoutManager - val layoutManager: RecyclerView.LayoutManager - - // This will only update the layout manager if settings changed - when (currentManager) { - is StaggeredGridLayoutManager -> - if (!shouldBeCardView) { - layoutManager = GridLayoutManager( - this, - calculateNoOfColumns() - ) - binding.recyclerView.layoutManager = layoutManager - } - is GridLayoutManager -> - if (shouldBeCardView) { - layoutManager = StaggeredGridLayoutManager( - calculateNoOfColumns(), - StaggeredGridLayoutManager.VERTICAL - ) - layoutManager.gapStrategy = - StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS - binding.recyclerView.layoutManager = layoutManager - } - else -> - if (currentManager == null) { - if (!shouldBeCardView) { - layoutManager = GridLayoutManager( - this, - calculateNoOfColumns() - ) - binding.recyclerView.layoutManager = layoutManager - } else { - layoutManager = StaggeredGridLayoutManager( - calculateNoOfColumns(), - StaggeredGridLayoutManager.VERTICAL - ) - layoutManager.gapStrategy = - StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS - binding.recyclerView.layoutManager = layoutManager - } - } - } - } - - private fun handleBottomBarActions() { - binding.bottomBar.setTabSelectedListener(object : BottomNavigationBar.OnTabSelectedListener { - override fun onTabUnselected(position: Int) = Unit - - override fun onTabReselected(position: Int) { - val layoutManager = binding.recyclerView.adapter - - when (layoutManager) { - is StaggeredGridLayoutManager -> - if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) { - getElementsAccordingToTab() - } else { - layoutManager.scrollToPositionWithOffset(0, 0) - } - is GridLayoutManager -> - if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) { - getElementsAccordingToTab() - } else { - layoutManager.scrollToPositionWithOffset(0, 0) - } - else -> Unit - } - } - - override fun onTabSelected(position: Int) { - offset = 0 - lastFetchDone = false - - elementsShown = position + 1 - getElementsAccordingToTab() - binding.recyclerView.scrollToPosition(0) - - fetchOnEmptyList() - } - }) - } - - private fun fetchOnEmptyList() { - binding.recyclerView.doOnNextLayout { - if (SharedItems.focusedItems.size - 1 == getLastVisibleItem()) { - getElementsAccordingToTab(true) - } - } - } - - private fun handleInfiniteScroll() { - recyclerViewScrollListener = object : RecyclerView.OnScrollListener() { - override fun onScrolled(localRecycler: RecyclerView, dx: Int, dy: Int) { - if (dy > 0) { - val lastVisibleItem = getLastVisibleItem() - - if (lastVisibleItem == (items.size - 1) && items.size < maxItemNumber()) { - getElementsAccordingToTab(appendResults = true) - } - } - } - } - - binding.recyclerView.clearOnScrollListeners() - binding.recyclerView.addOnScrollListener(recyclerViewScrollListener) - } - - private fun getLastVisibleItem() : Int { - val manager = binding.recyclerView.layoutManager - return when (manager) { - is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions( - null - ).last() - is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition() - else -> 0 - } - } - - private fun mayBeEmpty() = - if (items.isEmpty()) { - binding.emptyText.visibility = View.VISIBLE - } else { - binding.emptyText.visibility = View.GONE - } - - private fun getElementsAccordingToTab( - appendResults: Boolean = false, - offsetOverride: Int? = null - ) { - fun doGetAccordingToTab() { - when (elementsShown) { - UNREAD_SHOWN -> getUnRead(appendResults) - READ_SHOWN -> getRead(appendResults) - FAV_SHOWN -> getStarred(appendResults) - else -> getUnRead(appendResults) - } - } - - offset = if (appendResults) { - SharedItems.focusedItems.size - 1 - } else { - 0 - } - firstVisible = if (appendResults) firstVisible else 0 - - doGetAccordingToTab() - } - - private fun getUnRead(appendResults: Boolean = false) { - CoroutineScope(Dispatchers.Main).launch { - if (appendResults || !SharedItems.fetchedUnread) { - binding.swipeRefreshLayout.isRefreshing = true - getUnreadItems(applicationContext, api, db, itemsNumber, offset) - binding.swipeRefreshLayout.isRefreshing = false - } - SharedItems.getUnRead() - items = SharedItems.focusedItems - handleListResult() - } - } - - private fun getRead(appendResults: Boolean = false) { - CoroutineScope(Dispatchers.Main).launch { - if (appendResults || !SharedItems.fetchedAll) { - binding.swipeRefreshLayout.isRefreshing = true - getReadItems(applicationContext, api, db, itemsNumber, offset) - binding.swipeRefreshLayout.isRefreshing = false - } - SharedItems.getAll() - items = SharedItems.focusedItems - handleListResult() - } - } - - private fun getStarred(appendResults: Boolean = false) { - CoroutineScope(Dispatchers.Main).launch { - if (appendResults || !SharedItems.fetchedStarred) { - binding.swipeRefreshLayout.isRefreshing = true - getStarredItems(applicationContext, api, db, itemsNumber, offset) - binding.swipeRefreshLayout.isRefreshing = false - } - SharedItems.getStarred() - items = SharedItems.focusedItems - handleListResult() - } - } - - private fun handleListResult(appendResults: Boolean = false) { - if (appendResults) { - val oldManager = binding.recyclerView.layoutManager - firstVisible = when (oldManager) { - is StaggeredGridLayoutManager -> - oldManager.findFirstCompletelyVisibleItemPositions(null).last() - is GridLayoutManager -> - oldManager.findFirstCompletelyVisibleItemPosition() - else -> 0 - } - } - - if (recyclerAdapter == null) { - if (shouldBeCardView) { - recyclerAdapter = - ItemCardAdapter( - this, - items, - api, - db, - customTabActivityHelper, - internalBrowser, - articleViewer, - fullHeightCards, - appColors, - userIdentifier, - config - ) { - updateItems(it) - } - } else { - recyclerAdapter = - ItemListAdapter( - this, - items, - api, - db, - customTabActivityHelper, - internalBrowser, - articleViewer, - userIdentifier, - appColors, - config - ) { - updateItems(it) - } - - binding.recyclerView.addItemDecoration( - DividerItemDecoration( - this@HomeActivity, - DividerItemDecoration.VERTICAL - ) - ) - } - binding.recyclerView.adapter = recyclerAdapter - } else { - (recyclerAdapter as ItemsAdapter<*>).updateAllItems() - } - - reloadBadges() - mayBeEmpty() - } - - private fun reloadBadges() { - if (displayUnreadCount || displayAllCount) { - CoroutineScope(Dispatchers.Main).launch { - reloadBadges(applicationContext, api) - reloadBadgeContent() - } - } - } - - private fun reloadBadgeContent() { - if (displayUnreadCount) { - tabNewBadge - .setText(SharedItems.badgeUnread.toString()) - .maybeShow() - } - if (displayAllCount) { - tabArchiveBadge - .setText(SharedItems.badgeAll.toString()) - .maybeShow() - tabStarredBadge - .setText(SharedItems.badgeStarred.toString()) - .maybeShow() - } - } - - private fun reloadTagsBadges() { - tagsBadge.forEach { - binding.mainDrawer.updateBadge(it.key, StringHolder(it.value.toString())) - } - binding.mainDrawer.resetDrawerContent() - } - - private fun calculateNoOfColumns(): Int { - val displayMetrics = resources.displayMetrics - val dpWidth = displayMetrics.widthPixels / displayMetrics.density - return (dpWidth / 300).toInt() - } - - override fun onQueryTextChange(p0: String?): Boolean { - if (p0.isNullOrBlank()) { - SharedItems.searchFilter = null - getElementsAccordingToTab() - fetchOnEmptyList() - } - return false - } - - override fun onQueryTextSubmit(p0: String?): Boolean { - SharedItems.searchFilter = p0 - getElementsAccordingToTab() - fetchOnEmptyList() - return false - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - val inflater = menuInflater - inflater.inflate(R.menu.home_menu, menu) - - val searchItem = menu.findItem(R.id.action_search) - val searchView = searchItem.getActionView() as SearchView - searchView.setOnQueryTextListener(this) - - return true - } - - private fun needsConfirmation(titleRes: Int, messageRes: Int, doFn: () -> Unit) { - AlertDialog.Builder(this@HomeActivity) - .setMessage(messageRes) - .setTitle(titleRes) - .setPositiveButton(android.R.string.ok) { _, _ -> doFn() } - .setNegativeButton(android.R.string.cancel) { _, _ -> } - .create() - .show() - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.refresh -> { - if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) { - needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) { - api.update().enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - Toast.makeText( - this@HomeActivity, - R.string.refresh_success_response, Toast.LENGTH_LONG - ) - .show() - } - - override fun onFailure(call: Call, t: Throwable) { - Toast.makeText( - this@HomeActivity, - R.string.refresh_failer_message, - Toast.LENGTH_SHORT - ).show() - } - }) - Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show() - } - return true - } else { - return false - } - } - R.id.readAll -> { - if (elementsShown == UNREAD_SHOWN) { - needsConfirmation(R.string.readAll, R.string.markall_dialog_message) { - binding.swipeRefreshLayout.isRefreshing = true - - if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) { - CoroutineScope(Dispatchers.Main).launch { - val success = readAll(applicationContext, api, db) - if (success) { - Toast.makeText( - this@HomeActivity, - R.string.all_posts_read, - Toast.LENGTH_SHORT - ).show() - tabNewBadge.removeBadge() - - handleDrawerItems() - - getElementsAccordingToTab() - } else { - Toast.makeText( - this@HomeActivity, - R.string.all_posts_not_read, - Toast.LENGTH_SHORT - ).show() - } - handleListResult() - binding.swipeRefreshLayout.isRefreshing = false - } - } - } - } - return true - } - R.id.action_disconnect -> { - return Config.logoutAndRedirect(this, this@HomeActivity, editor) - } - else -> return super.onOptionsItemSelected(item) - } - } - - private fun maxItemNumber(): Int = - when (elementsShown) { - UNREAD_SHOWN -> SharedItems.badgeUnread - READ_SHOWN -> SharedItems.badgeAll - FAV_SHOWN -> SharedItems.badgeStarred - else -> SharedItems.badgeUnread // if !elementsShown then unread are fetched. - } - - private fun updateItems(adapterItems: ArrayList) { - items = adapterItems - } - - private fun handleRecurringTask() { - if (periodicRefresh) { - val myConstraints = Constraints.Builder() - .setRequiresBatteryNotLow(true) - .setRequiresCharging(refreshWhenChargingOnly) - .setRequiresStorageNotLow(true) - .build() - - val backgroundWork = - PeriodicWorkRequestBuilder(refreshMinutes, TimeUnit.MINUTES) - .setConstraints(myConstraints) - .addTag("selfoss-loading") - .build() - - WorkManager.getInstance(baseContext).enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork) - } - } - - private fun handleOfflineActions() { - fun doAndReportOnFail(call: Call, action: ActionEntity) { - call.enqueue(object: Callback { - override fun onResponse( - call: Call, - response: Response - ) { - thread { - db.actionsDao().delete(action) - } - } - - override fun onFailure(call: Call, t: Throwable) { - } - }) - } - - if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) { - CoroutineScope(Dispatchers.Main).launch { - 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) - } - } - } - } - } -} - diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/ImageActivity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/ImageActivity.kt deleted file mode 100644 index a2078e0..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/ImageActivity.kt +++ /dev/null @@ -1,53 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import android.os.Bundle -import android.view.MenuItem -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.viewpager2.adapter.FragmentStateAdapter -import apps.amine.bou.readerforselfoss.databinding.ActivityImageBinding -import apps.amine.bou.readerforselfoss.fragments.ImageFragment - -class ImageActivity : AppCompatActivity() { - private lateinit var allImages : ArrayList - 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 - position = intent.getIntExtra("position", 0) - - binding.pager.adapter = ScreenSlidePagerAdapter(this) - binding.pager.setCurrentItem(position, false) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - - return super.onOptionsItemSelected(item) - } - - private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { - - override fun getItemCount(): Int = allImages.size - - override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position]) - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/LoginActivity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/LoginActivity.kt deleted file mode 100644 index 5bc5e29..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/LoginActivity.kt +++ /dev/null @@ -1,294 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.os.Bundle -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import android.text.TextUtils -import android.view.Menu -import android.view.MenuItem -import android.view.View -import android.view.inputmethod.EditorInfo -import android.widget.TextView -import androidx.preference.PreferenceManager -import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi -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.utils.isBaseUrlValid -import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible -import com.mikepenz.aboutlibraries.LibsBuilder -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response - -class LoginActivity : AppCompatActivity() { - - private var inValidCount: Int = 0 - private var isWithSelfSignedCert = false - private var isWithLogin = false - private var isWithHTTPLogin = false - - private lateinit var settings: SharedPreferences - private lateinit var editor: SharedPreferences.Editor - private lateinit var userIdentifier: String - private lateinit var appColors: AppColors - private lateinit var binding: ActivityLoginBinding - - override fun onCreate(savedInstanceState: Bundle?) { - appColors = AppColors(this@LoginActivity) - - super.onCreate(savedInstanceState) - binding = ActivityLoginBinding.inflate(layoutInflater) - val view = binding.root - - setContentView(view) - - setSupportActionBar(binding.toolbar) - - handleBaseUrlFail() - - settings = PreferenceManager.getDefaultSharedPreferences(applicationContext) - userIdentifier = settings.getString("unique_id", "")!! - - editor = settings.edit() - - if (settings.getString("url", "")!!.isNotEmpty()) { - goToMain() - } - - handleActions() - } - - private fun handleActions() { - - binding.withSelfhostedCert.setOnCheckedChangeListener { _, b -> - isWithSelfSignedCert = !isWithSelfSignedCert - val visi: Int = if (b) View.VISIBLE else View.GONE - - binding.warningText.visibility = visi - } - - binding.passwordView.setOnEditorActionListener( - TextView.OnEditorActionListener { _, id, _ -> - if (id == R.id.loginView || id == EditorInfo.IME_NULL) { - attemptLogin() - return@OnEditorActionListener true - } - false - } - ) - - binding.signInButton.setOnClickListener { attemptLogin() } - - binding.withLogin.setOnCheckedChangeListener { _, b -> - isWithLogin = !isWithLogin - val visi: Int = if (b) View.VISIBLE else View.GONE - - binding.loginView.visibility = visi - binding.passwordView.visibility = visi - } - - binding.withHttpLogin.setOnCheckedChangeListener { _, b -> - isWithHTTPLogin = !isWithHTTPLogin - val visi: Int = if (b) View.VISIBLE else View.GONE - - binding.httpLoginView.visibility = visi - binding.httpPasswordView.visibility = visi - } - } - - private fun handleBaseUrlFail() { - if (intent.getBooleanExtra("baseUrlFail", false)) { - val alertDialog = AlertDialog.Builder(this).create() - alertDialog.setTitle(getString(R.string.warning_wrong_url)) - alertDialog.setMessage(getString(R.string.base_url_error)) - alertDialog.setButton( - AlertDialog.BUTTON_NEUTRAL, - "OK" - ) { dialog, _ -> dialog.dismiss() } - alertDialog.show() - } - } - - private fun goToMain() { - val intent = Intent(this, HomeActivity::class.java) - startActivity(intent) - finish() - } - - private fun attemptLogin() { - - // Reset errors. - binding.urlView.error = null - binding.loginView.error = null - binding.httpLoginView.error = null - binding.passwordView.error = null - binding.httpPasswordView.error = null - - // Store values at the time of the login attempt. - val url = binding.urlView.text.toString() - val login = binding.loginView.text.toString() - val httpLogin = binding.httpLoginView.text.toString() - val password = binding.passwordView.text.toString() - val httpPassword = binding.httpPasswordView.text.toString() - - var cancel = false - var focusView: View? = null - - if (!url.isBaseUrlValid(this@LoginActivity)) { - binding.urlView.error = getString(R.string.login_url_problem) - focusView = binding.urlView - cancel = true - inValidCount++ - if (inValidCount == 3) { - val alertDialog = AlertDialog.Builder(this).create() - alertDialog.setTitle(getString(R.string.warning_wrong_url)) - alertDialog.setMessage(getString(R.string.text_wrong_url)) - alertDialog.setButton( - AlertDialog.BUTTON_NEUTRAL, - "OK" - ) { dialog, _ -> dialog.dismiss() } - alertDialog.show() - inValidCount = 0 - } - } - - if (isWithLogin) { - if (TextUtils.isEmpty(password)) { - binding.passwordView.error = getString(R.string.error_invalid_password) - focusView = binding.passwordView - cancel = true - } - - if (TextUtils.isEmpty(login)) { - binding.loginView.error = getString(R.string.error_field_required) - focusView = binding.loginView - cancel = true - } - } - - if (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 - } - } - - if (cancel) { - focusView?.requestFocus() - } else { - showProgress(true) - - editor.putString("url", url) - editor.putString("login", login) - editor.putString("httpUserName", httpLogin) - editor.putString("password", password) - editor.putString("httpPassword", httpPassword) - editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert) - editor.apply() - - val api = SelfossApi( - this, - this@LoginActivity, - isWithSelfSignedCert, - -1L - ) - - if (this@LoginActivity.isNetworkAccessible(this@LoginActivity.findViewById(R.id.loginForm))) { - api.login().enqueue(object : Callback { - private fun preferenceError(t: Throwable) { - editor.remove("url") - editor.remove("login") - editor.remove("httpUserName") - editor.remove("password") - 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 onResponse( - call: Call, - response: Response - ) { - if (response.body() != null && response.body()!!.isSuccess) { - goToMain() - } else { - preferenceError(Exception("No response body...")) - } - } - - override fun onFailure(call: Call, t: Throwable) { - preferenceError(t) - } - }) - } else { - showProgress(false) - } - } - } - - private fun showProgress(show: Boolean) { - val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime) - - binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE - binding.loginForm - .animate() - .setDuration(shortAnimTime.toLong()) - .alpha( - if (show) 0F else 1F - ).setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE - } - } - ) - - binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE - binding.loginProgress - .animate() - .setDuration(shortAnimTime.toLong()) - .alpha( - if (show) 1F else 0F - ).setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE - } - } - ) - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.login_menu, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.about -> { - LibsBuilder() - .withAboutIconShown(true) - .withAboutVersionShown(true) - .start(this) - true - } - else -> super.onOptionsItemSelected(item) - } - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/MainActivity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/MainActivity.kt deleted file mode 100644 index a92179a..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/MainActivity.kt +++ /dev/null @@ -1,23 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import apps.amine.bou.readerforselfoss.databinding.ActivityMainBinding - -class MainActivity : AppCompatActivity() { - private lateinit var binding: ActivityMainBinding - - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) - val view = binding.root - setContentView(view) - - val intent = Intent(this, LoginActivity::class.java) - - startActivity(intent) - finish() - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/MyApp.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/MyApp.kt deleted file mode 100644 index 2808320..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/MyApp.kt +++ /dev/null @@ -1,101 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import android.app.NotificationChannel -import android.app.NotificationManager -import android.content.Context -import android.graphics.drawable.Drawable -import android.net.Uri -import android.os.Build -import androidx.preference.PreferenceManager -import android.widget.ImageView -import androidx.multidex.MultiDexApplication -import apps.amine.bou.readerforselfoss.utils.Config -import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth -import com.bumptech.glide.Glide -import com.bumptech.glide.request.RequestOptions -import com.ftinc.scoop.Scoop -import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader -import com.mikepenz.materialdrawer.util.DrawerImageLoader -import java.util.UUID.randomUUID - -class MyApp : MultiDexApplication() { - private lateinit var config: Config - - override fun onCreate() { - super.onCreate() - config = Config(baseContext) - - val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) - if (prefs.getString("unique_id", "")!!.isEmpty()) { - val editor = prefs.edit() - editor.putString("unique_id", randomUUID().toString()) - editor.apply() - } - - initDrawerImageLoader() - - initTheme() - - tryToHandleBug() - - handleNotificationChannels() - } - - private fun handleNotificationChannels() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - - val name = getString(R.string.notification_channel_sync) - val importance = NotificationManager.IMPORTANCE_LOW - val mChannel = NotificationChannel(Config.syncChannelId, name, importance) - - val newItemsChannelname = getString(R.string.new_items_channel_sync) - val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT - val newItemsChannelmChannel = NotificationChannel(Config.newItemsChannelId, newItemsChannelname, newItemsChannelimportance) - - notificationManager.createNotificationChannel(mChannel) - notificationManager.createNotificationChannel(newItemsChannelmChannel) - } - } - - private fun initDrawerImageLoader() { - DrawerImageLoader.init(object : AbstractDrawerImageLoader() { - override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) { - Glide.with(imageView.context) - .loadMaybeBasicAuth(config, uri.toString()) - .apply(RequestOptions.fitCenterTransform().placeholder(placeholder)) - .into(imageView) - } - - override fun cancel(imageView: ImageView) { - Glide.with(imageView.context).clear(imageView) - } - - override fun placeholder(ctx: Context, tag: String?): Drawable { - return baseContext.resources.getDrawable(R.mipmap.ic_launcher) - } - }) - } - - private fun initTheme() { - Scoop.waffleCone() - .addFlavor(getString(R.string.default_theme), R.style.NoBar, true) - .addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark, false) - .setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this)) - .initialize() - } - - private fun tryToHandleBug() { - val oldHandler = Thread.getDefaultUncaughtExceptionHandler() - - Thread.setDefaultUncaughtExceptionHandler { thread, e -> - if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any { - it.toString().contains("android.view.ViewDebug") - }) { - Unit - } else { - oldHandler.uncaughtException(thread, e) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/ReaderActivity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/ReaderActivity.kt deleted file mode 100644 index b68dce5..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/ReaderActivity.kt +++ /dev/null @@ -1,265 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import android.content.Context -import android.content.SharedPreferences -import android.graphics.Color -import android.os.Bundle -import android.view.KeyEvent -import androidx.preference.PreferenceManager -import androidx.appcompat.app.AppCompatActivity -import android.view.Menu -import android.view.MenuItem -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.room.Room -import androidx.viewpager2.adapter.FragmentStateAdapter -import androidx.viewpager2.widget.ViewPager2 -import apps.amine.bou.readerforselfoss.api.selfoss.Item -import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi -import apps.amine.bou.readerforselfoss.databinding.ActivityReaderBinding -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.Toppings -import apps.amine.bou.readerforselfoss.utils.Config -import apps.amine.bou.readerforselfoss.utils.SharedItems -import apps.amine.bou.readerforselfoss.utils.toggleStar -import com.ftinc.scoop.Scoop - -class ReaderActivity : AppCompatActivity() { - - private var markOnScroll: Boolean = false - private var currentItem: Int = 0 - private lateinit var userIdentifier: String - private lateinit var appColors: AppColors - - private lateinit var api: SelfossApi - - 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 - private val JUSTIFY = 1 - private val ALIGN_LEFT = 2 - - private fun showMenuItem(willAddToFavorite: Boolean) { - if (willAddToFavorite) { - toolbarMenu.findItem(R.id.star).icon.setTint(Color.WHITE) - } else { - toolbarMenu.findItem(R.id.star).icon.setTint(Color.RED) - } - } - - private fun canFavorite() { - showMenuItem(true) - } - - private fun canRemoveFromFavorite() { - showMenuItem(false) - } - - private lateinit var editor: SharedPreferences.Editor - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - appColors = AppColors(this) - binding = ActivityReaderBinding.inflate(layoutInflater) - val view = binding.root - - 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() - scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar) - scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value) - - setSupportActionBar(binding.toolBar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setDisplayShowHomeEnabled(true) - - val settings = - getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) - - prefs = PreferenceManager.getDefaultSharedPreferences(this) - editor = prefs.edit() - - userIdentifier = prefs.getString("unique_id", "")!! - markOnScroll = prefs.getBoolean("mark_on_scroll", false) - activeAlignment = prefs.getInt("text_align", JUSTIFY) - - api = SelfossApi( - this, - this@ReaderActivity, - settings.getBoolean("isSelfSignedCert", false), - prefs.getString("api_timeout", "-1")!!.toLong() - ) - - if (allItems.isEmpty()) { - finish() - } - - currentItem = intent.getIntExtra("currentItem", 0) - - readItem(allItems[currentItem]) - - binding.pager.adapter = ScreenSlidePagerAdapter(this) - binding.pager.setCurrentItem(currentItem, false) - } - - override fun onResume() { - super.onResume() - - binding.indicator.setViewPager(binding.pager) - } - - private fun readItem(item: Item) { - if (markOnScroll) { - SharedItems.readItem(applicationContext, api, db, item) - } - } - - override fun onSaveInstanceState(oldInstanceState: Bundle) { - super.onSaveInstanceState(oldInstanceState) - oldInstanceState.clear() - } - - private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : - FragmentStateAdapter(fa) { - - override fun getItemCount(): Int = allItems.size - - override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position]) - - } - - override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { - return when (keyCode) { - KeyEvent.KEYCODE_VOLUME_DOWN -> { - val currentFragment = supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment - currentFragment.scrollDown() - true - } - KeyEvent.KEYCODE_VOLUME_UP -> { - val currentFragment = supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment - currentFragment.scrollUp() - true - } - else -> { - super.onKeyDown(keyCode, event) - } - } - } - - private 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 { - val inflater = menuInflater - inflater.inflate(R.menu.reader_menu, menu) - toolbarMenu = menu - - if (allItems.isNotEmpty() && allItems[currentItem].starred) { - canRemoveFromFavorite() - } else { - canFavorite() - } - if (activeAlignment == JUSTIFY) { - alignmentMenu(false) - } else { - alignmentMenu(true) - } - - binding.pager.registerOnPageChangeCallback( - object : ViewPager2.OnPageChangeCallback() { - - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - - if (allItems[position].starred) { - canRemoveFromFavorite() - } else { - canFavorite() - } - readItem(allItems[position]) - } - } - ) - - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - fun afterSave() { - allItems[binding.pager.currentItem] = - allItems[binding.pager.currentItem].toggleStar() - canRemoveFromFavorite() - } - - fun afterUnsave() { - allItems[binding.pager.currentItem] = allItems[binding.pager.currentItem].toggleStar() - canFavorite() - } - - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - R.id.star -> { - if (allItems[binding.pager.currentItem].starred) { - SharedItems.unstarItem( - this@ReaderActivity, - api, - db, - allItems[binding.pager.currentItem] - ) - afterUnsave() - } else { - SharedItems.starItem( - this@ReaderActivity, - api, - db, - allItems[binding.pager.currentItem] - ) - afterSave() - } - } - R.id.align_left -> { - editor.putInt("text_align", ALIGN_LEFT) - editor.apply() - alignmentMenu(true) - refreshFragment() - } - R.id.align_justify -> { - editor.putInt("text_align", JUSTIFY) - editor.apply() - alignmentMenu(false) - refreshFragment() - } - } - return super.onOptionsItemSelected(item) - } - - private fun refreshFragment() { - finish() - overridePendingTransition(0, 0) - startActivity(intent) - overridePendingTransition(0, 0) - } - - companion object { - var allItems: ArrayList = ArrayList() - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/SourcesActivity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/SourcesActivity.kt deleted file mode 100644 index 51f6146..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/SourcesActivity.kt +++ /dev/null @@ -1,109 +0,0 @@ -package apps.amine.bou.readerforselfoss - -import android.content.Context -import android.content.Intent -import android.content.res.ColorStateList -import android.os.Bundle -import androidx.preference.PreferenceManager -import androidx.appcompat.app.AppCompatActivity -import androidx.recyclerview.widget.LinearLayoutManager -import android.widget.Toast -import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter -import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi -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.Toppings -import apps.amine.bou.readerforselfoss.utils.Config -import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible -import com.ftinc.scoop.Scoop -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response - -class SourcesActivity : AppCompatActivity() { - - private lateinit var appColors: AppColors - private lateinit var binding: ActivitySourcesBinding - - override fun onCreate(savedInstanceState: Bundle?) { - appColors = AppColors(this@SourcesActivity) - binding = ActivitySourcesBinding.inflate(layoutInflater) - val view = binding.root - - val scoop = Scoop.getInstance() - scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar) - scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value) - - super.onCreate(savedInstanceState) - - setContentView(view) - - setSupportActionBar(binding.toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setDisplayShowHomeEnabled(true) - - binding.fab.rippleColor = appColors.colorAccentDark - binding.fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent) - } - - override fun onStop() { - super.onStop() - binding.recyclerView.clearOnScrollListeners() - } - - override fun onResume() { - super.onResume() - val mLayoutManager = LinearLayoutManager(this) - - val settings = - getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) - val prefs = PreferenceManager.getDefaultSharedPreferences(this) - - val api = SelfossApi( - this, - this@SourcesActivity, - settings.getBoolean("isSelfSignedCert", false), - prefs.getString("api_timeout", "-1")!!.toLong() - ) - var items: ArrayList = ArrayList() - - binding.recyclerView.setHasFixedSize(true) - binding.recyclerView.layoutManager = mLayoutManager - - if (this@SourcesActivity.isNetworkAccessible(binding.recyclerView)) { - api.sources.enqueue(object : Callback> { - override fun onResponse( - call: Call>, - response: Response> - ) { - if (response.body() != null && response.body()!!.isNotEmpty()) { - items = response.body() as ArrayList - } - 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() - } - } - - override fun onFailure(call: Call>, t: Throwable) { - Toast.makeText( - this@SourcesActivity, - R.string.cant_get_sources, - Toast.LENGTH_SHORT - ).show() - } - }) - } - - binding.fab.setOnClickListener { - startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java)) - } - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemCardAdapter.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemCardAdapter.kt deleted file mode 100644 index a3abce0..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemCardAdapter.kt +++ /dev/null @@ -1,153 +0,0 @@ -package apps.amine.bou.readerforselfoss.adapters - -import android.app.Activity -import android.content.Context -import androidx.recyclerview.widget.RecyclerView -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView.ScaleType -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.databinding.CardItemBinding -import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase -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.customtabs.CustomTabActivityHelper -import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop -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.openItemUrl -import apps.amine.bou.readerforselfoss.utils.shareLink -import apps.amine.bou.readerforselfoss.utils.sourceAndDateText -import apps.amine.bou.readerforselfoss.utils.toTextDrawableString -import com.amulyakhare.textdrawable.TextDrawable -import com.amulyakhare.textdrawable.util.ColorGenerator -import com.bumptech.glide.Glide - -class ItemCardAdapter( - override val app: Activity, - override var items: ArrayList, - override val api: SelfossApi, - override val db: AppDatabase, - private val helper: CustomTabActivityHelper, - private val internalBrowser: Boolean, - private val articleViewer: Boolean, - private val fullHeightCards: Boolean, - override val appColors: AppColors, - override val userIdentifier: String, - override val config: Config, - override val updateItems: (ArrayList) -> Unit -) : ItemsAdapter() { - private val c: Context = app.baseContext - private val generator: ColorGenerator = ColorGenerator.MATERIAL - private val imageMaxHeight: Int = - c.resources.getDimension(R.dimen.card_image_max_height).toInt() - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - with(holder) { - val itm = items[position] - - binding.favButton.isSelected = itm.starred - binding.title.text = itm.getTitleDecoded() - - binding.title.setOnTouchListener(LinkOnTouchListener()) - - binding.title.setLinkTextColor(appColors.colorAccent) - - binding.sourceTitleAndDate.text = itm.sourceAndDateText() - - 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) - } - } - } - - override fun getItemCount(): Int { - return items.size - } - - inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root) { - init { - handleClickListeners() - handleCustomTabActions() - } - - private fun handleClickListeners() { - - binding.favButton.setOnClickListener { - val item = items[bindingAdapterPosition] - if (isNetworkAvailable(c)) { - if (item.starred) { - SharedItems.unstarItem(c, api, db, item) - item.starred = false - binding.favButton.isSelected = false - } else { - SharedItems.starItem(c, api, db, item) - item.starred = true - binding.favButton.isSelected = true - } - } - } - - binding.shareBtn.setOnClickListener { - val item = items[bindingAdapterPosition] - c.shareLink(item.getLinkDecoded(), item.getTitleDecoded()) - } - - binding.browserBtn.setOnClickListener { - c.openInBrowserAsNewTask(items[bindingAdapterPosition]) - } - } - - private fun handleCustomTabActions() { - val customTabsIntent = c.buildCustomTabsIntent() - helper.bindCustomTabsService(app) - - binding.root.setOnClickListener { - c.openItemUrl( - items, - bindingAdapterPosition, - items[bindingAdapterPosition].getLinkDecoded(), - customTabsIntent, - internalBrowser, - articleViewer, - app - ) - } - } - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemListAdapter.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemListAdapter.kt deleted file mode 100644 index f49de7f..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemListAdapter.kt +++ /dev/null @@ -1,105 +0,0 @@ -package apps.amine.bou.readerforselfoss.adapters - -import android.app.Activity -import android.content.Context -import androidx.recyclerview.widget.RecyclerView -import android.view.LayoutInflater -import android.view.ViewGroup -import apps.amine.bou.readerforselfoss.api.selfoss.Item -import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi -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.utils.Config -import apps.amine.bou.readerforselfoss.utils.LinkOnTouchListener -import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent -import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper -import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop -import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable -import apps.amine.bou.readerforselfoss.utils.openItemUrl -import apps.amine.bou.readerforselfoss.utils.sourceAndDateText -import apps.amine.bou.readerforselfoss.utils.toTextDrawableString -import com.amulyakhare.textdrawable.TextDrawable -import com.amulyakhare.textdrawable.util.ColorGenerator -import kotlin.collections.ArrayList - -class ItemListAdapter( - override val app: Activity, - override var items: ArrayList, - override val api: SelfossApi, - override val db: AppDatabase, - private val helper: CustomTabActivityHelper, - private val internalBrowser: Boolean, - private val articleViewer: Boolean, - override val userIdentifier: String, - override val appColors: AppColors, - override val config: Config, - override val updateItems: (ArrayList) -> Unit -) : ItemsAdapter() { - private val generator: ColorGenerator = ColorGenerator.MATERIAL - private val c: Context = app.baseContext - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - with(holder) { - val itm = items[position] - - binding.title.text = itm.getTitleDecoded() - - binding.title.setOnTouchListener(LinkOnTouchListener()) - - binding.title.setLinkTextColor(appColors.colorAccent) - - binding.sourceTitleAndDate.text = itm.sourceAndDateText() - - if (itm.getThumbnail(c).isEmpty()) { - - if (itm.getIcon(c).isEmpty()) { - val color = generator.getColor(itm.getSourceTitle()) - - 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 { - c.bitmapCenterCrop(config, itm.getThumbnail(c), binding.itemImage) - } - } - } - - override fun getItemCount(): Int = items.size - - inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) { - - init { - handleCustomTabActions() - } - - private fun handleCustomTabActions() { - val customTabsIntent = c.buildCustomTabsIntent() - helper.bindCustomTabsService(app) - - binding.root.setOnClickListener { - c.openItemUrl( - items, - bindingAdapterPosition, - items[bindingAdapterPosition].getLinkDecoded(), - customTabsIntent, - internalBrowser, - articleViewer, - app - ) - } - } - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemsAdapter.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemsAdapter.kt deleted file mode 100644 index 8d803aa..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemsAdapter.kt +++ /dev/null @@ -1,119 +0,0 @@ -package apps.amine.bou.readerforselfoss.adapters - -import android.app.Activity -import android.graphics.Color -import android.widget.TextView -import androidx.recyclerview.widget.RecyclerView -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.persistence.database.AppDatabase -import apps.amine.bou.readerforselfoss.themes.AppColors -import apps.amine.bou.readerforselfoss.utils.Config -import apps.amine.bou.readerforselfoss.utils.SharedItems -import com.google.android.material.snackbar.Snackbar - -abstract class ItemsAdapter : RecyclerView.Adapter() { - abstract var items: ArrayList - abstract val api: SelfossApi - abstract val db: AppDatabase - abstract val userIdentifier: String - abstract val app: Activity - abstract val appColors: AppColors - abstract val config: Config - abstract val updateItems: (ArrayList) -> Unit - - fun updateAllItems() { - items = SharedItems.focusedItems - notifyDataSetChanged() - updateItems(items) - } - - private fun unmarkSnackbar(i: Item, position: Int) { - val s = Snackbar - .make( - app.findViewById(R.id.coordLayout), - R.string.marked_as_read, - Snackbar.LENGTH_LONG - ) - .setAction(R.string.undo_string) { - SharedItems.unreadItem(app, api, db, i) - if (SharedItems.displayedItems == "unread") { - addItemAtIndex(i, position) - } 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() - } - - 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] - SharedItems.readItem(app, api, db, i) - if (SharedItems.displayedItems == "unread") { - items.remove(i) - notifyItemRemoved(position) - updateItems(items) - } else { - notifyItemChanged(position) - } - unmarkSnackbar(i, position) - } - - private fun unreadItemAtIndex(position: Int) { - SharedItems.unreadItem(app, api, db, items[position]) - notifyItemChanged(position) - markSnackbar(position) - } - - fun addItemAtIndex(item: Item, position: Int) { - items.add(position, item) - notifyItemInserted(position) - updateItems(items) - - } - - fun addItemsAtEnd(newItems: List) { - val oldSize = items.size - items.addAll(newItems) - notifyItemRangeInserted(oldSize, newItems.size) - updateItems(items) - - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/SourcesListAdapter.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/SourcesListAdapter.kt deleted file mode 100644 index 5f2806e..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/SourcesListAdapter.kt +++ /dev/null @@ -1,106 +0,0 @@ -package apps.amine.bou.readerforselfoss.adapters - -import android.app.Activity -import android.content.Context -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.recyclerview.widget.RecyclerView -import android.view.LayoutInflater -import android.view.ViewGroup -import android.widget.Button -import android.widget.Toast -import apps.amine.bou.readerforselfoss.R -import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi -import apps.amine.bou.readerforselfoss.api.selfoss.Source -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.network.isNetworkAccessible -import apps.amine.bou.readerforselfoss.utils.toTextDrawableString -import com.amulyakhare.textdrawable.TextDrawable -import com.amulyakhare.textdrawable.util.ColorGenerator -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response - -class SourcesListAdapter( - private val app: Activity, - private val items: ArrayList, - private val api: SelfossApi -) : RecyclerView.Adapter() { - private val c: Context = app.baseContext - private val generator: ColorGenerator = ColorGenerator.MATERIAL - private lateinit var config: Config - private lateinit var binding: SourceListItemBinding - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return ViewHolder(binding.root) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val itm = items[position] - config = Config(c) - - if (itm.getIcon(c).isEmpty()) { - val color = generator.getColor(itm.getTitleDecoded()) - - val drawable = - TextDrawable - .builder() - .round() - .build(itm.getTitleDecoded().toTextDrawableString(c), color) - binding.itemImage.setImageDrawable(drawable) - } else { - c.circularBitmapDrawable(config, itm.getIcon(c), binding.itemImage) - } - - binding.sourceTitle.text = itm.getTitleDecoded() - } - - override fun getItemCount(): Int = items.size - - inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) { - - init { - handleClickListeners() - } - - private fun handleClickListeners() { - - val deleteBtn: Button = mView.findViewById(R.id.deleteBtn) - - deleteBtn.setOnClickListener { - if (c.isNetworkAccessible(null)) { - val (id) = items[adapterPosition] - api.deleteSource(id).enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (response.body() != null && response.body()!!.isSuccess) { - items.removeAt(adapterPosition) - notifyItemRemoved(adapterPosition) - notifyItemRangeChanged(adapterPosition, itemCount) - } else { - Toast.makeText( - app, - R.string.can_delete_source, - Toast.LENGTH_SHORT - ).show() - } - } - - override fun onFailure(call: Call, t: Throwable) { - Toast.makeText( - app, - R.string.can_delete_source, - Toast.LENGTH_SHORT - ).show() - } - }) - } - } - } - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryApi.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryApi.kt deleted file mode 100644 index 8e56ff5..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryApi.kt +++ /dev/null @@ -1,35 +0,0 @@ -package apps.amine.bou.readerforselfoss.api.mercury - -import com.google.gson.GsonBuilder -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Call -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory - -class MercuryApi() { - private val service: MercuryService - - init { - - val interceptor = HttpLoggingInterceptor() - interceptor.level = HttpLoggingInterceptor.Level.NONE - val client = OkHttpClient.Builder().addInterceptor(interceptor).build() - - val gson = GsonBuilder() - .setLenient() - .create() - val retrofit = - Retrofit - .Builder() - .baseUrl("https://www.amine-bou.fr") - .client(client) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build() - service = retrofit.create(MercuryService::class.java) - } - - fun parseUrl(url: String): Call { - return service.parseUrl(url) - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryModels.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryModels.kt deleted file mode 100644 index a1b7fbc..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryModels.kt +++ /dev/null @@ -1,59 +0,0 @@ -package apps.amine.bou.readerforselfoss.api.mercury - -import android.os.Parcel -import android.os.Parcelable -import com.google.gson.annotations.SerializedName - -class ParsedContent( - @SerializedName("title") val title: String, - @SerializedName("content") val content: String?, - @SerializedName("date_published") val date_published: String, - @SerializedName("lead_image_url") val lead_image_url: String?, - @SerializedName("dek") val dek: String, - @SerializedName("url") val url: String, - @SerializedName("domain") val domain: String, - @SerializedName("excerpt") val excerpt: String, - @SerializedName("total_pages") val total_pages: Int, - @SerializedName("rendered_pages") val rendered_pages: Int, - @SerializedName("next_page_url") val next_page_url: String -) : Parcelable { - - companion object { - @JvmField - val CREATOR: Parcelable.Creator = - object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): ParsedContent = ParsedContent(source) - override fun newArray(size: Int): Array = arrayOfNulls(size) - } - } - - constructor(source: Parcel) : this( - title = source.readString().orEmpty(), - content = source.readString(), - date_published = source.readString().orEmpty(), - lead_image_url = source.readString(), - dek = source.readString().orEmpty(), - url = source.readString().orEmpty(), - domain = source.readString().orEmpty(), - excerpt = source.readString().orEmpty(), - total_pages = source.readInt(), - rendered_pages = source.readInt(), - next_page_url = source.readString().orEmpty() - ) - - override fun describeContents() = 0 - - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeString(title) - dest.writeString(content) - dest.writeString(date_published) - dest.writeString(lead_image_url) - dest.writeString(dek) - dest.writeString(url) - dest.writeString(domain) - dest.writeString(excerpt) - dest.writeInt(total_pages) - dest.writeInt(rendered_pages) - dest.writeString(next_page_url) - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryService.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryService.kt deleted file mode 100644 index 5d8a7b2..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryService.kt +++ /dev/null @@ -1,11 +0,0 @@ -package apps.amine.bou.readerforselfoss.api.mercury - -import retrofit2.Call -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.Query - -interface MercuryService { - @GET("parser.php") - fun parseUrl(@Query("link") link: String): Call -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/BooleanTypeAdapter.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/BooleanTypeAdapter.kt deleted file mode 100644 index 7f2d517..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/BooleanTypeAdapter.kt +++ /dev/null @@ -1,22 +0,0 @@ -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 BooleanTypeAdapter : JsonDeserializer { - - @Throws(JsonParseException::class) - override fun deserialize( - json: JsonElement, - typeOfT: Type, - context: JsonDeserializationContext - ): Boolean? = - try { - json.asInt == 1 - } catch (e: Exception) { - json.asBoolean - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossApi.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossApi.kt deleted file mode 100644 index b29c26e..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossApi.kt +++ /dev/null @@ -1,245 +0,0 @@ -package apps.amine.bou.readerforselfoss.api.selfoss - -import android.app.Activity -import android.content.Context -import apps.amine.bou.readerforselfoss.utils.Config -import apps.amine.bou.readerforselfoss.utils.SharedItems -import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient -import com.burgstaller.okhttp.AuthenticationCacheInterceptor -import com.burgstaller.okhttp.CachingAuthenticatorDecorator -import com.burgstaller.okhttp.DispatchingAuthenticator -import com.burgstaller.okhttp.basic.BasicAuthenticator -import com.burgstaller.okhttp.digest.CachingAuthenticator -import com.burgstaller.okhttp.digest.Credentials -import com.burgstaller.okhttp.digest.DigestAuthenticator -import com.google.gson.GsonBuilder -import okhttp3.* -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.ResponseBody.Companion.toResponseBody -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Call -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import java.net.SocketTimeoutException -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.TimeUnit - -class SelfossApi( - c: Context, - callingActivity: Activity?, - isWithSelfSignedCert: Boolean, - timeout: Long -) { - - private lateinit var service: SelfossService - private val config: Config = Config(c) - private val userName: String - private val password: String - - fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder = - if (isWithSelfSignedCert) { - getUnsafeHttpClient() - } else { - 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 = - DispatchingAuthenticator.Builder() - .with("digest", DigestAuthenticator(this)) - .with("basic", BasicAuthenticator(this)) - .build() - - fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean, timeout: Long): OkHttpClient.Builder { - val authCache = ConcurrentHashMap() - return OkHttpClient - .Builder() - .maybeWithSettingsTimeout(timeout) - .maybeWithSelfSigned(isWithSelfSignedCert) - .authenticator(CachingAuthenticatorDecorator(this, 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 { - userName = config.userLogin - password = config.userPassword - - val authenticator = - Credentials( - config.httpUserLogin, - config.httpUserPassword - ).createAuthenticator() - - val gson = - GsonBuilder() - .registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter()) - .registerTypeAdapter(SelfossTagType::class.java, SelfossTagTypeTypeAdapter()) - .setLenient() - .create() - - val logging = HttpLoggingInterceptor() - - - logging.level = HttpLoggingInterceptor.Level.NONE - val httpClient = authenticator.getHttpClien(isWithSelfSignedCert, timeout) - - 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 { - val retrofit = - Retrofit - .Builder() - .baseUrl(config.baseUrl) - .client(httpClient.build()) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build() - service = retrofit.create(SelfossService::class.java) - } catch (e: IllegalArgumentException) { - if (callingActivity != null) { - Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true) - } - } - } - - fun login(): Call = - service.loginToSelfoss(config.userLogin, config.userPassword) - - suspend fun readItems( - itemsNumber: Int, - offset: Int - ): retrofit2.Response> = - getItems("read", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset) - - suspend fun newItems( - itemsNumber: Int, - offset: Int - ): retrofit2.Response> = - getItems("unread", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset) - - suspend fun starredItems( - itemsNumber: Int, - offset: Int - ): retrofit2.Response> = - getItems("starred", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset) - - fun allItems(): Call> = - service.allItems(userName, password) - - suspend fun allNewItems(): retrofit2.Response> = - getItems("unread", null, null, null, 200, 0) - - suspend fun allReadItems(): retrofit2.Response> = - getItems("read", null, null, null, 200, 0) - - suspend fun allStarredItems(): retrofit2.Response> = - getItems("read", null, null, null, 200, 0) - - private suspend fun getItems( - type: String, - tag: String?, - sourceId: Long?, - search: String?, - items: Int, - offset: Int - ): retrofit2.Response> = - service.getItems(type, tag, sourceId, search, null, userName, password, items, offset) - - suspend fun updateItems( - updatedSince: String - ): retrofit2.Response> = - service.getItems("read", null, null, null, updatedSince, userName, password, 200, 0) - - fun markItem(itemId: String): Call = - service.markAsRead(itemId, userName, password) - - fun unmarkItem(itemId: String): Call = - service.unmarkAsRead(itemId, userName, password) - - suspend fun readAll(ids: List): SuccessResponse = - service.markAllAsRead(ids, userName, password) - - fun starrItem(itemId: String): Call = - service.starr(itemId, userName, password) - - fun unstarrItem(itemId: String): Call = - service.unstarr(itemId, userName, password) - - suspend fun stats(): retrofit2.Response = service.stats(userName, password) - - val tags: Call> - get() = service.tags(userName, password) - - fun update(): Call = - service.update(userName, password) - - val apiVersion: Call - get() = service.version() - - val sources: Call> - get() = service.sources(userName, password) - - fun deleteSource(id: String): Call = - service.deleteSource(id, userName, password) - - fun spouts(): Call> = - service.spouts(userName, password) - - fun createSource( - title: String, - url: String, - spout: String, - tags: String, - filter: String - ): Call = - service.createSource(title, url, spout, tags, filter, userName, password) - - fun createSourceApi2( - title: String, - url: String, - spout: String, - tags: List, - filter: String - ): Call = - service.createSourceApi2(title, url, spout, tags, filter, userName, password) -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossFetching.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossFetching.kt deleted file mode 100644 index 0d38b57..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossFetching.kt +++ /dev/null @@ -1,134 +0,0 @@ -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, itemsNumber: Int) = withContext(Dispatchers.IO) { - if (isNetworkAvailable(context)) { - val response = when (SharedItems.displayedItems) { - "read" -> api.readItems(itemsNumber, 0) - "unread" -> api.newItems(itemsNumber, 0) - "starred" -> api.starredItems(itemsNumber, 0) - else -> api.readItems(itemsNumber, 0) - } - - if (response.isSuccessful) { - SharedItems.refreshFocusedItems(response.body() as ArrayList) - SharedItems.updateDatabase(db) - } - } -} - -suspend fun getReadItems(context: Context, api: SelfossApi, db: AppDatabase, itemsNumber: Int, offset: Int) = withContext(Dispatchers.IO) { - if (isNetworkAvailable(context)) { - try { - enqueueArticles(api.readItems( itemsNumber, offset), db, false) - SharedItems.fetchedAll = true - SharedItems.updateDatabase(db) - } catch (e: Throwable) {} - } -} - -suspend fun getUnreadItems(context: Context, api: SelfossApi, db: AppDatabase, itemsNumber: Int, offset: Int) = withContext(Dispatchers.IO) { - if (isNetworkAvailable(context)) { - try { - if (!SharedItems.fetchedUnread) { - SharedItems.clearDBItems(db) - } - enqueueArticles(api.newItems(itemsNumber, offset), db, false) - SharedItems.fetchedUnread = true - } catch (e: Throwable) {} - } - SharedItems.updateDatabase(db) -} - -suspend fun getStarredItems(context: Context, api: SelfossApi, db: AppDatabase, itemsNumber: Int, offset: Int) = withContext(Dispatchers.IO) { - if (isNetworkAvailable(context)) { - try { - enqueueArticles(api.starredItems(itemsNumber, 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>, db: AppDatabase, clearDatabase: Boolean) { - if (response.isSuccessful) { - if (clearDatabase) { - CoroutineScope(Dispatchers.IO).launch { - SharedItems.clearDBItems(db) - } - } - val allItems = response.body() as ArrayList - SharedItems.appendNewItems(allItems) - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossModels.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossModels.kt deleted file mode 100644 index 0988cc0..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossModels.kt +++ /dev/null @@ -1,253 +0,0 @@ -package apps.amine.bou.readerforselfoss.api.selfoss - -import android.content.Context -import android.net.Uri -import android.os.Parcel -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.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 java.util.* -import kotlin.collections.ArrayList - -private fun constructUrl(config: Config?, path: String, file: String?): String { - return if (file.isEmptyOrNullOrNullString()) { - "" - } else { - val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon() - baseUriBuilder.appendPath(path).appendPath(file) - - baseUriBuilder.toString() - } -} - -data class Tag( - @SerializedName("tag") val tag: String, - @SerializedName("color") val color: String, - @SerializedName("unread") val unread: Int -) { - fun getTitleDecoded(): String { - return Html.fromHtml(tag).toString() - } -} - -class SuccessResponse(@SerializedName("success") val success: Boolean) { - val isSuccess: Boolean - get() = success -} - -class Stats( - @SerializedName("total") val total: Int, - @SerializedName("unread") val unread: Int, - @SerializedName("starred") val starred: Int -) - -data class Spout( - @SerializedName("name") val name: 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( - @SerializedName("id") val id: String, - @SerializedName("title") val title: String, - @SerializedName("tags") val tags: SelfossTagType, - @SerializedName("spout") val spout: String, - @SerializedName("error") val error: String, - @SerializedName("icon") val icon: String -) { - var config: Config? = null - - fun getIcon(app: Context): String { - if (config == null) { - config = Config(app) - } - return constructUrl(config, "favicons", icon) - } - - fun getTitleDecoded(): String { - return Html.fromHtml(title).toString() - } -} - -data class Item( - @SerializedName("id") val id: String, - @SerializedName("datetime") val datetime: String, - @SerializedName("title") val title: String, - @SerializedName("content") val content: String, - @SerializedName("unread") var unread: Boolean, - @SerializedName("starred") var starred: Boolean, - @SerializedName("thumbnail") val thumbnail: String?, - @SerializedName("icon") val icon: String?, - @SerializedName("link") val link: String, - @SerializedName("sourcetitle") val sourcetitle: String, - @SerializedName("tags") val tags: SelfossTagType -) : Parcelable { - - var config: Config? = null - - companion object { - @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): Item = Item(source) - override fun newArray(size: Int): Array = arrayOfNulls(size) - } - } - - constructor(source: Parcel) : this( - id = source.readString().orEmpty(), - datetime = source.readString().orEmpty(), - title = source.readString().orEmpty(), - content = source.readString().orEmpty(), - unread = 0.toByte() != source.readByte(), - starred = 0.toByte() != source.readByte(), - thumbnail = source.readString(), - icon = source.readString(), - link = source.readString().orEmpty(), - sourcetitle = source.readString().orEmpty(), - tags = if (source.readParcelable(ClassLoader.getSystemClassLoader()) != null) source.readParcelable(ClassLoader.getSystemClassLoader())!! else SelfossTagType("") - ) - - override fun describeContents() = 0 - - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeString(id) - dest.writeString(datetime) - dest.writeString(title) - dest.writeString(content) - dest.writeByte((if (unread) 1 else 0)) - dest.writeByte((if (starred) 1 else 0)) - dest.writeString(thumbnail) - dest.writeString(icon) - dest.writeString(link) - dest.writeString(sourcetitle) - dest.writeParcelable(tags, flags) - } - - fun getIcon(app: Context): String { - if (config == null) { - config = Config(app) - } - return constructUrl(config, "favicons", icon) - } - - fun getThumbnail(app: Context): String { - if (config == null) { - config = Config(app) - } - return constructUrl(config, "thumbnails", thumbnail) - } - - fun getImages() : ArrayList { - val allImages = ArrayList() - - for ( image in Jsoup.parse(content).getElementsByTag("img")) { - val url = image.attr("src") - if (url.lowercase(Locale.US).contains(".jpg") || - url.lowercase(Locale.US).contains(".jpeg") || - url.lowercase(Locale.US).contains(".png") || - url.lowercase(Locale.US).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 - fun getLinkDecoded(): String { - var stringUrl: String - stringUrl = - if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) { - if (link.contains("&url=")) { - link.substringAfter("&url=") - } else { - this.link.replace("&", "&") - } - } else { - this.link.replace("&", "&") - } - - // handle :443 => https - if (stringUrl.contains(":443")) { - stringUrl = stringUrl.replace(":443", "").replace("http://", "https://") - } - - // handle url not starting with http - if (stringUrl.startsWith("//")) { - stringUrl = "http:$stringUrl" - } - - return stringUrl - } -} - -data class SelfossTagType(val tags: String) : Parcelable { - - companion object { - @JvmField val CREATOR: Parcelable.Creator = - object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): SelfossTagType = - SelfossTagType(source) - - override fun newArray(size: Int): Array = arrayOfNulls(size) - } - } - - constructor(source: Parcel) : this( - tags = source.readString().orEmpty() - ) - - override fun describeContents() = 0 - - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeString(tags) - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossService.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossService.kt deleted file mode 100644 index 60640db..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossService.kt +++ /dev/null @@ -1,141 +0,0 @@ -package apps.amine.bou.readerforselfoss.api.selfoss - -import retrofit2.Call -import retrofit2.Response -import retrofit2.http.DELETE -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.GET -import retrofit2.http.Headers -import retrofit2.http.POST -import retrofit2.http.Path -import retrofit2.http.Query - -internal interface SelfossService { - - @GET("login") - fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call - - @GET("items") - suspend fun getItems( - @Query("type") type: String, - @Query("tag") tag: String?, - @Query("source") source: Long?, - @Query("search") search: String?, - @Query("updatedsince") updatedSince: String?, - @Query("username") username: String, - @Query("password") password: String, - @Query("items") items: Int, - @Query("offset") offset: Int - ): Response> - - @GET("items") - fun allItems( - @Query("username") username: String, - @Query("password") password: String - ): Call> - - @Headers("Content-Type: application/x-www-form-urlencoded") - @POST("mark/{id}") - fun markAsRead( - @Path("id") id: String, - @Query("username") username: String, - @Query("password") password: String - ): Call - - @Headers("Content-Type: application/x-www-form-urlencoded") - @POST("unmark/{id}") - fun unmarkAsRead( - @Path("id") id: String, - @Query("username") username: String, - @Query("password") password: String - ): Call - - @FormUrlEncoded - @POST("mark") - suspend fun markAllAsRead( - @Field("ids[]") ids: List, - @Query("username") username: String, - @Query("password") password: String - ): SuccessResponse - - @Headers("Content-Type: application/x-www-form-urlencoded") - @POST("starr/{id}") - fun starr( - @Path("id") id: String, - @Query("username") username: String, - @Query("password") password: String - ): Call - - @Headers("Content-Type: application/x-www-form-urlencoded") - @POST("unstarr/{id}") - fun unstarr( - @Path("id") id: String, - @Query("username") username: String, - @Query("password") password: String - ): Call - - @GET("stats") - suspend fun stats( - @Query("username") username: String, - @Query("password") password: String - ): Response - - @GET("tags") - fun tags( - @Query("username") username: String, - @Query("password") password: String - ): Call> - - @GET("update") - fun update( - @Query("username") username: String, - @Query("password") password: String - ): Call - - @GET("sources/spouts") - fun spouts( - @Query("username") username: String, - @Query("password") password: String - ): Call> - - @GET("sources/list") - fun sources( - @Query("username") username: String, - @Query("password") password: String - ): Call> - - @GET("api/about") - fun version(): Call - - @DELETE("source/{id}") - fun deleteSource( - @Path("id") id: String, - @Query("username") username: String, - @Query("password") password: String - ): Call - - @FormUrlEncoded - @POST("source") - fun createSource( - @Field("title") title: String, - @Field("url") url: String, - @Field("spout") spout: String, - @Field("tags") tags: String, - @Field("filter") filter: String, - @Query("username") username: String, - @Query("password") password: String - ): Call - - @FormUrlEncoded - @POST("source") - fun createSourceApi2( - @Field("title") title: String, - @Field("url") url: String, - @Field("spout") spout: String, - @Field("tags[]") tags: List, - @Field("filter") filter: String, - @Query("username") username: String, - @Query("password") password: String - ): Call -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossTagTypeTypeAdapter.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossTagTypeTypeAdapter.kt deleted file mode 100644 index 4e9c3cb..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossTagTypeTypeAdapter.kt +++ /dev/null @@ -1,22 +0,0 @@ -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 { - - @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()) - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/background/background.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/background/background.kt deleted file mode 100644 index 59d5648..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/background/background.kt +++ /dev/null @@ -1,169 +0,0 @@ -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 doAndReportOnFail(call: Call, action: ActionEntity) { - call.enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - thread { - db.actionsDao().delete(action) - } - } - - override fun onFailure(call: Call, t: Throwable) { - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt deleted file mode 100644 index ef011ea..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt +++ /dev/null @@ -1,563 +0,0 @@ -package apps.amine.bou.readerforselfoss.fragments - -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -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.net.Uri -import android.os.Bundle -import androidx.preference.PreferenceManager -import android.view.* -import android.webkit.* -import android.widget.Toast -import com.google.android.material.floatingactionbutton.FloatingActionButton -import androidx.fragment.app.Fragment -import androidx.core.widget.NestedScrollView -import androidx.appcompat.app.AlertDialog -import androidx.browser.customtabs.CustomTabsIntent -import androidx.core.content.res.ResourcesCompat -import androidx.room.Room -import apps.amine.bou.readerforselfoss.ImageActivity -import apps.amine.bou.readerforselfoss.R -import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi -import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent -import apps.amine.bou.readerforselfoss.api.selfoss.Item -import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi -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.utils.* -import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper -import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth -import apps.amine.bou.readerforselfoss.utils.glide.getBitmapInputStream -import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible -import com.bumptech.glide.Glide -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.request.RequestOptions -import com.github.rubensousa.floatingtoolbar.FloatingToolbar -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response -import java.net.MalformedURLException -import java.net.URL -import java.util.* -import java.util.concurrent.ExecutionException -import kotlin.collections.ArrayList - -class ArticleFragment : Fragment() { - private var fontSize: Int = 16 - private lateinit var item: Item - private var mCustomTabActivityHelper: CustomTabActivityHelper? = null - private lateinit var url: String - private lateinit var contentText: String - private lateinit var contentSource: String - private lateinit var contentImage: String - private lateinit var contentTitle: String - private lateinit var allImages : ArrayList - private lateinit var editor: SharedPreferences.Editor - private lateinit var fab: FloatingActionButton - 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() { - super.onStop() - if (mCustomTabActivityHelper != null) { - mCustomTabActivityHelper!!.unbindCustomTabsService(activity) - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - appColors = AppColors(requireActivity()) - config = Config(requireActivity()) - - super.onCreate(savedInstanceState) - - item = requireArguments().getParcelable(ARG_ITEMS)!! - - db = Room.databaseBuilder( - requireContext(), - AppDatabase::class.java, "selfoss-database" - ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build() - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - try { - _binding = FragmentArticleBinding.inflate(inflater, container, false) - - url = item.getLinkDecoded() - contentText = item.content - contentTitle = item.getTitleDecoded() - contentImage = item.getThumbnail(requireActivity()) - contentSource = item.sourceAndDateText() - allImages = item.getImages() - - prefs = PreferenceManager.getDefaultSharedPreferences(activity) - editor = prefs.edit() - fontSize = prefs.getString("reader_font_size", "16")!!.toInt() - staticBar = prefs.getBoolean("reader_static_bar", false) - - font = prefs.getString("reader_font", "")!! - 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 - } - } - - refreshAlignment() - - val settings = requireActivity().getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) - - val api = SelfossApi( - requireContext(), - requireActivity(), - settings.getBoolean("isSelfSignedCert", false), - prefs.getString("api_timeout", "-1")!!.toLong() - ) - - fab = binding.fab - - fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent) - - fab.rippleColor = appColors.colorAccentDark - - val floatingToolbar: FloatingToolbar = binding.floatingToolbar - floatingToolbar.attachFab(fab) - - floatingToolbar.background = ColorDrawable(appColors.colorAccent) - - val customTabsIntent = requireActivity().buildCustomTabsIntent() - mCustomTabActivityHelper = CustomTabActivityHelper() - mCustomTabActivityHelper!!.bindCustomTabsService(activity) - - - floatingToolbar.setClickListener( - object : FloatingToolbar.ItemClickListener { - override fun onItemClick(item: MenuItem) { - when (item.itemId) { - R.id.more_action -> getContentFromMercury(customTabsIntent) - R.id.share_action -> requireActivity().shareLink(url, contentTitle) - R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item) - R.id.unread_action -> if (context != null) { - if (this@ArticleFragment.item.unread) { - SharedItems.readItem( - context!!, - api, - db, - this@ArticleFragment.item - ) - this@ArticleFragment.item.unread = false - Toast.makeText( - context, - R.string.marked_as_read, - Toast.LENGTH_LONG - ).show() - } else { - SharedItems.unreadItem( - context!!, - api, - db, - this@ArticleFragment.item - ) - this@ArticleFragment.item.unread = true - Toast.makeText( - context, - R.string.marked_as_unread, - Toast.LENGTH_LONG - ).show() - } - } - else -> Unit - } - } - - 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) - } 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 - } - } - - binding.nestedScrollView.setOnScrollChangeListener( - NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY -> - if (scrollY > oldScrollY) { - floatingToolbar.hide() - fab.hide() - } else { - if (staticBar) { - floatingToolbar.show() - } else { - if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show() - } - } - } - ) - - } catch (e: InflateException) { - AlertDialog.Builder(requireContext()) - .setMessage(requireContext().getString(R.string.webview_dialog_issue_message)) - .setTitle(requireContext().getString(R.string.webview_dialog_issue_title)) - .setPositiveButton(android.R.string.ok - ) { _, _ -> - val sharedPref = PreferenceManager.getDefaultSharedPreferences(requireContext()) - val editor = sharedPref.edit() - editor.putBoolean("prefer_article_viewer", false) - editor.apply() - requireActivity().finish() - } - .create() - .show() - } - - return binding.root - } - - 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(customTabsIntent: CustomTabsIntent) { - if ((context != null && requireContext().isNetworkAccessible(null)) || context == null) { - binding.progressBar.visibility = View.VISIBLE - val parser = MercuryApi() - - parser.parseUrl(url).enqueue( - object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - // TODO: clean all the following after finding the mercury content issue - try { - if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) { - try { - binding.titleView.text = response.body()!!.title - if (typeface != null) { - binding.titleView.typeface = typeface - } - try { - // 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) { - } - - try { - contentText = response.body()!!.content.orEmpty() - htmlToWebview() - } catch (e: Exception) { - } - - try { - if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) { - binding.imageView.visibility = View.VISIBLE - 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) { - } - } - } - } catch (e: Exception) { - if (context != null) { - } - } - } - - override fun onFailure( - call: Call, - t: Throwable - ) = openInBrowserAfterFailing(customTabsIntent) - } - ) - } - } - - private fun htmlToWebview() { - val stringColor = String.format("#%06X", 0xFFFFFF and appColors.colorAccent) - - 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 - - // TODO: Set the color strings programmatically - val (stringTextColor, stringBackgroundColor) = if (appColors.isDarkTheme) { - Pair("#FFFFFF", "#303030") - } else { - Pair("#212121", "#FAFAFA") - } - - binding.webcontent.settings.useWideViewPort = true - binding.webcontent.settings.loadWithOverviewMode = true - binding.webcontent.settings.javaScriptEnabled = false - - binding.webcontent.webViewClient = object : WebViewClient() { - override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean { - if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { - requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) - } - return true - } - - override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? { - val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL) - if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) { - try { - val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get() - return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG)) - }catch ( e : ExecutionException) {} - } - else if (url.lowercase(Locale.US).contains(".png")) { - try { - val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get() - return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG)) - }catch ( e : ExecutionException) {} - } - else if (url.lowercase(Locale.US).contains(".webp")) { - try { - val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get() - return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP)) - }catch ( e : ExecutionException) {} - } - - 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 - - try { - val itemUrl = URL(url) - baseUrl = itemUrl.protocol + "://" + itemUrl.host - } catch (e: MalformedURLException) { - } - - 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()) { - """ - | - """.trimMargin() - } else { - "" - } - - binding.webcontent.loadDataWithBaseURL( - baseUrl, - """ - | - | - | - | $fontLinkAndStyle - | - | - | $contentText - |""".trimMargin(), - "text/html", - "utf-8", - null - ) - } - - fun scrollDown() { - val height = binding.nestedScrollView.measuredHeight - binding.nestedScrollView.smoothScrollBy(0, height/2) - } - - fun scrollUp() { - val height = binding.nestedScrollView.measuredHeight - binding.nestedScrollView.smoothScrollBy(0, -height/2) - } - - private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) { - binding.progressBar.visibility = View.GONE - requireActivity().openItemUrlInternalBrowser( - url, - customTabsIntent, - requireActivity() - ) - } - - companion object { - private const val ARG_ITEMS = "items" - - fun newInstance( - item: Item - ): ArticleFragment { - val fragment = ArticleFragment() - val args = Bundle() - args.putParcelable(ARG_ITEMS, item) - fragment.arguments = args - return 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 - } - - -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ImageFragment.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ImageFragment.kt deleted file mode 100644 index 04ca538..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ImageFragment.kt +++ /dev/null @@ -1,57 +0,0 @@ -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 - } - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/ActionsDao.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/ActionsDao.kt deleted file mode 100644 index 055d9e2..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/ActionsDao.kt +++ /dev/null @@ -1,23 +0,0 @@ -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 - - @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) -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/DrawerDataDao.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/DrawerDataDao.kt deleted file mode 100644 index f142bc0..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/DrawerDataDao.kt +++ /dev/null @@ -1,36 +0,0 @@ -package apps.amine.bou.readerforselfoss.persistence.dao - -import androidx.room.Delete -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity -import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity - -@Dao -interface DrawerDataDao { - @Query("SELECT * FROM tags") - fun tags(): List - - @Query("SELECT * FROM sources") - fun sources(): List - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertAllTags(vararg tags: TagEntity) - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertAllSources(vararg sources: SourceEntity) - - @Query("DELETE FROM tags") - fun deleteAllTags() - - @Query("DELETE FROM sources") - fun deleteAllSources() - - @Delete - fun deleteTag(tag: TagEntity) - - @Delete - fun deleteSource(source: SourceEntity) -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/ItemsDao.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/ItemsDao.kt deleted file mode 100644 index b76ca14..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/ItemsDao.kt +++ /dev/null @@ -1,29 +0,0 @@ -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.ItemEntity -import androidx.room.Update - - - -@Dao -interface ItemsDao { - @Query("SELECT * FROM items order by id desc") - suspend fun items(): List - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAllItems(vararg items: ItemEntity) - - @Query("DELETE FROM items") - suspend fun deleteAllItems() - - @Delete - suspend fun delete(item: ItemEntity) - - @Update - suspend fun updateItem(item: ItemEntity) -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/database/AppDatabase.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/database/AppDatabase.kt deleted file mode 100644 index fb1a4be..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/database/AppDatabase.kt +++ /dev/null @@ -1,20 +0,0 @@ -package apps.amine.bou.readerforselfoss.persistence.database - -import androidx.room.RoomDatabase -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.ItemsDao -import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity -import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity -import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity -import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity - -@Database(entities = [TagEntity::class, SourceEntity::class, ItemEntity::class, ActionEntity::class], version = 4) -abstract class AppDatabase : RoomDatabase() { - abstract fun drawerDataDao(): DrawerDataDao - - abstract fun itemsDao(): ItemsDao - - abstract fun actionsDao(): ActionsDao -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/ActionEntity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/ActionEntity.kt deleted file mode 100644 index 9763a46..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/ActionEntity.kt +++ /dev/null @@ -1,22 +0,0 @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/DrawerDataEntity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/DrawerDataEntity.kt deleted file mode 100644 index 4676548..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/DrawerDataEntity.kt +++ /dev/null @@ -1,33 +0,0 @@ -package apps.amine.bou.readerforselfoss.persistence.entities - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "tags") -data class TagEntity( - @PrimaryKey - @ColumnInfo(name = "tag") - val tag: String, - @ColumnInfo(name = "color") - val color: String, - @ColumnInfo(name = "unread") - val unread: Int -) - -@Entity(tableName = "sources") -data class SourceEntity( - @PrimaryKey - @ColumnInfo(name = "id") - val id: String, - @ColumnInfo(name = "title") - val title: String, - @ColumnInfo(name = "tags") - val tags: String, - @ColumnInfo(name = "spout") - val spout: String, - @ColumnInfo(name = "error") - val error: String, - @ColumnInfo(name = "icon") - val icon: String -) \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/ItemEntity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/ItemEntity.kt deleted file mode 100644 index 9b57c08..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/ItemEntity.kt +++ /dev/null @@ -1,32 +0,0 @@ -package apps.amine.bou.readerforselfoss.persistence.entities - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "items") -data class ItemEntity( - @PrimaryKey - @ColumnInfo(name = "id") - val id: String, - @ColumnInfo(name = "datetime") - val datetime: String, - @ColumnInfo(name = "title") - val title: String, - @ColumnInfo(name = "content") - val content: String, - @ColumnInfo(name = "unread") - val unread: Boolean, - @ColumnInfo(name = "starred") - var starred: Boolean, - @ColumnInfo(name = "thumbnail") - val thumbnail: String?, - @ColumnInfo(name = "icon") - val icon: String?, - @ColumnInfo(name = "link") - val link: String, - @ColumnInfo(name = "sourcetitle") - val sourcetitle: String, - @ColumnInfo(name = "tags") - val tags: String -) \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/migrations/migrations.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/migrations/migrations.kt deleted file mode 100644 index d1116cc..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/migrations/migrations.kt +++ /dev/null @@ -1,34 +0,0 @@ -package apps.amine.bou.readerforselfoss.persistence.migrations - -import androidx.sqlite.db.SupportSQLiteDatabase -import androidx.room.migration.Migration - -val MIGRATION_1_2: Migration = object : Migration(1, 2) { - override fun migrate(database: SupportSQLiteDatabase) { - 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") - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/settings/SettingsActivity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/settings/SettingsActivity.kt deleted file mode 100644 index 3bef30a..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/settings/SettingsActivity.kt +++ /dev/null @@ -1,221 +0,0 @@ -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.databinding.ActivitySettingsBinding -import apps.amine.bou.readerforselfoss.themes.Toppings -import apps.amine.bou.readerforselfoss.utils.Config -import com.ftinc.scoop.Scoop -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) - } - val binding = ActivitySettingsBinding.inflate(layoutInflater) - - val scoop = Scoop.getInstance() - scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar) - scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value) - - setContentView(binding.root) - 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(binding.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("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("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("trackerLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openUrl(Uri.parse(Config.trackerUrl)) - true - } - - preferenceManager.findPreference("sourceLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openUrl(Uri.parse(Config.sourceUrl)) - false - } - - preferenceManager.findPreference("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) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/themes/AppColors.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/themes/AppColors.kt deleted file mode 100644 index 538b767..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/themes/AppColors.kt +++ /dev/null @@ -1,61 +0,0 @@ -package apps.amine.bou.readerforselfoss.themes - -import android.app.Activity -import androidx.annotation.ColorInt -import androidx.preference.PreferenceManager -import apps.amine.bou.readerforselfoss.R - -class AppColors(a: Activity) { - - @ColorInt val colorPrimary: Int - @ColorInt val colorPrimaryDark: Int - @ColorInt val colorAccent: Int - @ColorInt val colorAccentDark: Int - @ColorInt val colorBackground: Int - @ColorInt val textColor: Int - val isDarkTheme: Boolean - - init { - val sharedPref = PreferenceManager.getDefaultSharedPreferences(a) - - colorPrimary = - sharedPref.getInt( - "color_primary", - a.resources.getColor(R.color.colorPrimary) - ) - colorPrimaryDark = - sharedPref.getInt( - "color_primary_dark", - a.resources.getColor(R.color.colorPrimaryDark) - ) - colorAccent = - sharedPref.getInt( - "color_accent", - a.resources.getColor(R.color.colorAccent) - ) - colorAccentDark = - sharedPref.getInt( - "color_accent_dark", - a.resources.getColor(R.color.colorAccentDark) - ) - isDarkTheme = - sharedPref.getBoolean( - "dark_theme", - false - ) - - colorBackground = if (isDarkTheme) { - a.setTheme(R.style.NoBarDark) - R.color.darkBackground - } else { - a.setTheme(R.style.NoBar) - R.color.grey_50 - } - - textColor = if (isDarkTheme) { - R.color.white - } else { - R.color.grey_900 - } - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/themes/Toppings.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/themes/Toppings.kt deleted file mode 100644 index cca5107..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/themes/Toppings.kt +++ /dev/null @@ -1,8 +0,0 @@ -package apps.amine.bou.readerforselfoss.themes - -enum class Toppings(val value: Int) { - PRIMARY(1), - PRIMARY_DARK(2), - ACCENT(3), - ACCENT_DARK(4) -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/ApiUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/ApiUtils.kt deleted file mode 100644 index 4f9c3cc..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/ApiUtils.kt +++ /dev/null @@ -1,7 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils - -import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse -import retrofit2.Response - -fun Response.succeeded(): Boolean = - this.code() === 200 && this.body() != null && this.body()!!.isSuccess \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/AppUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/AppUtils.kt deleted file mode 100644 index f206294..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/AppUtils.kt +++ /dev/null @@ -1,41 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils - -import android.content.Context -import android.content.Intent -import apps.amine.bou.readerforselfoss.R - -fun String?.isEmptyOrNullOrNullString(): Boolean = - this == null || this == "null" || this.isEmpty() - -fun String.longHash(): Long { - var h = 98764321261L - val l = this.length - val chars = this.toCharArray() - - for (i in 0 until l) { - h = 31 * h + chars[i].code.toLong() - } - return h -} - -fun String.toStringUriWithHttp(): String = - if (!this.startsWith("https://") && !this.startsWith("http://")) { - "http://" + this - } else { - this - } - -fun Context.shareLink(itemUrl: String, itemTitle: String) { - val sendIntent = Intent() - sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - sendIntent.action = Intent.ACTION_SEND - sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp()) - sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle) - sendIntent.type = "text/plain" - startActivity( - Intent.createChooser( - sendIntent, - getString(R.string.share) - ).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - ) -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/Config.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/Config.kt deleted file mode 100644 index ef1c7bd..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/Config.kt +++ /dev/null @@ -1,64 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import androidx.preference.PreferenceManager -import apps.amine.bou.readerforselfoss.LoginActivity - -class Config(c: Context) { - - val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(c) - - val baseUrl: String - get() = settings.getString("url", "")!! - - val userLogin: String - get() = settings.getString("login", "")!! - - val userPassword: String - get() = settings.getString("password", "")!! - - val httpUserLogin: String - get() = settings.getString("httpUserName", "")!! - - val httpUserPassword: String - get() = settings.getString("httpPassword", "")!! - - companion object { - const val settingsName = "paramsselfoss" - - const val feedbackEmail = "aminecmi@gmail.com" - - const val translationUrl = "https://crwd.in/readerforselfoss" - - const val sourceUrl = "https://github.com/aminecmi/ReaderforSelfoss" - - 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 - - /* Execute logout and clear all settings to default */ - fun logoutAndRedirect( - c: Context, - callingActivity: Activity, - editor: SharedPreferences.Editor, - baseUrlFail: Boolean = false - ): Boolean { - val settings = PreferenceManager.getDefaultSharedPreferences(c) - settings.edit().clear().commit() - val intent = Intent(c, LoginActivity::class.java) - if (baseUrlFail) { - intent.putExtra("baseUrlFail", baseUrlFail) - } - c.startActivity(intent) - callingActivity.finish() - return true - } - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/DateUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/DateUtils.kt deleted file mode 100644 index 454ff5a..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/DateUtils.kt +++ /dev/null @@ -1,31 +0,0 @@ -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 - ) -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/HttpUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/HttpUtils.kt deleted file mode 100644 index 0fdc31c..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/HttpUtils.kt +++ /dev/null @@ -1,43 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils - -import okhttp3.OkHttpClient -import java.security.cert.CertificateException -import java.security.cert.X509Certificate -import javax.net.ssl.SSLContext -import javax.net.ssl.TrustManager -import javax.net.ssl.X509TrustManager - -fun getUnsafeHttpClient(): OkHttpClient.Builder = - try { - // Create a trust manager that does not validate certificate chains - val trustAllCerts = arrayOf(object : X509TrustManager { - override fun getAcceptedIssuers(): Array = - arrayOf() - - @Throws(CertificateException::class) - override fun checkClientTrusted( - chain: Array, - authType: String - ) { - } - - @Throws(CertificateException::class) - override fun checkServerTrusted( - chain: Array, - authType: String - ) { - } - }) - - // Install the all-trusting trust manager - val sslContext = SSLContext.getInstance("SSL") - sslContext.init(null, trustAllCerts, java.security.SecureRandom()) - - val sslSocketFactory = sslContext.socketFactory - - OkHttpClient.Builder() - .sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager) - .hostnameVerifier { _, _ -> true } - } catch (e: Exception) { - throw RuntimeException(e) - } \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/ItemsUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/ItemsUtils.kt deleted file mode 100644 index b9ba764..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/ItemsUtils.kt +++ /dev/null @@ -1,36 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils - -import android.content.Context -import apps.amine.bou.readerforselfoss.api.selfoss.Item -import apps.amine.bou.readerforselfoss.api.selfoss.SelfossTagType - -fun String.toTextDrawableString(c: Context): String { - val textDrawable = StringBuilder() - for (s in this.split(" ".toRegex()).filter { it.isNotEmpty() }.toTypedArray()) { - try { - textDrawable.append(s[0]) - } catch (e: StringIndexOutOfBoundsException) { - } - } - return textDrawable.toString() -} - -fun Item.sourceAndDateText(): String { - val formattedDate = parseRelativeDate(this.datetime) - - return this.getSourceTitle() + formattedDate -} - -fun Item.toggleStar(): Item { - this.starred = !this.starred - return this -} - -fun List.flattenTags(): List = - this.flatMap { - val item = it - val tags: List = it.tags.tags.split(",") - tags.map { t -> - item.copy(tags = SelfossTagType(t.trim())) - } - } \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/LinksUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/LinksUtils.kt deleted file mode 100644 index d1fb810..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/LinksUtils.kt +++ /dev/null @@ -1,220 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils - -import android.app.Activity -import android.app.PendingIntent -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import android.graphics.BitmapFactory -import android.net.Uri -import android.os.Build -import android.text.Spannable -import android.text.style.ClickableSpan -import androidx.browser.customtabs.CustomTabsIntent -import android.util.Patterns -import android.view.MotionEvent -import android.view.View -import android.widget.TextView -import android.widget.Toast -import apps.amine.bou.readerforselfoss.R -import apps.amine.bou.readerforselfoss.ReaderActivity -import apps.amine.bou.readerforselfoss.api.selfoss.Item -import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper -import okhttp3.HttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull - -fun Context.buildCustomTabsIntent(): CustomTabsIntent { - - val actionIntent = Intent(Intent.ACTION_SEND) - actionIntent.type = "text/plain" - val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_IMMUTABLE - } else { - 0 - } - val createPendingShareIntent: PendingIntent = PendingIntent.getActivity( - this, - 0, - actionIntent, - pflags - ) - - val intentBuilder = CustomTabsIntent.Builder() - - // TODO: change to primary when it's possible to customize custom tabs title color - //intentBuilder.setToolbarColor(c.getResources().getColor(R.color.colorPrimary)); - intentBuilder.setToolbarColor(resources.getColor(R.color.colorAccentDark)) - intentBuilder.setShowTitle(true) - - - intentBuilder.setStartAnimations( - this, - R.anim.slide_in_right, - R.anim.slide_out_left - ) - intentBuilder.setExitAnimations( - this, - android.R.anim.slide_in_left, - android.R.anim.slide_out_right - ) - - val closeicon = BitmapFactory.decodeResource(resources, R.drawable.ic_close_white_24dp) - intentBuilder.setCloseButtonIcon(closeicon) - - val shareLabel = this.getString(R.string.label_share) - val icon = BitmapFactory.decodeResource( - resources, - R.drawable.ic_share_white_24dp - ) - intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent) - - return intentBuilder.build() -} - -fun Context.openItemUrlInternally( - allItems: ArrayList, - currentItem: Int, - linkDecoded: String, - customTabsIntent: CustomTabsIntent, - articleViewer: Boolean, - app: Activity -) { - if (articleViewer) { - ReaderActivity.allItems = allItems - SharedItems.position = currentItem - val intent = Intent(this, ReaderActivity::class.java) - intent.putExtra("currentItem", currentItem) - app.startActivity(intent) - } else { - this.openItemUrlInternalBrowser( - linkDecoded, - customTabsIntent, - app) - } -} - -fun Context.openItemUrlInternalBrowser( - linkDecoded: String, - customTabsIntent: CustomTabsIntent, - app: Activity -) { - try { - CustomTabActivityHelper.openCustomTab( - app, - customTabsIntent, - Uri.parse(linkDecoded) - ) { _, uri -> - val intent = Intent(Intent.ACTION_VIEW, uri) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - startActivity(intent) - } - } catch (e: Exception) { - openInBrowser(linkDecoded, app) - } -} - -fun Context.openItemUrl( - allItems: ArrayList, - currentItem: Int, - linkDecoded: String, - customTabsIntent: CustomTabsIntent, - internalBrowser: Boolean, - articleViewer: Boolean, - app: Activity -) { - - if (!linkDecoded.isUrlValid()) { - Toast.makeText( - this, - this.getString(R.string.cant_open_invalid_url), - Toast.LENGTH_LONG - ).show() - } else { - if (!internalBrowser) { - openInBrowser(linkDecoded, app) - } else if (articleViewer) { - this.openItemUrlInternally( - allItems, - currentItem, - linkDecoded, - customTabsIntent, - articleViewer, - app - ) - } else { - this.openItemUrlInternalBrowser( - linkDecoded, - customTabsIntent, - app - ) - } - } -} - -private fun openInBrowser(linkDecoded: String, app: Activity) { - val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(linkDecoded) - try { - app.startActivity(intent) - } catch (e: ActivityNotFoundException) { - Toast.makeText(app.baseContext, e.message, Toast.LENGTH_LONG).show() - } -} - -fun String.isUrlValid(): Boolean = - this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches() - -fun String.isBaseUrlValid(ctx: Context): Boolean { - val baseUrl = this.toHttpUrlOrNull() - var existsAndEndsWithSlash = false - if (baseUrl != null) { - val pathSegments = baseUrl.pathSegments - existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1] - } - - return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash -} - -fun Context.openInBrowserAsNewTask(i: Item) { - val intent = Intent(Intent.ACTION_VIEW) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp()) - 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 - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/SharedItems.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/SharedItems.kt deleted file mode 100644 index c5f114f..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/SharedItems.kt +++ /dev/null @@ -1,404 +0,0 @@ -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 = arrayListOf() - get() { - return ArrayList(field) - } - set(value) { - field = ArrayList(value) - } - var focusedItems: ArrayList = arrayListOf() - 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) { - var tmpItems = items - if (tmpItems != newItems) { - tmpItems = tmpItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList - tmpItems.addAll(newItems) - items = tmpItems - - sortItems() - getFocusedItems() - } - } - - fun refreshFocusedItems(newItems: ArrayList) { - 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 - } - if (searchFilter != null) { - tmpItems = tmpItems.filter { filterSearch(it) } as ArrayList - } - if (sourceFilter != null) { - tmpItems = tmpItems.filter { it.sourcetitle == sourceFilter } as ArrayList - } - 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 - filter() - } - - fun getRead() { - displayedItems = "read" - focusedItems = items.filter { item -> !item.unread } as ArrayList - filter() - } - - fun getStarred() { - displayedItems = "starred" - focusedItems = items.filter { item -> item.starred } as ArrayList - 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 - 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) { - 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 { - override fun onResponse( - call: Call, - response: Response - ) { - - val tmpItems = items - tmpItems[position].unread = false - items = tmpItems - - resetDBItem(db) - getFocusedItems() - badgeUnread-- - } - - override fun onFailure(call: Call, 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 { - override fun onResponse( - call: Call, - response: Response - ) { - - val tmpItems = items - tmpItems[position].unread = true - items = tmpItems - - resetDBItem(db) - getFocusedItems() - badgeUnread++ - } - - override fun onFailure(call: Call, 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 { - override fun onResponse( - call: Call, - response: Response - ) { - val tmpItems = items - tmpItems[position].starred = true - items = tmpItems - - resetDBItem(db) - getFocusedItems() - badgeStarred++ - } - - override fun onFailure( - call: Call, - 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 { - override fun onResponse( - call: Call, - response: Response - ) { - val tmpItems = items - tmpItems[position].starred = false - items = tmpItems - - resetDBItem(db) - getFocusedItems() - badgeStarred-- - } - - override fun onFailure( - call: Call, - 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 - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/SizeUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/SizeUtils.kt deleted file mode 100644 index 46518ba..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/SizeUtils.kt +++ /dev/null @@ -1,9 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils - -import android.content.res.Resources - -val Int.toPx: Int - get() = (this * Resources.getSystem().displayMetrics.density).toInt() - -val Int.toDp: Int - get() = (this / Resources.getSystem().displayMetrics.density).toInt() diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/bottombar/BottomBarUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/bottombar/BottomBarUtils.kt deleted file mode 100644 index 0a5656d..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/bottombar/BottomBarUtils.kt +++ /dev/null @@ -1,12 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils.bottombar - -import com.ashokvarma.bottomnavigation.TextBadgeItem - -fun TextBadgeItem.removeBadge(): TextBadgeItem { - this.setText("") - this.hide() - return this -} - -fun TextBadgeItem.maybeShow(): TextBadgeItem = - if (this.isHidden) this.show() else this diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/CustomTabActivityHelper.java b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/CustomTabActivityHelper.java deleted file mode 100644 index ebfbd96..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/CustomTabActivityHelper.java +++ /dev/null @@ -1,153 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils.customtabs; - - -import android.app.Activity; -import android.net.Uri; -import android.os.Bundle; -import androidx.browser.customtabs.CustomTabsClient; -import androidx.browser.customtabs.CustomTabsIntent; -import androidx.browser.customtabs.CustomTabsServiceConnection; -import androidx.browser.customtabs.CustomTabsSession; - -import java.util.List; - -/** - * This is a helper class to manage the connection to the Custom Tabs Service. - */ -public class CustomTabActivityHelper implements ServiceConnectionCallback { - private CustomTabsSession mCustomTabsSession; - private CustomTabsClient mClient; - private CustomTabsServiceConnection mConnection; - private ConnectionCallback mConnectionCallback; - - /** - * Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView. - * - * @param activity The host activity. - * @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available. - * @param uri the Uri to be opened. - * @param fallback a CustomTabFallback to be used if Custom Tabs is not available. - */ - public static void openCustomTab(Activity activity, - CustomTabsIntent customTabsIntent, - Uri uri, - CustomTabFallback fallback) { - String packageName = CustomTabsHelper.getPackageNameToUse(activity); - - //If we cant find a package name, it means theres no browser that supports - //Chrome Custom Tabs installed. So, we fallback to the webview - if (packageName == null) { - if (fallback != null) { - fallback.openUri(activity, uri); - } - } else { - customTabsIntent.intent.setPackage(packageName); - customTabsIntent.launchUrl(activity, uri); - } - } - - /** - * Unbinds the Activity from the Custom Tabs Service. - * - * @param activity the activity that is connected to the service. - */ - public void unbindCustomTabsService(Activity activity) { - if (mConnection == null) return; - activity.unbindService(mConnection); - mClient = null; - mCustomTabsSession = null; - mConnection = null; - } - - /** - * Creates or retrieves an exiting CustomTabsSession. - * - * @return a CustomTabsSession. - */ - public CustomTabsSession getSession() { - if (mClient == null) { - mCustomTabsSession = null; - } else if (mCustomTabsSession == null) { - mCustomTabsSession = mClient.newSession(null); - } - return mCustomTabsSession; - } - - /** - * Register a Callback to be called when connected or disconnected from the Custom Tabs Service. - * - * @param connectionCallback - */ - public void setConnectionCallback(ConnectionCallback connectionCallback) { - this.mConnectionCallback = connectionCallback; - } - - /** - * Binds the Activity to the Custom Tabs Service. - * - * @param activity the activity to be binded to the service. - */ - public void bindCustomTabsService(Activity activity) { - if (mClient != null) return; - - String packageName = CustomTabsHelper.getPackageNameToUse(activity); - if (packageName == null) return; - - mConnection = new ServiceConnection(this); - CustomTabsClient.bindCustomTabsService(activity, packageName, mConnection); - } - - /** - * @return true if call to mayLaunchUrl was accepted. - * @see {@link CustomTabsSession#mayLaunchUrl(Uri, Bundle, List)}. - */ - public boolean mayLaunchUrl(Uri uri, Bundle extras, List otherLikelyBundles) { - if (mClient == null) return false; - - CustomTabsSession session = getSession(); - return session != null && session.mayLaunchUrl(uri, extras, otherLikelyBundles); - - } - - @Override - public void onServiceConnected(CustomTabsClient client) { - mClient = client; - mClient.warmup(0L); - if (mConnectionCallback != null) mConnectionCallback.onCustomTabsConnected(); - } - - @Override - public void onServiceDisconnected() { - mClient = null; - mCustomTabsSession = null; - if (mConnectionCallback != null) mConnectionCallback.onCustomTabsDisconnected(); - } - - /** - * A Callback for when the service is connected or disconnected. Use those callbacks to - * handle UI changes when the service is connected or disconnected. - */ - public interface ConnectionCallback { - /** - * Called when the service is connected. - */ - void onCustomTabsConnected(); - - /** - * Called when the service is disconnected. - */ - void onCustomTabsDisconnected(); - } - - /** - * To be used as a fallback to open the Uri when Custom Tabs is not available. - */ - public interface CustomTabFallback { - /** - * @param activity The Activity that wants to open the Uri. - * @param uri The uri to be opened by the fallback. - */ - void openUri(Activity activity, Uri uri); - } - -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/CustomTabsHelper.java b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/CustomTabsHelper.java deleted file mode 100644 index 080f5b9..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/CustomTabsHelper.java +++ /dev/null @@ -1,130 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils.customtabs; - - -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import androidx.browser.customtabs.CustomTabsService; -import android.text.TextUtils; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -import apps.amine.bou.readerforselfoss.utils.customtabs.helpers.KeepAliveService; - -@SuppressWarnings("ALL") -class CustomTabsHelper { - private static final String TAG = "CustomTabsHelper"; - private static final String STABLE_PACKAGE = "com.android.chrome"; - private static final String BETA_PACKAGE = "com.chrome.beta"; - private static final String DEV_PACKAGE = "com.chrome.dev"; - private static final String LOCAL_PACKAGE = "com.google.android.apps.chrome"; - private static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE = - "android.support.customtabs.extra.KEEP_ALIVE"; - - private static String sPackageNameToUse; - - private CustomTabsHelper() { - } - - public static void addKeepAliveExtra(Context context, Intent intent) { - Intent keepAliveIntent = new Intent().setClassName( - context.getPackageName(), KeepAliveService.class.getCanonicalName()); - intent.putExtra(EXTRA_CUSTOM_TABS_KEEP_ALIVE, keepAliveIntent); - } - - /** - * Goes through all apps that handle VIEW intents and have a warmup service. Picks - * the one chosen by the user if there is one, otherwise makes a best effort to return a - * valid package name. - *

- * This is not threadsafe. - * - * @param context {@link Context} to use for accessing {@link PackageManager}. - * @return The package name recommended to use for connecting to custom tabs related components. - */ - public static String getPackageNameToUse(Context context) { - if (sPackageNameToUse != null) return sPackageNameToUse; - - PackageManager pm = context.getPackageManager(); - // Get default VIEW intent handler. - Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com")); - ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0); - String defaultViewHandlerPackageName = null; - if (defaultViewHandlerInfo != null) { - defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName; - } - - // Get all apps that can handle VIEW intents. - List resolvedActivityList = pm.queryIntentActivities(activityIntent, 0); - List packagesSupportingCustomTabs = new ArrayList<>(); - for (ResolveInfo info : resolvedActivityList) { - Intent serviceIntent = new Intent(); - serviceIntent.setAction(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION); - serviceIntent.setPackage(info.activityInfo.packageName); - if (pm.resolveService(serviceIntent, 0) != null) { - packagesSupportingCustomTabs.add(info.activityInfo.packageName); - } - } - - // Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents - // and service calls. - if (packagesSupportingCustomTabs.isEmpty()) { - sPackageNameToUse = null; - } else if (packagesSupportingCustomTabs.size() == 1) { - sPackageNameToUse = packagesSupportingCustomTabs.get(0); - } else if (!TextUtils.isEmpty(defaultViewHandlerPackageName) - && !hasSpecializedHandlerIntents(context, activityIntent) - && packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) { - sPackageNameToUse = defaultViewHandlerPackageName; - } else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) { - sPackageNameToUse = STABLE_PACKAGE; - } else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) { - sPackageNameToUse = BETA_PACKAGE; - } else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) { - sPackageNameToUse = DEV_PACKAGE; - } else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) { - sPackageNameToUse = LOCAL_PACKAGE; - } - return sPackageNameToUse; - } - - /** - * Used to check whether there is a specialized handler for a given intent. - * - * @param intent The intent to check with. - * @return Whether there is a specialized handler for the given intent. - */ - private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) { - try { - PackageManager pm = context.getPackageManager(); - List handlers = pm.queryIntentActivities( - intent, - PackageManager.GET_RESOLVED_FILTER); - if (handlers == null || handlers.isEmpty()) { - return false; - } - for (ResolveInfo resolveInfo : handlers) { - IntentFilter filter = resolveInfo.filter; - if (filter == null) continue; - if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue; - if (resolveInfo.activityInfo == null) continue; - return true; - } - } catch (RuntimeException e) { - Log.e(TAG, "Runtime exception while getting specialized handlers"); - } - return false; - } - - /** - * @return All possible chrome package names that provide custom tabs feature. - */ - public static String[] getPackages() { - return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE}; - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/ServiceConnection.java b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/ServiceConnection.java deleted file mode 100644 index f035dff..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/ServiceConnection.java +++ /dev/null @@ -1,33 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils.customtabs; - - -import android.content.ComponentName; -import androidx.browser.customtabs.CustomTabsClient; -import androidx.browser.customtabs.CustomTabsServiceConnection; - -import java.lang.ref.WeakReference; - -/** - * Implementation for the CustomTabsServiceConnection that avoids leaking the - * ServiceConnectionCallback - */ -public class ServiceConnection extends CustomTabsServiceConnection { - // A weak reference to the ServiceConnectionCallback to avoid leaking it. - private WeakReference mConnectionCallback; - - public ServiceConnection(ServiceConnectionCallback connectionCallback) { - mConnectionCallback = new WeakReference<>(connectionCallback); - } - - @Override - public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) { - ServiceConnectionCallback connectionCallback = mConnectionCallback.get(); - if (connectionCallback != null) connectionCallback.onServiceConnected(client); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - ServiceConnectionCallback connectionCallback = mConnectionCallback.get(); - if (connectionCallback != null) connectionCallback.onServiceDisconnected(); - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/ServiceConnectionCallback.java b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/ServiceConnectionCallback.java deleted file mode 100644 index d9db63a..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/ServiceConnectionCallback.java +++ /dev/null @@ -1,19 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils.customtabs; - - -import androidx.browser.customtabs.CustomTabsClient; - - -public interface ServiceConnectionCallback { - /** - * Called when the service is connected. - * - * @param client a CustomTabsClient - */ - void onServiceConnected(CustomTabsClient client); - - /** - * Called when the service is disconnected. - */ - void onServiceDisconnected(); -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/helpers/KeepAliveService.java b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/helpers/KeepAliveService.java deleted file mode 100644 index 9758d72..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/customtabs/helpers/KeepAliveService.java +++ /dev/null @@ -1,15 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils.customtabs.helpers; - -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.IBinder; - -public class KeepAliveService extends Service { - private static final Binder sBinder = new Binder(); - - @Override - public IBinder onBind(Intent intent) { - return sBinder; - } -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/drawer/CustomBaseViewHolder.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/drawer/CustomBaseViewHolder.kt deleted file mode 100644 index 5c6506a..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/drawer/CustomBaseViewHolder.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomBaseViewHolder.java */ -package apps.amine.bou.readerforselfoss.utils.drawer - -import androidx.recyclerview.widget.RecyclerView -import android.view.View -import android.widget.ImageView -import android.widget.TextView - -import apps.amine.bou.readerforselfoss.R - -open class CustomBaseViewHolder(var view: View) : RecyclerView.ViewHolder(view) { - var icon: ImageView = view.findViewById(R.id.material_drawer_icon) - var name: TextView = view.findViewById(R.id.material_drawer_name) - var description: TextView = view.findViewById(R.id.material_drawer_description) -} diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/glide/GlideUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/glide/GlideUtils.kt deleted file mode 100644 index 9183126..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/glide/GlideUtils.kt +++ /dev/null @@ -1,69 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils.glide - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.util.Base64 -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory -import android.widget.ImageView -import apps.amine.bou.readerforselfoss.utils.Config -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.target.BitmapImageViewTarget -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.InputStream - -fun Context.bitmapCenterCrop(config: Config, url: String, iv: ImageView) = - Glide.with(this) - .asBitmap() - .loadMaybeBasicAuth(config, url) - .apply(RequestOptions.centerCropTransform()) - .into(iv) - -fun Context.circularBitmapDrawable(config: Config, url: String, iv: ImageView) = - Glide.with(this) - .asBitmap() - .loadMaybeBasicAuth(config, url) - .apply(RequestOptions.centerCropTransform()) - .into(object : BitmapImageViewTarget(iv) { - override fun setResource(resource: Bitmap?) { - val circularBitmapDrawable = RoundedBitmapDrawableFactory.create( - resources, - resource - ) - circularBitmapDrawable.isCircular = true - iv.setImageDrawable(circularBitmapDrawable) - } - }) - -fun RequestBuilder.loadMaybeBasicAuth(config: Config, url: String): RequestBuilder { - 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 { - 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) -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/glide/SelfSignedGlideModule.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/glide/SelfSignedGlideModule.kt deleted file mode 100644 index ca1e234..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/glide/SelfSignedGlideModule.kt +++ /dev/null @@ -1,33 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils.glide - -import android.content.Context -import apps.amine.bou.readerforselfoss.utils.Config -import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient -import com.bumptech.glide.Glide -import com.bumptech.glide.GlideBuilder -import com.bumptech.glide.Registry -import com.bumptech.glide.load.model.GlideUrl -import com.bumptech.glide.module.GlideModule -import java.io.InputStream - -class SelfSignedGlideModule : GlideModule { - - override fun applyOptions(context: Context?, builder: GlideBuilder?) { - } - - override fun registerComponents(context: Context?, glide: Glide?, registry: Registry?) { - - if (context != null) { - val pref = context?.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) - if (pref.getBoolean("isSelfSignedCert", false)) { - val client = getUnsafeHttpClient().build() - - registry?.append( - GlideUrl::class.java, - InputStream::class.java, - com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client) - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/network/NetworkUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/network/NetworkUtils.kt deleted file mode 100644 index 8b93a4a..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/network/NetworkUtils.kt +++ /dev/null @@ -1,64 +0,0 @@ -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 - } -} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/persistence/EntitiesUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/persistence/EntitiesUtils.kt deleted file mode 100644 index d9efb40..0000000 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/persistence/EntitiesUtils.kt +++ /dev/null @@ -1,73 +0,0 @@ -package apps.amine.bou.readerforselfoss.utils.persistence - -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.Tag -import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity -import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity -import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity - -fun TagEntity.toView(): Tag = - Tag( - this.tag, - this.color, - this.unread - ) - -fun SourceEntity.toView(): Source = - Source( - this.id, - this.title, - SelfossTagType(this.tags), - this.spout, - this.error, - this.icon - ) - -fun Source.toEntity(): SourceEntity = - SourceEntity( - this.id, - this.getTitleDecoded(), - this.tags.tags, - this.spout, - this.error, - this.icon.orEmpty() - ) - -fun Tag.toEntity(): TagEntity = - TagEntity( - this.tag, - this.color, - this.unread - ) - -fun ItemEntity.toView(): Item = - Item( - this.id, - this.datetime, - this.title, - this.content, - this.unread, - this.starred, - this.thumbnail, - this.icon, - this.link, - this.sourcetitle, - SelfossTagType(this.tags) - ) - -fun Item.toEntity(): ItemEntity = - ItemEntity( - this.id, - this.datetime, - this.getTitleDecoded(), - this.content, - this.unread, - this.starred, - this.thumbnail, - this.icon, - this.link, - this.getSourceTitle(), - this.tags.tags - ) \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_right.xml b/app/src/main/res/anim/slide_in_right.xml deleted file mode 100644 index 3189c25..0000000 --- a/app/src/main/res/anim/slide_in_right.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_left.xml b/app/src/main/res/anim/slide_out_left.xml deleted file mode 100644 index 0ec7682..0000000 --- a/app/src/main/res/anim/slide_out_left.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/ic_menu_heart_color.xml b/app/src/main/res/color/ic_menu_heart_color.xml deleted file mode 100644 index dd8fa5f..0000000 --- a/app/src/main/res/color/ic_menu_heart_color.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable-anydpi/ic_format_align_justify.xml b/app/src/main/res/drawable-anydpi/ic_format_align_justify.xml deleted file mode 100644 index 0001f38..0000000 --- a/app/src/main/res/drawable-anydpi/ic_format_align_justify.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable-anydpi/ic_format_align_left.xml b/app/src/main/res/drawable-anydpi/ic_format_align_left.xml deleted file mode 100644 index 0e0782a..0000000 --- a/app/src/main/res/drawable-anydpi/ic_format_align_left.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/background_splash.xml b/app/src/main/res/drawable/background_splash.xml deleted file mode 100644 index 32241ec..0000000 --- a/app/src/main/res/drawable/background_splash.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/bg.png b/app/src/main/res/drawable/bg.png deleted file mode 100644 index d93c122..0000000 Binary files a/app/src/main/res/drawable/bg.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_add_white_24dp.xml b/app/src/main/res/drawable/ic_add_white_24dp.xml deleted file mode 100644 index e3979cd..0000000 --- a/app/src/main/res/drawable/ic_add_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_white_eye_24dp.xml b/app/src/main/res/drawable/ic_baseline_white_eye_24dp.xml deleted file mode 100644 index b2add52..0000000 --- a/app/src/main/res/drawable/ic_baseline_white_eye_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_bug_report_black_24dp.xml b/app/src/main/res/drawable/ic_bug_report_black_24dp.xml deleted file mode 100644 index 4d83902..0000000 --- a/app/src/main/res/drawable/ic_bug_report_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_bug_report_white_24dp.xml b/app/src/main/res/drawable/ic_bug_report_white_24dp.xml deleted file mode 100644 index 5c8f5bc..0000000 --- a/app/src/main/res/drawable/ic_bug_report_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_chrome_reader_mode_black_24dp.xml b/app/src/main/res/drawable/ic_chrome_reader_mode_black_24dp.xml deleted file mode 100644 index 99b5867..0000000 --- a/app/src/main/res/drawable/ic_chrome_reader_mode_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_chrome_reader_mode_white_24dp.xml b/app/src/main/res/drawable/ic_chrome_reader_mode_white_24dp.xml deleted file mode 100644 index 43fd20a..0000000 --- a/app/src/main/res/drawable/ic_chrome_reader_mode_white_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_close_white_24dp.xml b/app/src/main/res/drawable/ic_close_white_24dp.xml deleted file mode 100644 index 0c8775c..0000000 --- a/app/src/main/res/drawable/ic_close_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_color_lens_black_24dp.xml b/app/src/main/res/drawable/ic_color_lens_black_24dp.xml deleted file mode 100644 index f75e2fb..0000000 --- a/app/src/main/res/drawable/ic_color_lens_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_color_lens_white_24dp.xml b/app/src/main/res/drawable/ic_color_lens_white_24dp.xml deleted file mode 100644 index 4abeea5..0000000 --- a/app/src/main/res/drawable/ic_color_lens_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_history_white_24dp.xml b/app/src/main/res/drawable/ic_history_white_24dp.xml deleted file mode 100644 index de25eb4..0000000 --- a/app/src/main/res/drawable/ic_history_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_info_black_24dp.xml b/app/src/main/res/drawable/ic_info_black_24dp.xml deleted file mode 100644 index cc94088..0000000 --- a/app/src/main/res/drawable/ic_info_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_info_outline_white_24dp.xml b/app/src/main/res/drawable/ic_info_outline_white_24dp.xml deleted file mode 100644 index af0d4d0..0000000 --- a/app/src/main/res/drawable/ic_info_outline_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_menu_done_all_white_24dp.xml b/app/src/main/res/drawable/ic_menu_done_all_white_24dp.xml deleted file mode 100644 index 2479e86..0000000 --- a/app/src/main/res/drawable/ic_menu_done_all_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_menu_heart_60dp.xml b/app/src/main/res/drawable/ic_menu_heart_60dp.xml deleted file mode 100644 index 9cee08b..0000000 --- a/app/src/main/res/drawable/ic_menu_heart_60dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_menu_refresh_white_24dp.xml b/app/src/main/res/drawable/ic_menu_refresh_white_24dp.xml deleted file mode 100644 index cc2d1e0..0000000 --- a/app/src/main/res/drawable/ic_menu_refresh_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_menu_search_white_24dp.xml b/app/src/main/res/drawable/ic_menu_search_white_24dp.xml deleted file mode 100644 index be5ad99..0000000 --- a/app/src/main/res/drawable/ic_menu_search_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_open_in_browser_black_24dp.xml b/app/src/main/res/drawable/ic_open_in_browser_black_24dp.xml deleted file mode 100644 index 3fb9799..0000000 --- a/app/src/main/res/drawable/ic_open_in_browser_black_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_open_in_browser_white_24dp.xml b/app/src/main/res/drawable/ic_open_in_browser_white_24dp.xml deleted file mode 100644 index 3fb9799..0000000 --- a/app/src/main/res/drawable/ic_open_in_browser_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_remove_circle_outline_black_24dp.xml b/app/src/main/res/drawable/ic_remove_circle_outline_black_24dp.xml deleted file mode 100644 index 9af9456..0000000 --- a/app/src/main/res/drawable/ic_remove_circle_outline_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_settings_black_24dp.xml b/app/src/main/res/drawable/ic_settings_black_24dp.xml deleted file mode 100644 index ace746c..0000000 --- a/app/src/main/res/drawable/ic_settings_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_share_black_24dp.xml b/app/src/main/res/drawable/ic_share_black_24dp.xml deleted file mode 100644 index e3fe874..0000000 --- a/app/src/main/res/drawable/ic_share_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_share_white_24dp.xml b/app/src/main/res/drawable/ic_share_white_24dp.xml deleted file mode 100644 index 045bbc0..0000000 --- a/app/src/main/res/drawable/ic_share_white_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_signal_wifi_off_black_24dp.xml b/app/src/main/res/drawable/ic_signal_wifi_off_black_24dp.xml deleted file mode 100644 index 8339d79..0000000 --- a/app/src/main/res/drawable/ic_signal_wifi_off_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_stat_cloud_download_black_24dp.xml b/app/src/main/res/drawable/ic_stat_cloud_download_black_24dp.xml deleted file mode 100644 index 261c312..0000000 --- a/app/src/main/res/drawable/ic_stat_cloud_download_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_tab_archive_black_24dp.xml b/app/src/main/res/drawable/ic_tab_archive_black_24dp.xml deleted file mode 100644 index 8b18a9d..0000000 --- a/app/src/main/res/drawable/ic_tab_archive_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_tab_favorite_black_24dp.xml b/app/src/main/res/drawable/ic_tab_favorite_black_24dp.xml deleted file mode 100644 index cfba5d8..0000000 --- a/app/src/main/res/drawable/ic_tab_favorite_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_tab_fiber_new_black_24dp.xml b/app/src/main/res/drawable/ic_tab_fiber_new_black_24dp.xml deleted file mode 100644 index 6097dff..0000000 --- a/app/src/main/res/drawable/ic_tab_fiber_new_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_widgets_black_24dp.xml b/app/src/main/res/drawable/ic_widgets_black_24dp.xml deleted file mode 100644 index 4abb823..0000000 --- a/app/src/main/res/drawable/ic_widgets_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/font/open_sans.xml b/app/src/main/res/font/open_sans.xml deleted file mode 100644 index f9284b2..0000000 --- a/app/src/main/res/font/open_sans.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/app/src/main/res/font/roboto.xml b/app/src/main/res/font/roboto.xml deleted file mode 100644 index 2641caf..0000000 --- a/app/src/main/res/font/roboto.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/app/src/main/res/layout/activity_add_source.xml b/app/src/main/res/layout/activity_add_source.xml deleted file mode 100644 index df87241..0000000 --- a/app/src/main/res/layout/activity_add_source.xml +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - -