diff --git a/.buildconfig b/.buildconfig new file mode 100644 index 000000000..5f4bc0535 --- /dev/null +++ b/.buildconfig @@ -0,0 +1,10 @@ +[default] +name=Default +runtime=host +config-opts= +run-opts= +prefix=/home/veli/.cache/gnome-builder/install/TrebleShot/host +app-id= +postbuild= +prebuild= +default=true diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 000000000..a5aa3230c --- /dev/null +++ b/.drone.yml @@ -0,0 +1,9 @@ +kind: pipeline +type: docker +name: default + +steps: + - name: build + image: mreichelt/android:30 + commands: + - ./gradlew -Dorg.gradle.jvmargs=-Xms8192m assembleFossReliantDebug lintFossReliantDebug testFossReliantDebugUnitTest diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..bbb79eb93 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: tasali +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..458987a61 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Log files** +After producing the error, go to *About* > *tap to the mail icon*, now the log was saved to your *Received files* under the name of *trebleshot_log(\*).txt*. The asterisk is the file number. Grab the biggest one and paste it here. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment (please complete the following information):** + - Android version: [e.g. Android Marshmallow or 6 / Stock or Custom LineageOS] + - Language: [e.g. Turkish] + - App version [e.g. 1.2.7.6] + - Device Model and Brand [e.g. Samsung Galaxy Note 9+ (SM-N960U)] + +**Additional context** +Add any other context about the problem here. + +*Please remove the placeholders after filling them.* diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 000000000..99bb9a009 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,7 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..066b2d920 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 000000000..ecf029de2 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,26 @@ +# This workflow will build a Java project with Gradle +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ master, dev ] + pull_request: + branches: [ master, dev ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew -Dorg.gradle.jvmargs=-Xms3072m assembleFossReliantDebug lintFossReliantDebug testFossReliantDebugUnitTest diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 7a9a59f08..055a802ae --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ -build -gradle -.gradle -.idea -gradlew.bat -gradlew -TSMAT.iml +/app/build +/build +/.gradle +/.idea +/projectFilesBackup +*.iml +signing.properties local.properties -app/build +licenses.yml +*.apk diff --git a/COPYING b/COPYING new file mode 100755 index 000000000..9350f84f7 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 2 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, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year mName of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details mType `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; mType `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 000000000..461949e50 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,78 @@ +# TrebleShot + +##### What is the device name? + +The device name is the name used to identify TrebleShot users. + +##### What's my default TrebleShot name? + +The default TrebleShot name is the name of your device. + +##### What permission does TrebleShot require? + +TrebleShot needs to access these on your device (requirements may vary): +Phone, location, wifi, hotspot, storage. + +##### How do I access my external storage/SD card on my Android Lollipop (5) or above running device? + +While you are viewing your files, *tap to more options (three dot menu)* then choose *Mount folder* and then *Choose folder / storage you want to access*. Your choice will be present in "Home" section (welcome screen of files). + +##### How do I change my TrebleShot device name? +Click on the menu button at the top left of your TrebleShot screen, click *preference*, select *device name* and input your preferred name, click *ok*. + +##### Can I share system apps? + +Yes, you can add system apps to the list of shareable apps, by going to your TrebleShot preference, under *advance section* and tick the box *show system apps*. + +##### How do I resume a transfer? + +When a transfer fails, some files may be marked as "Interrupted". To resume them, select the individual file +then tap to *Retry* button and then tap to *Start/Resume (play icon)* on the right corner of the screen +to resume them. This will resume it where it was left. To retry all interrupted files tap to *Retry all* +button in *more options (three dot)* menu. + +##### What is the default storage for received files? + +The default storage is your device internal memory under the folder titled *TrebleShot*. + +##### How do I start sharing apps on TrebleShot? + +There are two ways in which you can start sharing files via TrebleShot. +* QR code scan. +* Scan for other TrebleShot devices via Wifi. + +* Switch on your device Wi-Fi, open your Trebleshot app and click *connect* a QR scanner would appear, place the scanner over the host device QR code, for automatic scan and connection. + +* Open your TrebleShot app click on scan a prompt message will appear, asking you to enable your device Wi-Fi, select *enable*, and allow TrebleShot access your device location, click *scan* again, and TrebleShot would search for nearby TrebleShot devices. +A list of available TrebleShot devices would appear for selection. + +##### How do I check my received files from TrebleShot? + +Your received files would appear on your device notification. + +##### How do I monitor the file transfer progress? + +Click the menu button at the top left of the screen and select *pending transfer*. + +##### Where can I view the list of files sharable via my TrebleShot app? + +Click the menu button at the top left and select share this would display options for apps, music, pictures, and videos. + +##### How do I share files? +Get to the list of files via the immediate above answer and click on your preferred file, click the *arrow button* at the bottom of the page to send. + +##### Can I share files from TrebleShot to other apps? + +Yes, you can share apps or files from TrebleShot to other apps like Whatsapp. +Select the file you want to send, then click the dotted vertical line seen after the send icon, select *share with other apps*, and a list of apps would appear. + +##### What is TrustZone? + +When you don't want to prompted every time you receive a file from certain devices, +you can turn it on and mark those devices as *TrustZone* device. You can find more +tweak how it should act in the preferences. + +##### I have more questions, need to contact the developer? +Click on the menu button at the top left, and select *about* click the *mail* icon at the top right and send us a mail. + +Thank you for using TrebleShot! diff --git a/Privacy.md b/Privacy.md new file mode 100644 index 000000000..00528937b --- /dev/null +++ b/Privacy.md @@ -0,0 +1,88 @@ +# TrebleShot Privacy Policy +This is the developer's policy which is created to give details and informs you if your personal information is collected, used, disclosed and secured by the developer. These policies are stated to ensure you know what personal details you may or may not disclose when the software is being utilized and the choices you have associated with that data. If you decide to continue using this software, it means you accept all terms and conditions stated by the developer in this policy.
+ +## Permission and Access + +TrebleShot software requires some permissions to function effectively and efficiently. Without some of these permissions enabled, you might be unable to perform certain operations with the software. +TrebleShot requires the enablement of the following permissions; +- camera +- location +- storage +- telephone +The main permission required to get TrebleShot functioning is the storage permission, the rest permission are sometimes required to perform some operations but they are optional and can be enabled and disabled anytime at will when not in use. + +TrebleShot have some other capabilities which are functions the software can remotely effect on its own. + +- **view wifi connections**: This capability allows the app to view information about wifi networking such as whether wifi is enabled and the name of connected wifi devices. + +- **connect and disconnect from wifi**: This allows the app to connect and disconnect from wifi access points and to make changes to the device configuration for wifi networks. + +- **change network connectivity**: This is the capability of the app to change the state of network connectivity. + +- **have full network access**: The app is capable of creating network sockets and can use customized network protocols. This capability does not require the software to send data to the internet. + +- **view network connections**: This is the capability of the app to view information about network connections such as which networks exist and are connected. + +- **control vibration**: This app can control your device vibrator. + +- **prevent phone from sleeping**: When running, the app has the capability to prevent your device from sleeping. + +
+ +## Information and data collection + +The developer does not externally collect any information or data when the services of TrebleShot are utilized. When performing some activities with the software, personal data is not collected by the because developer all the information are locally stored on your device. Information contained in a file might be shared between paired devices during an operation but not in any way linked to the developer. +
+ +## Crash reports sending + +TrebleShot is stable and works fine but you may still experience few errors and crashes, information about these crashes are important to us so the issues can be fixed immediately and the software is running efficiently again. +If you encounter a bug during the use of this software, please open an [issue](https://github.com/genonbeta/TrebleShot/issues/new) on GitHub and it will be fixed in due time. You can also send an email directly to the developer from within the software. When submitting a crash report, please provide a logcat to help the developers pinpoint the cause of the crash quickly and fix it accordingly.
+ +## Service Providers + +TrebleShot is dependent upon the services of third-party libraries to facilitate its utilization and to perform service-related services or to assist in analyzing how the services of the software are used. +These third parties have access to your Personal Data only to perform these tasks on the developer's behalf and are obligated not to disclose or use it for any other purpose.
+ + +## Transfer Of Data + +With these third-party libraries contained in the software, your information, including personal data, may be transferred to computers located outside of your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from your jurisdiction. Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer.
+ +## Links To Other Sites + +TrebleShot contains links to other services that are not directly operated by the developer. If you click on a third party library webpage or license URL, you will be directed to that third party's site. +The developer has no control over and assumes no responsibility for the content, privacy policies or practices of any third party sites or services. +There are also links within the app that redirects you to user profiles of the contributors of this project on Github and there exist also a link to where the app can be translated.
+ +## Changes To This Privacy Policy + +The developer may update this policy as there will be further development of the software and these developments will likely not be consistent with some statements and terms contained in this current policy. +It is advisable to check this policy periodically for any changes. Changes to this Privacy Policy are effective when they are made on this document. + +## About Us + +TrebleShot is a project developed by Genonbeta Libre Software Org. The development of the TrebleShot app is a group effort of different individuals who have contributed immensely to the project. The project has also received a lot of contributions from the open source community on Github in form of suggestions, bug reports, and fixes, translations, and documentation. + +TrebleShot is an open source Android software that solves file sharing problems, such as slow sending speed over a Bluetooth connection, failed to send operation etc. These problems are solved with TrebleShot which utilizes the wifi network as a means of transmission and files are shared seamlessly over a wifi connection to another device. TrebleShot is designed to allow this operation to be paused or resumed at any time even when there is a case of an error. TrebleShot utilizes wifi network and not an internet connection meaning that internet data is not consumed when the software is utilized and also even in areas where there is a poor internet connection, the software still functions optimally. +The software allows multiple files of different formats to be sent simultaneously. + +### Donate +If you value this work, you can help it. There are a too few methods that work in Turkey, you can use Bitcoin or install Google Play version to do so (In-App Billing method) (taxes will apply). + +**Bitcoin Address**: [1DBsq8aZjn54hnDYsRY7pTLb3HfsE1mSv8](https://blockchain.info/address/1DBsq8aZjn54hnDYsRY7pTLb3HfsE1mSv8) + + +![BitcoinQR](assets/1DBsq8aZjn54hnDYsRY7pTLb3HfsE1mSv8.png) + +
+ +## Contact Us + +If you have any questions regarding this Privacy Policy or the practices of this software, please contact us by sending an email to genonbeta@gmail.com or by opening an [issue](https://github.com/genonbeta/TrebleShot/issues/new) on Github +Last Updated: This Privacy Policy was last updated on [08-10-2018]. + diff --git a/README.md b/README.md old mode 100644 new mode 100755 index ddb6ff15b..fc0576bf8 --- a/README.md +++ b/README.md @@ -1,7 +1,85 @@ -

TrebleShot is File-Sending Application

-With this application application you can send files over the nearby connections like Wi-Fi or Ethernet -

-Supports Android 4+
-Uses low ram
-Always ready
-Secure +# TrebleShot libre software file and text sharing app +Send and receive files over available connections. [FAQ](https://github.com/genonbeta/TrebleShot/blob/master/FAQ.md). + +## Get it on +[](https://f-droid.org/packages/org.monora.uprotocol.client.android/) [](https://play.google.com/store/apps/details?id=org.monora.uprotocol.client.android) + +An alpha version of desktop application is also available for sharing with desktop. Get it [here](https://github.com/genonbeta/TrebleShot-Desktop) + +## Main features +* Pause and resume transfers +* Share large files +* Share all kinds of content, video, photo, music and app libraries +* No Internet connection is required: Set up a hotspot and connect to it using a QR code +* Share between multiple devices at the same time +* Exchange texts of any kind and save them to TrebleShot +* Share folders for backup and other purposes +* Light UI: Works faster than its rivals on low-end devices +* Speed-oriented: Minimal UI optimized for speed +* Advanced features: Network change handling, choose network based on measured speed + +## Project status +[![Build Status](https://travis-ci.org/genonbeta/TrebleShot.svg)](https://travis-ci.org/genonbeta/TrebleShot) +[![Translation Status](https://hosted.weblate.org/widgets/trebleshot/-/svg-badge.svg)](https://hosted.weblate.org/engage/trebleshot/) + +## Build from source +Run the following command. This will build `fossReliant` variant in debug mode. +```sh +./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleFossReliantDebug lintFossReliantDebug testFossReliantDebugUnitTest +``` +If you are facing errors, checkout the latest stable release using its tag name. + +## Pull requests +Before making a pull request, please make sure your commits follow the requirements below. +* Make sure you are on the `dev` branch. `master` branch is updated before releasing a new stable release. +* Open an issue regarding the pull request you are about to make if possible. +* Use spaces instead of tabs. +* Use LF (Unix and Linux) line separator. +* The right margin is limited to 120 chars and overflowing lines wrapped in a meaningful way. That is: + * logic statements are wrapped before `&&` or `||`. + * method calls are wrapped after the most fitting parameter. + * inner logic statements with parenthesis should be together if they can fit a single line. +* The opening braces `{` after the class and method declarations should come with the next line. +* When you reformat code, please make sure the previously untouched code does not change in an undefined way. +* Do not create simple methods for the sake of creating methods. For instance, do not a create a method +that compares two booleans like `function isTrue(boolean a, bolean b) { return a == b; }`. +* If you plan ignore an exception make sure the parameter name in the `catch` block is *ignored*. This will silence the +warnings. +* Add `todo` and `fixme` comments when needed. In short, add `// todo: What to do` or `// fixme: What is the problem` +before the appropriate code. This will ensure they are not forgotten. +* Do not duplicate code blocks. Create methods or classes when needed. +* Variable names should follow the pattern below: + * Constants are `SOME_VARIABLE`. + * Global variables are `mSomeVariable` if not public or `someVariable` if public. + * Local variables are `someVariable`. +* If blocks (do, while, for, if) will contain a single line, they can be free of braces. Also, make sure they are +wrapped. +* Headless braces `{}` can be used for scoping. +* When creating strings for localization, please follow the patterns suggested in +[Language contribution](https://github.com/genonbeta/TrebleShot/wiki/Language-contribution) wiki. +* Other than that some other good practises that you can follow would be: + * making sure your code backward compatible with the minimum SDK version defined in the main module. + * committing your changes when you are about to change something unrelated. + + +## Screenshots +[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png) +[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png) +[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png) +[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png) +[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png) +[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png) +[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png) +[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png) +[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png) + +## Localization +To translate TrebleShot, please read +[this wiki](https://github.com/genonbeta/TrebleShot/wiki/Language-contribution) first if you haven't +worked with Weblate, where you will find the [TrebleShot localization](https://hosted.weblate.org/engage/TrebleShot/). + + +[![Translation Status](https://hosted.weblate.org/widgets/trebleshot/-/multi-auto.svg)](https://hosted.weblate.org/engage/TrebleShot/) + + +This app is licensed under GNU Public License version 2.0 or later version. diff --git a/TrebleShot.iml b/TrebleShot.iml deleted file mode 100644 index e8fa09f87..000000000 --- a/TrebleShot.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/app-release.apk b/app/app-release.apk deleted file mode 100644 index 7f4ed9391..000000000 Binary files a/app/app-release.apk and /dev/null differ diff --git a/app/app.iml b/app/app.iml deleted file mode 100644 index 580ceb3cb..000000000 --- a/app/app.iml +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle old mode 100644 new mode 100755 index ae49c4b93..783195f2c --- a/app/build.gradle +++ b/app/build.gradle @@ -1,31 +1,185 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-parcelize' +apply plugin: 'dagger.hilt.android.plugin' +apply plugin: 'com.cookpad.android.plugin.license-tools' +apply plugin: 'androidx.navigation.safeargs.kotlin' android { - compileSdkVersion 25 - buildToolsVersion '25.0.1' + compileSdkVersion 30 + + buildFeatures { + dataBinding true + } + + signingConfigs { + gbDistribution { + // In the root folder of the project, you can find the template properties file + // named 'signing.properties.txt'. Rename it to 'signing.properties' to make use of it + def signingPropertiesFile = project.rootProject.file('signing.properties') + + if (signingPropertiesFile.exists()) { + Properties signingProperties = new Properties() + signingProperties.load(signingPropertiesFile.newDataInputStream()) - aaptOptions.cruncherEnabled = false - aaptOptions.useNewCruncher = false + storeFile file(signingProperties.getProperty('signing.storeFile')) + keyAlias signingProperties.getProperty('signing.keyAlias') + storePassword signingProperties.getProperty('signing.storePassword') + keyPassword signingProperties.getProperty('signing.keyPassword') + } + } + } + + compileOptions { + coreLibraryDesugaringEnabled true + + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } defaultConfig { - applicationId "com.genonbeta.TrebleShot" + applicationId "org.monora.uprotocol.client.android" minSdkVersion 14 - targetSdkVersion 21 - versionCode 36 - versionName "1.1.5" + targetSdkVersion 30 + versionCode 99 + versionName "2.0" + multiDexEnabled true + + vectorDrawables { + useSupportLibrary = true + } } + + lintOptions { + checkReleaseBuilds false + abortOnError false + } + buildTypes { release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + + debug { + applicationIdSuffix '.debug' + versionNameSuffix '-DEBUG' + } + } + + flavorDimensions 'mode' + + productFlavors { + fossReliant { + dimension 'mode' + signingConfig signingConfigs.gbDistribution + } + + googlePlay { + dimension 'mode' + signingConfig signingConfigs.gbDistribution + } } } +task copyHebrewResources(type: Copy) { + // this task copies Hebrew related resources to a folder with iw alias + // If it is causing issues, removing will only affect Hebrew resources + // https://github.com/genonbeta/TrebleShot/issues/67 #67 + + File mergedResourcesDir = new File("${buildDir}/mergedResources") + + if (!mergedResourcesDir.isDirectory()) + mergedResourcesDir.mkdirs() + + for (String resEach : android.sourceSets.main.res.srcDirs) { + File currentFolder = new File(resEach) + + for (File folderContents : currentFolder.listFiles()) { + if (folderContents.name.endsWith("-he")) { + String iwNamedFolder = "${folderContents.name.substring(0, folderContents.name.lastIndexOf("-he"))}-iw" + + from folderContents.path + into "${mergedResourcesDir.path}/${iwNamedFolder}" + println("Copying resource ${folderContents.path}") + } + } + } + + println("Adding merged resource directories to the resources array ${mergedResourcesDir.path}") + android.sourceSets.main.res.srcDirs += mergedResourcesDir.path +} + +preBuild.dependsOn copyHebrewResources + dependencies { - compile fileTree(include: ['*.jar'], dir: 'libs') - compile project(path: ':updatewithgithub') - compile 'com.android.support:design:25.0.1' - compile 'com.android.support:cardview-v7:25.0.1' - compile 'com.android.support:appcompat-v7:25.0.1' + testImplementation 'junit:junit:4.13' + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation project(':core') + implementation project(':database') + implementation project(':zxing') + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + implementation 'androidx.core:core-ktx:1.3.2' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.collection:collection-ktx:1.1.0' + implementation 'androidx.fragment:fragment-ktx:1.3.2' + implementation 'androidx.gridlayout:gridlayout:1.0.0' + implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" + implementation "androidx.navigation:navigation-ui-ktx:$nav_version" + implementation 'androidx.preference:preference-ktx:1.1.1' + implementation 'androidx.recyclerview:recyclerview:1.2.0' + implementation 'androidx.room:room-ktx:2.2.6' + implementation 'androidx.room:room-runtime:2.2.6' + kapt 'androidx.room:room-compiler:2.2.6' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' + implementation 'androidx.viewpager2:viewpager2:1.0.0' + implementation 'com.google.android.material:material:1.3.0' + implementation "com.google.dagger:hilt-android:$hilt_version" + kapt "com.google.dagger:hilt-android-compiler:$hilt_version" + implementation 'com.madgag.spongycastle:bcpkix-jdk15on:1.58.0.0' + implementation 'org.monora.uprotocol:core:0.1.0-ALPHA31' + implementation 'org.apache.maven:maven-artifact:3.6.3' + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + implementation 'org.nanohttpd:nanohttpd:2.2.0' + implementation 'com.github.bumptech.glide:glide:4.10.0' + kapt 'androidx.annotation:annotation:1.2.0' + kapt 'com.github.bumptech.glide:compiler:4.10.0' + implementation 'com.github.bumptech.glide:okhttp3-integration:4.10.0' + googlePlayImplementation 'com.anjlab.android.iab.v3:library:1.0.44' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' + implementation 'com.google.zxing:core:3.3.0' // Needed because 'zxing' module won't export it. Do not update! +} + +repositories { + mavenCentral() } diff --git a/app/fossReliant/release/output-metadata.json b/app/fossReliant/release/output-metadata.json new file mode 100644 index 000000000..f2099a514 --- /dev/null +++ b/app/fossReliant/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "org.monora.uprotocol.client.android", + "variantName": "fossReliantRelease", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "properties": [], + "versionCode": 99, + "versionName": "2.0", + "enabled": true, + "outputFile": "app-fossReliant-release.apk" + } + ] +} \ No newline at end of file diff --git a/app/fossReliant/release/output.json b/app/fossReliant/release/output.json new file mode 100644 index 000000000..45241263f --- /dev/null +++ b/app/fossReliant/release/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":99,"versionName":"1.4.3","enabled":true,"outputFile":"app-fossReliant-release.apk","fullName":"fossReliantRelease","baseName":"fossReliant-release"},"path":"app-fossReliant-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/googlePlay/release/app-googlePlay-release.aab b/app/googlePlay/release/app-googlePlay-release.aab new file mode 100644 index 000000000..8a86ab467 Binary files /dev/null and b/app/googlePlay/release/app-googlePlay-release.aab differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro old mode 100644 new mode 100755 index ebce3e333..be71f9825 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,17 +1,7 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/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 +-keep class org.spongycastle.** { *; } -# Add any project specific keep options here: +# Application classes that will be serialized/deserialized over Gson +-keep class org.monora.uprotocol.client.android.model.** { *; } +-keep class org.monora.uprotocol.client.android.database.model.** { *; } -# 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 *; -#} +-keep class * extends androidx.fragment.app.Fragment{} \ No newline at end of file diff --git a/app/src/debug/res/values/colors.xml b/app/src/debug/res/values/colors.xml new file mode 100644 index 000000000..22d9c5417 --- /dev/null +++ b/app/src/debug/res/values/colors.xml @@ -0,0 +1,24 @@ + + + + + + #afb42b + + \ No newline at end of file diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml new file mode 100755 index 000000000..67c57b94a --- /dev/null +++ b/app/src/debug/res/values/strings.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/app/src/debug/res/values/strings_config.xml b/app/src/debug/res/values/strings_config.xml new file mode 100755 index 000000000..d9df81a14 --- /dev/null +++ b/app/src/debug/res/values/strings_config.xml @@ -0,0 +1,22 @@ + + + + + org.monora.uprotocol.client.android.debug + \ No newline at end of file diff --git a/app/src/googlePlay/AndroidManifest.xml b/app/src/googlePlay/AndroidManifest.xml new file mode 100755 index 000000000..61c6d2386 --- /dev/null +++ b/app/src/googlePlay/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/googlePlay/java/org/monora/uprotocol/client/android/activity/DonationActivity.kt b/app/src/googlePlay/java/org/monora/uprotocol/client/android/activity/DonationActivity.kt new file mode 100644 index 000000000..5146e2c8c --- /dev/null +++ b/app/src/googlePlay/java/org/monora/uprotocol/client/android/activity/DonationActivity.kt @@ -0,0 +1,82 @@ +package org.monora.uprotocol.client.android.activity + +import android.os.Bundle +import android.view.animation.AlphaAnimation +import android.view.animation.Animation +import android.widget.TextView +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.fragment.external.DonationsFragment + +/** + * created by: veli + * date: 7/12/18 10:32 PM + */ +class DonationActivity : Activity() { + private val animation: Animation by lazy { + AlphaAnimation(0.0f, 1.0f).also { + it.duration = 3000 + it.repeatCount = Animation.INFINITE + it.repeatMode = Animation.REVERSE + + it.setAnimationListener(object : Animation.AnimationListener { + var animPoint = 0 + + var textPoint = 0 + + override fun onAnimationStart(animation: Animation) { + animPoint = 0 + textPoint = 0 + developerText.text = texts[textPoint] + + changeSpeed() + } + + override fun onAnimationEnd(animation: Animation) { + + } + + override fun onAnimationRepeat(animation: Animation) { + animPoint++ + + if (animPoint % 2 == 0) { + animPoint = 0 + textPoint = if (textPoint + 1 >= texts.size) 0 else textPoint + 1 + developerText.text = texts[textPoint] + + changeSpeed() + } + } + + fun changeSpeed() { + animation.duration = (30 * developerText.text.length).toLong() + } + }) + } + } + + private val developerText: TextView by lazy { + findViewById(R.id.developerText) + } + + private val texts: Array by lazy { + getString(R.string.text_prefaceDonation).split(";").toTypedArray() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_donation) + setSupportActionBar(findViewById(R.id.toolbar)) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + override fun onResume() { + super.onResume() + developerText.animation = animation + } + + override fun onPause() { + super.onPause() + developerText.animation = null + } +} \ No newline at end of file diff --git a/app/src/googlePlay/java/org/monora/uprotocol/client/android/fragment/external/DonationsFragment.kt b/app/src/googlePlay/java/org/monora/uprotocol/client/android/fragment/external/DonationsFragment.kt new file mode 100644 index 000000000..2bfeb3411 --- /dev/null +++ b/app/src/googlePlay/java/org/monora/uprotocol/client/android/fragment/external/DonationsFragment.kt @@ -0,0 +1,151 @@ +package org.monora.uprotocol.client.android.fragment.external + +import android.content.Context +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import android.widget.Toast +import com.anjlab.android.iab.v3.BillingProcessor +import com.anjlab.android.iab.v3.BillingProcessor.IBillingHandler +import com.anjlab.android.iab.v3.BillingProcessor.newBillingProcessor +import com.anjlab.android.iab.v3.SkuDetails +import com.anjlab.android.iab.v3.TransactionDetails +import com.genonbeta.android.framework.app.RecyclerViewFragment +import com.genonbeta.android.framework.widget.RecyclerViewAdapter +import com.genonbeta.android.framework.widget.RecyclerViewAdapter.ViewHolder +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.config.AppConfig +import org.monora.uprotocol.client.android.fragment.external.DonationsFragment.DefaultAdapter + +/** + * created by: veli + * date: 7/15/18 10:12 PM + */ +class DonationsFragment : RecyclerViewFragment(), IBillingHandler { + private val billingProcessor: BillingProcessor by lazy { + newBillingProcessor(activity, AppConfig.KEY_GOOGLE_PUBLIC, this) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, + ): View? { + return generateDefaultView(inflater, container, savedInstanceState) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + billingProcessor.initialize() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + adapter = DefaultAdapter(requireContext()) + emptyListImageView.setImageResource(R.drawable.ic_favorite_white_24dp) + emptyListTextView.text = getString(R.string.mesg_noInternetConnection) + useEmptyListActionButton(getString(R.string.butn_retry)) { refreshList() } + } + + override fun onProductPurchased(productId: String, details: TransactionDetails?) { + showToast(R.string.mesg_donationSuccessful) + } + + override fun onPurchaseHistoryRestored() { + + } + + override fun onBillingError(errorCode: Int, error: Throwable?) { + showToast(R.string.mesg_somethingWentWrong) + } + + override fun onBillingInitialized() { + refreshList() + } + + private fun showToast(stringRes: Int) { + Handler(Looper.getMainLooper()).post { + context?.let { + Toast.makeText(it, stringRes, Toast.LENGTH_LONG).show() + } + } + } + + inner class DefaultAdapter( + context: Context, + ) : RecyclerViewAdapter(context) { + private val list: MutableList = ArrayList() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(layoutInflater.inflate(R.layout.list_donations, parent, false)).also { vh -> + vh.itemView.setOnClickListener { + val skuDetails: SkuDetails = list[vh.adapterPosition] + billingProcessor.purchase( + activity, skuDetails.productId, "Donations are not real world items" + ) + } + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val details: SkuDetails = getList()[position] + val text1: TextView = holder.itemView.findViewById(R.id.text) + val text2: TextView = holder.itemView.findViewById(R.id.text2) + val text3: TextView = holder.itemView.findViewById(R.id.text3) + val lastParenthesis: Int = details.title.lastIndexOf(" (") + text1.text = if (lastParenthesis != -1) details.title.substring(0, lastParenthesis) else details.title + text2.text = details.description + text3.text = details.priceText + } + + override fun onLoad(): MutableList { + if (list.isNotEmpty()) { + Log.d(TAG, "Skipped reloading because the list already loaded.") + return list + } + + if (billingProcessor.isInitialized && billingProcessor.isOneTimePurchaseSupported) { + Log.d(TAG, "Configuration is OK") + + val items = arrayListOf( + "trebleshot.donation.1", + "trebleshot.donation.2", + "trebleshot.donation.3", + "trebleshot.donation.4", + "trebleshot.donation.5", + "trebleshot.donation.6", + ) + + // For testing purposes + //items.add("android.test.purchased"); + try { + return billingProcessor.getPurchaseListingDetails(items).toMutableList().also { + it.sortWith { o1: SkuDetails, o2: SkuDetails -> o1.priceLong.compareTo(o2.priceLong) } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + return mutableListOf() + } + + override fun onUpdate(passedItem: MutableList) { + synchronized(list) { + list.clear() + list.addAll(passedItem) + } + } + + override fun getItemCount(): Int = list.size + + override fun getList(): MutableList = list + } + + companion object { + private val TAG = DonationsFragment::class.simpleName + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml old mode 100644 new mode 100755 index 36dddba8c..1514eb07a --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,83 +1,263 @@ - + + + xmlns:tools="http://schemas.android.com/tools" + package="org.monora.uprotocol.client.android"> + + + - - - - - - - - + + + + + + + + + + + + + android:label="@string/text_appName" + android:supportsRtl="true" + android:theme="@style/Theme.TrebleShot" + tools:ignore="GoogleAppIndexingWarning"> + + android:theme="@style/Theme.TrebleShot.NoActionBar"> - - + + - - + + + android:name="org.monora.uprotocol.client.android.activity.SharingActivity" + android:label="@string/text_appName"> - - - - - - - - + + + + + + + + android:name="org.monora.uprotocol.client.android.activity.TransferMemberActivity" + android:label="@string/text_addDevicesToTransfer" + android:theme="@style/Theme.TrebleShot.NoActionBar.StaticStatusBar" /> + android:name="org.monora.uprotocol.client.android.activity.FileExplorerActivity" + android:label="@string/text_fileExplorer" + android:theme="@style/Theme.TrebleShot.NoActionBar.StaticStatusBar" /> - - - + + + - + + + + + + + + + + + - - - + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + - - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/app/src/main/androidTest/com/genonbeta/TrebleShot/AndroidTest.java b/app/src/main/androidTest/com/genonbeta/TrebleShot/AndroidTest.java new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/app/src/main/androidTest/com/genonbeta/TrebleShot/AndroidTest.java @@ -0,0 +1 @@ + diff --git a/app/src/main/assets/licenses.json b/app/src/main/assets/licenses.json new file mode 100644 index 000000000..c673fa82b --- /dev/null +++ b/app/src/main/assets/licenses.json @@ -0,0 +1 @@ +{"libraries":[{"artifactId":{"name":"databinding-adapters","group":"androidx.databinding","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","libraryName":"databinding-adapters"},{"artifactId":{"name":"databinding-runtime","group":"androidx.databinding","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","libraryName":"databinding-runtime"},{"artifactId":{"name":"databinding-common","group":"androidx.databinding","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/studio","libraryName":"databinding-common"},{"artifactId":{"name":"viewbinding","group":"androidx.databinding","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","libraryName":"viewbinding"},{"artifactId":{"name":"lifecycle-runtime","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-runtime"},{"artifactId":{"name":"collection","group":"androidx.collection","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"collection"},{"artifactId":{"name":"lifecycle-common","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-common"},{"artifactId":{"name":"core-common","group":"androidx.arch.core","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"core-common"},{"artifactId":{"name":"annotation","group":"androidx.annotation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"annotation"},{"artifactId":{"name":"kotlin-parcelize-runtime","group":"org.jetbrains.kotlin","version":"+"},"license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-parcelize-runtime"},{"artifactId":{"name":"preference-ktx","group":"androidx.preference","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"preference-ktx"},{"artifactId":{"name":"fragment-ktx","group":"androidx.fragment","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/fragment#1.3.0","libraryName":"fragment-ktx"},{"artifactId":{"name":"activity-ktx","group":"androidx.activity","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/activity#1.2.0","libraryName":"activity-ktx"},{"artifactId":{"name":"core-ktx","group":"androidx.core","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"core-ktx"},{"artifactId":{"name":"kotlin-stdlib-jdk7","group":"org.jetbrains.kotlin","version":"+"},"license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-stdlib-jdk7"},{"artifactId":{"name":"material","group":"com.google.android.material","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/material-components/material-components-android","libraryName":"material"},{"artifactId":{"name":"constraintlayout","group":"androidx.constraintlayout","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://tools.android.com","libraryName":"constraintlayout"},{"artifactId":{"name":"preference","group":"androidx.preference","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"preference"},{"artifactId":{"name":"appcompat","group":"androidx.appcompat","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"appcompat"},{"artifactId":{"name":"cardview","group":"androidx.cardview","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"cardview"},{"artifactId":{"name":"viewpager2","group":"androidx.viewpager2","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"viewpager2"},{"artifactId":{"name":"recyclerview","group":"androidx.recyclerview","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"recyclerview"},{"artifactId":{"name":"gridlayout","group":"androidx.gridlayout","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"gridlayout"},{"artifactId":{"name":"legacy-support-core-ui","group":"androidx.legacy","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"legacy-support-core-ui"},{"artifactId":{"name":"swiperefreshlayout","group":"androidx.swiperefreshlayout","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"swiperefreshlayout"},{"artifactId":{"name":"appcompat-resources","group":"androidx.appcompat","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"appcompat-resources"},{"artifactId":{"name":"drawerlayout","group":"androidx.drawerlayout","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"drawerlayout"},{"artifactId":{"name":"lifecycle-extensions","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-extensions"},{"artifactId":{"name":"hilt-android","group":"com.google.dagger","version":"+"},"license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"hilt-android"},{"artifactId":{"name":"okhttp3-integration","group":"com.github.bumptech.glide","version":"+"},"license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"okhttp3-integration"},{"artifactId":{"name":"glide","group":"com.github.bumptech.glide","version":"+"},"license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"glide"},{"artifactId":{"name":"fragment","group":"androidx.fragment","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/fragment#1.3.0","libraryName":"fragment"},{"artifactId":{"name":"coordinatorlayout","group":"androidx.coordinatorlayout","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"coordinatorlayout"},{"artifactId":{"name":"viewpager","group":"androidx.viewpager","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"viewpager"},{"artifactId":{"name":"slidingpanelayout","group":"androidx.slidingpanelayout","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"slidingpanelayout"},{"artifactId":{"name":"customview","group":"androidx.customview","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"customview"},{"artifactId":{"name":"dynamicanimation","group":"androidx.dynamicanimation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"dynamicanimation"},{"artifactId":{"name":"transition","group":"androidx.transition","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"transition"},{"artifactId":{"name":"vectordrawable-animated","group":"androidx.vectordrawable","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"vectordrawable-animated"},{"artifactId":{"name":"vectordrawable","group":"androidx.vectordrawable","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"vectordrawable"},{"artifactId":{"name":"activity","group":"androidx.activity","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/activity#1.2.0","libraryName":"activity"},{"artifactId":{"name":"legacy-support-core-utils","group":"androidx.legacy","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"legacy-support-core-utils"},{"artifactId":{"name":"loader","group":"androidx.loader","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"loader"},{"artifactId":{"name":"asynclayoutinflater","group":"androidx.asynclayoutinflater","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"asynclayoutinflater"},{"artifactId":{"name":"core","group":"androidx.core","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"core"},{"artifactId":{"name":"collection-ktx","group":"androidx.collection","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"collection-ktx"},{"artifactId":{"name":"versionedparcelable","group":"androidx.versionedparcelable","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"versionedparcelable"},{"artifactId":{"name":"collection","group":"androidx.collection","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"collection"},{"artifactId":{"name":"room-ktx","group":"androidx.room","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"room-ktx"},{"artifactId":{"name":"room-runtime","group":"androidx.room","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"room-runtime"},{"artifactId":{"name":"core","group":"com.google.zxing","version":"+"},"normalizedLicense":"","libraryName":"core"},{"artifactId":{"name":"lifecycle-livedata-ktx","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/lifecycle#2.3.0","libraryName":"lifecycle-livedata-ktx"},{"artifactId":{"name":"lifecycle-runtime-ktx","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/lifecycle#2.3.0","libraryName":"lifecycle-runtime-ktx"},{"artifactId":{"name":"lifecycle-viewmodel-ktx","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/lifecycle#2.3.0","libraryName":"lifecycle-viewmodel-ktx"},{"artifactId":{"name":"bcpkix-jdk15on","group":"com.madgag.spongycastle","version":"+"},"license":"Bouncy Castle Licence","licenseUrl":"http://www.bouncycastle.org/licence.html","normalizedLicense":"Bouncy Castle Licence","url":"http://rtyley.github.io/spongycastle/","libraryName":"bcpkix-jdk15on"},{"artifactId":{"name":"core","group":"org.monora.uprotocol","version":"+"},"license":"MIT License","licenseUrl":"https://github.com/uprotocol/platform-java/blob/master/LICENSE","normalizedLicense":"mit","libraryName":"core"},{"artifactId":{"name":"core","group":"org.monora.coolsocket","version":"+"},"license":"General Public Licence, Version 2.0","licenseUrl":"https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html","normalizedLicense":"General Public Licence, Version 2.0","url":"https://github.com//libcoolsocket/java","libraryName":"core"},{"artifactId":{"name":"converter-gson","group":"com.squareup.retrofit2","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/square/retrofit","libraryName":"converter-gson"},{"artifactId":{"name":"retrofit","group":"com.squareup.retrofit2","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/square/retrofit","libraryName":"retrofit"},{"artifactId":{"name":"nanohttpd","group":"org.nanohttpd","version":"+"},"normalizedLicense":"","libraryName":"nanohttpd"},{"artifactId":{"name":"kotlin-android-extensions-runtime","group":"org.jetbrains.kotlin","version":"+"},"license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-android-extensions-runtime"},{"artifactId":{"name":"lifecycle-livedata-core-ktx","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/lifecycle#2.3.0","libraryName":"lifecycle-livedata-core-ktx"},{"artifactId":{"name":"savedstate-ktx","group":"androidx.savedstate","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/savedstate#1.1.0","libraryName":"savedstate-ktx"},{"artifactId":{"name":"kotlinx-coroutines-android","group":"org.jetbrains.kotlinx","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/Kotlin/kotlinx.coroutines","libraryName":"kotlinx-coroutines-android"},{"artifactId":{"name":"kotlinx-coroutines-core-jvm","group":"org.jetbrains.kotlinx","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/Kotlin/kotlinx.coroutines","libraryName":"kotlinx-coroutines-core-jvm"},{"artifactId":{"name":"kotlin-stdlib","group":"org.jetbrains.kotlin","version":"+"},"license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-stdlib"},{"artifactId":{"name":"cursoradapter","group":"androidx.cursoradapter","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"cursoradapter"},{"artifactId":{"name":"room-common","group":"androidx.room","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"room-common"},{"artifactId":{"name":"sqlite-framework","group":"androidx.sqlite","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"sqlite-framework"},{"artifactId":{"name":"sqlite","group":"androidx.sqlite","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"sqlite"},{"artifactId":{"name":"lifecycle-livedata","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/lifecycle#2.3.0","libraryName":"lifecycle-livedata"},{"artifactId":{"name":"lifecycle-process","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-process"},{"artifactId":{"name":"lifecycle-service","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-service"},{"artifactId":{"name":"lifecycle-runtime","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/lifecycle#2.3.0","libraryName":"lifecycle-runtime"},{"artifactId":{"name":"lifecycle-viewmodel-savedstate","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/lifecycle#2.3.0","libraryName":"lifecycle-viewmodel-savedstate"},{"artifactId":{"name":"lifecycle-livedata-core","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/lifecycle#2.3.0","libraryName":"lifecycle-livedata-core"},{"artifactId":{"name":"core-runtime","group":"androidx.arch.core","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"core-runtime"},{"artifactId":{"name":"savedstate","group":"androidx.savedstate","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/savedstate#1.1.0","libraryName":"savedstate"},{"artifactId":{"name":"core-common","group":"androidx.arch.core","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"core-common"},{"artifactId":{"name":"lifecycle-viewmodel","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/lifecycle#2.3.0","libraryName":"lifecycle-viewmodel"},{"artifactId":{"name":"gifdecoder","group":"com.github.bumptech.glide","version":"+"},"license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"gifdecoder"},{"artifactId":{"name":"interpolator","group":"androidx.interpolator","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"interpolator"},{"artifactId":{"name":"lifecycle-common","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/lifecycle#2.3.0","libraryName":"lifecycle-common"},{"artifactId":{"name":"tracing","group":"androidx.tracing","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/tracing#1.0.0","libraryName":"tracing"},{"artifactId":{"name":"documentfile","group":"androidx.documentfile","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"documentfile"},{"artifactId":{"name":"localbroadcastmanager","group":"androidx.localbroadcastmanager","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"localbroadcastmanager"},{"artifactId":{"name":"print","group":"androidx.print","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"print"},{"artifactId":{"name":"annotation","group":"androidx.annotation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"annotation"},{"artifactId":{"name":"constraintlayout-solver","group":"androidx.constraintlayout","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://tools.android.com","libraryName":"constraintlayout-solver"},{"artifactId":{"name":"annotation-experimental","group":"androidx.annotation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"annotation-experimental"},{"artifactId":{"name":"hilt-core","group":"com.google.dagger","version":"+"},"license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"hilt-core"},{"artifactId":{"name":"dagger","group":"com.google.dagger","version":"+"},"license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"dagger"},{"artifactId":{"name":"dagger-lint-aar","group":"com.google.dagger","version":"+"},"license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"dagger-lint-aar"},{"artifactId":{"name":"jsr305","group":"com.google.code.findbugs","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://findbugs.sourceforge.net/","libraryName":"jsr305"},{"artifactId":{"name":"javax.inject","group":"javax.inject","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://code.google.com/p/atinject/","libraryName":"javax.inject"},{"artifactId":{"name":"prov","group":"com.madgag.spongycastle","version":"+"},"license":"Bouncy Castle Licence","licenseUrl":"http://www.bouncycastle.org/licence.html","normalizedLicense":"Bouncy Castle Licence","url":"http://rtyley.github.io/spongycastle/","libraryName":"prov"},{"artifactId":{"name":"core","group":"com.madgag.spongycastle","version":"+"},"license":"Bouncy Castle Licence","licenseUrl":"http://www.bouncycastle.org/licence.html","normalizedLicense":"Bouncy Castle Licence","url":"http://rtyley.github.io/spongycastle/","libraryName":"core"},{"artifactId":{"name":"json","group":"org.json","version":"+"},"license":"The JSON License","licenseUrl":"http://json.org/license.html","normalizedLicense":"The JSON License","url":"https://github.com/douglascrockford/JSON-java","libraryName":"json"},{"artifactId":{"name":"annotations","group":"org.jetbrains","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/license/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/JetBrains/java-annotations","libraryName":"annotations"},{"artifactId":{"name":"okhttp","group":"com.squareup.okhttp3","version":"+"},"normalizedLicense":"","libraryName":"okhttp"},{"artifactId":{"name":"gson","group":"com.google.code.gson","version":"+"},"normalizedLicense":"","libraryName":"gson"},{"artifactId":{"name":"disklrucache","group":"com.github.bumptech.glide","version":"+"},"license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"disklrucache"},{"artifactId":{"name":"annotations","group":"com.github.bumptech.glide","version":"+"},"license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"annotations"},{"artifactId":{"name":"kotlin-stdlib-common","group":"org.jetbrains.kotlin","version":"+"},"license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-stdlib-common"},{"artifactId":{"name":"junit","group":"junit","version":"+"},"license":"Eclipse Public License 1.0","licenseUrl":"http://www.eclipse.org/legal/epl-v10.html","normalizedLicense":"epl1","url":"http://junit.org","libraryName":"junit"},{"artifactId":{"name":"okio","group":"com.squareup.okio","version":"+"},"normalizedLicense":"","libraryName":"okio"},{"artifactId":{"name":"hamcrest-core","group":"org.hamcrest","version":"+"},"normalizedLicense":"","libraryName":"hamcrest-core"},{"artifactId":{"name":"constraintlayout","group":"androidx.constraintlayout","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://tools.android.com","libraryName":"constraintlayout"},{"artifactId":{"name":"http-request","group":"com.github.kevinsawicki","version":"+"},"license":"MIT License","licenseUrl":"http://www.opensource.org/licenses/mit-license.php","normalizedLicense":"mit","url":"https://github.com/kevinsawicki/http-request","libraryName":"http-request"},{"artifactId":{"name":"maven-artifact","group":"org.apache.maven","version":"+"},"normalizedLicense":"","libraryName":"maven-artifact"},{"artifactId":{"name":"fragment","group":"androidx.fragment","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"fragment"},{"artifactId":{"name":"activity","group":"androidx.activity","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"activity"},{"artifactId":{"name":"lifecycle-runtime","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-runtime"},{"artifactId":{"name":"lifecycle-viewmodel","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-viewmodel"},{"artifactId":{"name":"savedstate","group":"androidx.savedstate","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"savedstate"},{"artifactId":{"name":"lifecycle-livedata","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-livedata"},{"artifactId":{"name":"lifecycle-livedata-core","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-livedata-core"},{"artifactId":{"name":"core-runtime","group":"androidx.arch.core","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"core-runtime"},{"artifactId":{"name":"lifecycle-common","group":"androidx.lifecycle","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-common"},{"artifactId":{"name":"plexus-utils","group":"org.codehaus.plexus","version":"+"},"normalizedLicense":"","libraryName":"plexus-utils"},{"artifactId":{"name":"commons-lang3","group":"org.apache.commons","version":"+"},"normalizedLicense":"","url":"http://commons.apache.org/proper/commons-lang/","libraryName":"commons-lang3"},{"artifactId":{"name":"annotations","group":"org.jetbrains","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://www.jetbrains.org","libraryName":"annotations"},{"artifactId":{"name":"constraintlayout-solver","group":"androidx.constraintlayout","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://tools.android.com","libraryName":"constraintlayout-solver"},{"artifactId":{"name":"navigation-fragment-ktx","group":"androidx.navigation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-fragment-ktx"},{"artifactId":{"name":"navigation-ui-ktx","group":"androidx.navigation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-ui-ktx"},{"artifactId":{"name":"navigation-runtime-ktx","group":"androidx.navigation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-runtime-ktx"},{"artifactId":{"name":"navigation-common-ktx","group":"androidx.navigation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-common-ktx"},{"artifactId":{"name":"navigation-ui","group":"androidx.navigation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-ui"},{"artifactId":{"name":"navigation-fragment","group":"androidx.navigation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-fragment"},{"artifactId":{"name":"navigation-runtime","group":"androidx.navigation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-runtime"},{"artifactId":{"name":"navigation-common","group":"androidx.navigation","version":"+"},"license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-common"},{"artifactId":{"name":"commons-codec","group":"commons-codec","version":"+"},"normalizedLicense":"","url":"http://commons.apache.org/proper/commons-codec/","libraryName":"commons-codec"}]} \ No newline at end of file diff --git a/app/src/main/assets/webshare/help.html b/app/src/main/assets/webshare/help.html new file mode 100644 index 000000000..a204a75d6 --- /dev/null +++ b/app/src/main/assets/webshare/help.html @@ -0,0 +1,29 @@ + + +
+
${help_title}
+
+ +
+ ${apk_filename} +
+ +
+ ${licence_text} +
\ No newline at end of file diff --git a/app/src/main/assets/webshare/home.html b/app/src/main/assets/webshare/home.html new file mode 100644 index 000000000..1c9a33ff2 --- /dev/null +++ b/app/src/main/assets/webshare/home.html @@ -0,0 +1,171 @@ + + + + + + + + + + + ${title} + + + +
+ + + + + + +
+ + Logo + + + ${header} +
+ ${title_header} +
+ + + ${help_alt} + +
+
+
+ ${main_content} +
+ +
+
+ + +
+
+
+ ${footer_text} +
+ + + \ No newline at end of file diff --git a/app/src/main/assets/webshare/image/arrow-down.svg b/app/src/main/assets/webshare/image/arrow-down.svg new file mode 100644 index 000000000..f0b197b7f --- /dev/null +++ b/app/src/main/assets/webshare/image/arrow-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/assets/webshare/image/arrow-left.svg b/app/src/main/assets/webshare/image/arrow-left.svg new file mode 100644 index 000000000..72f5e6ddb --- /dev/null +++ b/app/src/main/assets/webshare/image/arrow-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/assets/webshare/image/help-circle.svg b/app/src/main/assets/webshare/image/help-circle.svg new file mode 100644 index 000000000..0eb4857d0 --- /dev/null +++ b/app/src/main/assets/webshare/image/help-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/assets/webshare/image/icon.png b/app/src/main/assets/webshare/image/icon.png new file mode 100644 index 000000000..b7a0aaa10 Binary files /dev/null and b/app/src/main/assets/webshare/image/icon.png differ diff --git a/app/src/main/assets/webshare/layout_not_found.html b/app/src/main/assets/webshare/layout_not_found.html new file mode 100644 index 000000000..60995e605 --- /dev/null +++ b/app/src/main/assets/webshare/layout_not_found.html @@ -0,0 +1,25 @@ + + +
+ ${content} +
+ +
+ ${detail} +
\ No newline at end of file diff --git a/app/src/main/assets/webshare/list_transfer.html b/app/src/main/assets/webshare/list_transfer.html new file mode 100644 index 000000000..3ceba1e98 --- /dev/null +++ b/app/src/main/assets/webshare/list_transfer.html @@ -0,0 +1,34 @@ + + + +
+ + + + + +
+ ${action_layout} + +
+ ${content} +
+
+
+
\ No newline at end of file diff --git a/app/src/main/assets/webshare/list_transfer_group.html b/app/src/main/assets/webshare/list_transfer_group.html new file mode 100644 index 000000000..2bfa76501 --- /dev/null +++ b/app/src/main/assets/webshare/list_transfer_group.html @@ -0,0 +1,28 @@ + + + +
+ + ${content} + + + ${action_layout} + +
+
\ No newline at end of file diff --git a/app/src/main/java/com/genonbeta/CoolSocket/CoolCommunication.java b/app/src/main/java/com/genonbeta/CoolSocket/CoolCommunication.java deleted file mode 100644 index 340b21972..000000000 --- a/app/src/main/java/com/genonbeta/CoolSocket/CoolCommunication.java +++ /dev/null @@ -1,300 +0,0 @@ -package com.genonbeta.CoolSocket; - -import java.io.IOException; -import java.io.PrintWriter; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; - -public abstract class CoolCommunication extends CoolSocket -{ - public CoolCommunication() - { - } - - public CoolCommunication(int port) - { - super(port); - } - - public CoolCommunication(String address, int port) - { - super(address, port); - } - - @Override - protected void onPacketReceived(Socket socket) - { - try - { - PrintWriter writer = this.getStreamWriter(socket.getOutputStream()); - String message = this.readStreamMessage(socket.getInputStream()); - - this.onMessage(socket, message, writer, socket.getInetAddress().isAnyLocalAddress() ? "127.0.0.1" : socket.getInetAddress().getHostAddress()); - - writer.append(CoolSocket.END_SEQUENCE); - writer.flush(); - - socket.close(); - } catch (IOException e) - { - this.onError(e); - } - } - - abstract protected void onMessage(Socket socket, String message, PrintWriter writer, String clientIp); - - public static class Messenger - { - public static final int NO_TIMEOUT = -1; - - public static void send(String socketHost, int socketPort, String message, ResponseHandler handler) - { - send(new InetSocketAddress(socketHost, socketPort), message, handler); - } - - public static void send(InetSocketAddress address, String message, ResponseHandler handler) - { - SenderRunnable runnable = new SenderRunnable(address, message, handler); - new Thread(runnable).start(); - } - - public static boolean sendOnCurrentThread(String socketHost, int socketPort, String message, ResponseHandler handler) - { - return sendOnCurrentThread(new InetSocketAddress(socketHost, socketPort), message, handler); - } - - public static boolean sendOnCurrentThread(InetSocketAddress address, String message, ResponseHandler handler) - { - SenderRunnable runnable = new SenderRunnable(address, message, handler); - - return runnable.runProcess(); - } - - private static class SenderRunnable implements Runnable - { - private Process mProcess; - - public SenderRunnable(SocketAddress address, String message, ResponseHandler handler) - { - this.mProcess = new Process(address, message, handler); - } - - @Override - public void run() - { - runProcess(); - } - - public boolean runProcess() - { - if (this.mProcess.getResponseHandler() != null) - this.mProcess.getResponseHandler().onConfigure(this.mProcess); - - Socket socket = new Socket(); - - try - { - socket.bind(null); - socket.connect(this.mProcess.getSocketAddress()); - - if (this.mProcess.getSocketTimeout() != NO_TIMEOUT) - socket.setSoTimeout(this.mProcess.getSocketTimeout()); - - PrintWriter writer = getStreamWriter(socket.getOutputStream()); - - this.mProcess.setSocket(socket); - this.mProcess.setWriter(writer); - - if (this.mProcess.getMessage() != null) - writer.append(this.mProcess.getMessage()); - - if (this.mProcess.getResponseHandler() != null) - this.mProcess.getResponseHandler().onMessage(socket, mProcess, writer); - - this.mProcess.waitForResponse(); - - if (this.mProcess.getResponseHandler() != null) - this.mProcess.getResponseHandler().onResponseAvaiable(this.mProcess.getResponse()); - - return true; - } catch (IOException e) - { - if (this.mProcess.getResponseHandler() != null) - this.mProcess.getResponseHandler().onError(e); - } - finally - { - if (this.mProcess.getResponseHandler() != null) - this.mProcess.getResponseHandler().onFinal(); - } - - return false; - } - } - - public static class Process - { - private SocketAddress mAddress; - private ResponseHandler mHandler; - private String mMessage; - private String mResponse; - private PrintWriter mWriter; - private Socket mSocket; - private Object mPutLater; - - private boolean mResponseReceived = false; - private boolean mIsFlushRequested = false; - private int mSocketTimeout = Messenger.NO_TIMEOUT; - - public Process(SocketAddress address, String message, ResponseHandler handler) - { - this.mAddress = address; - this.mMessage = message; - this.mHandler = handler; - } - - public String getMessage() - { - return this.mMessage; - } - - public Object getPutLater() - { - return this.mPutLater; - } - - public PrintWriter getPrintWriter() - { - return this.mWriter; - } - - public String getResponse() - { - return this.mResponse; - } - - public ResponseHandler getResponseHandler() - { - return this.mHandler; - } - - public Socket getSocket() - { - return this.mSocket; - } - - public void setSocket(Socket socket) - { - this.mSocket = socket; - } - - public SocketAddress getSocketAddress() - { - return this.mAddress; - } - - public int getSocketTimeout() - { - return this.mSocketTimeout; - } - - public void setSocketTimeout(int timeout) - { - this.mSocketTimeout = timeout; - } - - public boolean isResponseReceived() - { - return this.mResponseReceived; - } - - public void setResponseReceived(String response) - { - this.mResponse = response; - this.mResponseReceived = true; - } - - public boolean isFlushRequested() - { - return this.mIsFlushRequested; - } - - public void putLater(Object object) - { - this.mPutLater = object; - } - - public boolean requestFlush() - { - if (!this.isFlushRequested() && this.getPrintWriter() != null) - { - this.mIsFlushRequested = true; - - if (this.getPutLater() != null) - { - this.getPrintWriter().append(this.getPutLater().toString()); - this.mPutLater = null; - } - - this.getPrintWriter().append(CoolSocket.END_SEQUENCE); - this.getPrintWriter().flush(); - - try - { - this.setResponseReceived(readStreamMessage(this.getSocket().getInputStream())); - } catch (IOException e) - { - return false; - } - - return true; - } - - return false; - } - - public void setWriter(PrintWriter writer) - { - this.mWriter = writer; - } - - public String waitForResponse() - { - long timeStart = System.currentTimeMillis(); - - if (this.requestFlush()) - { - while (!this.isResponseReceived() && (this.getSocketTimeout() == Messenger.NO_TIMEOUT || (System.currentTimeMillis() - timeStart) < this.getSocketTimeout())) - { - } - } - - return this.getResponse(); - } - } - - public static class ResponseHandler - { - public void onConfigure(Process process) - { - } - - public void onMessage(Socket socket, Process process, PrintWriter response) - { - } - - public void onResponseAvaiable(String response) - { - } - - public void onError(Exception exception) - { - } - - public void onFinal() - { - } - } - } -} diff --git a/app/src/main/java/com/genonbeta/CoolSocket/CoolJsonCommunication.java b/app/src/main/java/com/genonbeta/CoolSocket/CoolJsonCommunication.java deleted file mode 100644 index 9e3f90061..000000000 --- a/app/src/main/java/com/genonbeta/CoolSocket/CoolJsonCommunication.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.genonbeta.CoolSocket; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.PrintWriter; -import java.net.Socket; - -public abstract class CoolJsonCommunication extends CoolCommunication -{ - public static final int NO_TAB = -1; - - private int mAddTabsToResponse = NO_TAB; - private boolean mAllowMalformedRequest = false; - - public CoolJsonCommunication() - { - } - - public CoolJsonCommunication(int port) - { - super(port); - } - - public CoolJsonCommunication(String address, int port) - { - super(address, port); - } - - public boolean isMalformedRequestAllowed() - { - return this.mAllowMalformedRequest; - } - - public int getAddTabsToResponse() - { - return this.mAddTabsToResponse; - } - - public void setAddTabsToResponse(int line) - { - if (line >= NO_TAB) - this.mAddTabsToResponse = line; - } - - public void setAllowMalformedRequest(boolean allow) - { - this.mAllowMalformedRequest = allow; - } - - @Override - protected void onMessage(Socket socket, String message, PrintWriter writer, String clientIp) - { - JSONObject receivedMessage = null; - - try - { - receivedMessage = new JSONObject(message); - } catch (JSONException e) - { - this.onError(e); - } - - if (receivedMessage == null && !this.isMalformedRequestAllowed()) - return; // request cannot be parsed && malformed requests are not allowed - - if (receivedMessage == null) - receivedMessage = new JSONObject(); - - try - { - JSONObject responseJson = new JSONObject(); - - this.onJsonMessage(socket, receivedMessage, responseJson, clientIp); - - writer.append((this.getAddTabsToResponse() > NO_TAB) ? responseJson.toString(this.getAddTabsToResponse()) : responseJson.toString()); - writer.flush(); - } catch (JSONException e) - { - this.onError(e); - } - } - - public abstract void onJsonMessage(Socket socket, JSONObject receivedMessage, JSONObject response, String clientIp); - - public static abstract class JsonResponseHandler extends Messenger.ResponseHandler - { - @Override - public void onMessage(Socket socket, CoolCommunication.Messenger.Process process, final PrintWriter response) - { - JSONObject json = new JSONObject(); - process.putLater(json); - - this.onJsonMessage(socket, process, json); - } - - public abstract void onJsonMessage(Socket socket, CoolCommunication.Messenger.Process process, JSONObject json); - } -} diff --git a/app/src/main/java/com/genonbeta/CoolSocket/CoolSocket.java b/app/src/main/java/com/genonbeta/CoolSocket/CoolSocket.java deleted file mode 100644 index 196b22385..000000000 --- a/app/src/main/java/com/genonbeta/CoolSocket/CoolSocket.java +++ /dev/null @@ -1,288 +0,0 @@ -package com.genonbeta.CoolSocket; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketException; -import java.util.ArrayList; - -abstract public class CoolSocket -{ - public static final String END_SEQUENCE = "\n();;"; - public static final int NO_TIMEOUT = -1; - - private Thread mServerThread; - private ServerSocket mServerSocket; - private SocketAddress mSocketAddress = null; - private int mSocketTimeout = NO_TIMEOUT; // no timeout - private int mMaxConnections = 0; // no limit - private SocketRunnable mSocketRunnable = new SocketRunnable(); - private ArrayList mConnections = new ArrayList(); - - public CoolSocket() - { - } - - public CoolSocket(int port) - { - this.mSocketAddress = new InetSocketAddress(port); - } - - public CoolSocket(String address, int port) - { - this.mSocketAddress = new InetSocketAddress(address, port); - } - - public static PrintWriter getStreamWriter(OutputStream outputStream) - { - return new PrintWriter(new BufferedOutputStream(outputStream)); - } - - public static ByteArrayOutputStream readStream(InputStream inputStreamIns) throws IOException - { - BufferedInputStream inputStream = new BufferedInputStream(inputStreamIns); - ByteArrayOutputStream inputStreamResult = new ByteArrayOutputStream(); - - byte[] buffer = new byte[8096]; - int len = 0; - - do - { - if ((len = inputStream.read(buffer)) > 0) - inputStreamResult.write(buffer, 0, len); - } - while (!inputStreamResult.toString().endsWith(END_SEQUENCE)); - - return inputStreamResult; - } - - public static String readStreamMessage(InputStream inputStream) throws IOException - { - return readStreamMessage(readStream(inputStream)); - } - - public static String readStreamMessage(ByteArrayOutputStream outputStream) - { - String message = outputStream.toString(); - - return message.substring(0, message.length() - END_SEQUENCE.length()); - } - - protected void onClosingConnection(ClientHandler client) - { - } - - abstract protected void onError(Exception exception); - - abstract protected void onPacketReceived(Socket socket); - - public ArrayList getConnections() - { - return this.mConnections; - } - - public int getLocalPort() - { - return this.getServerSocket().getLocalPort(); - } - - protected ServerSocket getServerSocket() - { - return this.mServerSocket; - } - - public SocketAddress getSocketAddress() - { - return this.mSocketAddress; - } - - public void setSocketAddress(SocketAddress address) - { - this.mSocketAddress = address; - } - - protected SocketRunnable getSocketRunnable() - { - return this.mSocketRunnable; - } - - protected Thread getServerThread() - { - return this.mServerThread; - } - - public boolean isComponentsReady() - { - return this.getServerSocket() != null && this.getServerThread() != null && this.getSocketAddress() != null; - } - - public boolean isInterrupted() - { - return this.getServerThread().isInterrupted(); - } - - public boolean isServerAlive() - { - return this.getServerThread().isAlive(); - } - - protected boolean respondRequest(Socket socket) - { - if (this.getConnections().size() < this.mMaxConnections || this.mMaxConnections == 0) - { - ClientHandler clientRunnable = new ClientHandler(socket); - Thread selfThread = new Thread(clientRunnable); - - this.getConnections().add(clientRunnable); - - selfThread.start(); - } - else - return false; - - return true; - } - - public void setMaxConnections(int value) - { - this.mMaxConnections = value; - } - - public void setSocketTimeout(int timeout) - { - this.mSocketTimeout = timeout; - } - - public boolean start() - { - if (this.getServerSocket() == null || this.getServerSocket().isClosed()) - { - try - { - this.mServerSocket = new ServerSocket(); - this.getServerSocket().bind(this.mSocketAddress); - } catch (IOException e) - { - e.printStackTrace(); - return false; - } - } - - if (this.getServerThread() == null || Thread.State.TERMINATED.equals(this.getServerThread().getState())) - { - this.mServerThread = new Thread(this.getSocketRunnable()); - - this.getServerThread().setDaemon(true); - this.getServerThread().setName("CoolSocket Main Thread"); - } - else if (this.getServerThread().isAlive()) - return false; - - this.getServerThread().start(); - - return true; - } - - public boolean startDelayed(int timeout) - { - long startTime = System.currentTimeMillis(); - - while (this.isServerAlive() && (System.currentTimeMillis() - startTime) < timeout) - { - } - - return this.start(); - } - - public boolean stop() - { - if (this.isInterrupted()) - return false; - - this.getServerThread().interrupt(); - - if (!this.getServerSocket().isClosed()) - { - try - { - this.getServerSocket().close(); - } catch (IOException e) - { - e.printStackTrace(); - } - } - - return true; - } - - protected class ClientHandler implements Runnable - { - private Socket mSocket; - - public ClientHandler(Socket socket) - { - this.mSocket = socket; - } - - public InetAddress getAddress() - { - return this.getSocket().getInetAddress(); - } - - public Socket getSocket() - { - return this.mSocket; - } - - @Override - public void run() - { - try - { - if (CoolSocket.this.mSocketTimeout > NO_TIMEOUT) - this.mSocket.setSoTimeout(CoolSocket.this.mSocketTimeout); - } catch (SocketException e) - { - e.printStackTrace(); - } - - CoolSocket.this.onPacketReceived(this.mSocket); - - CoolSocket.this.onClosingConnection(this); - CoolSocket.this.getConnections().remove(this); - } - } - - private class SocketRunnable implements Runnable - { - @Override - public void run() - { - try - { - do - { - Socket request = CoolSocket.this.getServerSocket().accept(); - - if (CoolSocket.this.isInterrupted()) - request.close(); - else - respondRequest(request); - } - while (!CoolSocket.this.isInterrupted()); - } catch (IOException e) - { - CoolSocket.this.onError(e); - } - } - } -} diff --git a/app/src/main/java/com/genonbeta/CoolSocket/CoolTransfer.java b/app/src/main/java/com/genonbeta/CoolSocket/CoolTransfer.java deleted file mode 100644 index e23862340..000000000 --- a/app/src/main/java/com/genonbeta/CoolSocket/CoolTransfer.java +++ /dev/null @@ -1,342 +0,0 @@ -package com.genonbeta.CoolSocket; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.nio.channels.NotYetBoundException; -import java.util.concurrent.ArrayBlockingQueue; - -public class CoolTransfer -{ - public final static int DELAY_DISABLED = -1; - - public abstract static class Send - { - private ArrayBlockingQueue mProcess = new ArrayBlockingQueue(100, true); - private int notifyDelay = CoolTransfer.DELAY_DISABLED; - - public abstract void onError(String serverIp, int port, File file, Exception error, T extra); - - public abstract void onNotify(Socket socket, String serverIp, int port, File file, int percent, T extra); - - public abstract void onTransferCompleted(String serverIp, int port, File file, T extra); - - public abstract void onSocketReady(Socket socket, String serverIp, int port, File file, T extra); - - public abstract boolean onStart(String serverIp, int port, File file, T extra); - - public boolean onBreakRequest(String serverIp, int port, File file, T extra) - { - return false; - } - - public void onStop(String serverIp, int port, File file, T extra) - { - } - - public ArrayBlockingQueue getProcesses() - { - return this.mProcess; - } - - public SendHandler send(String serverIp, int port, File file, byte[] bufferSize, T extra) - { - SendHandler handler = new SendHandler(serverIp, port, file, bufferSize, extra); - Thread thread = new Thread(handler); - - thread.start(); - - return handler; - } - - public SendHandler sendOnCurrentThread(String serverIp, int port, File file, byte[] bufferSize, T extra) - { - SendHandler handler = new SendHandler(serverIp, port, file, bufferSize, extra); - - handler.onRun(); - handler.transferCompleted(); - - return handler; - } - - public void setNotifyDelay(int delay) - { - this.notifyDelay = delay; - } - - public class SendHandler extends TransferHandler - { - protected String mServerIp; - protected int mPort; - protected File mFile; - protected byte[] mBufferSize; - protected T mExtra; - - public SendHandler(String serverIp, int port, File file, byte[] bufferSize, T extra) - { - this.mServerIp = serverIp; - this.mPort = port; - this.mFile = file; - this.mBufferSize = bufferSize; - this.mExtra = extra; - } - - @Override - public boolean isBreakRequested() - { - return super.isBreakRequested() || Send.this.onBreakRequest(this.mServerIp, this.mPort, this.mFile, this.mExtra); - } - - @Override - protected void onRun() - { - Send.this.getProcesses().offer(this); - - if (!Send.this.onStart(this.mServerIp, this.mPort, this.mFile, this.mExtra)) - return; - - try - { - Socket socket = new Socket(); - - socket.bind(null); - socket.connect(new InetSocketAddress(this.mServerIp, this.mPort)); - - Send.this.onSocketReady(socket, this.mServerIp, this.mPort, this.mFile, this.mExtra); - - FileInputStream inputStream = new FileInputStream(this.mFile); - OutputStream outputStream = socket.getOutputStream(); - - int len; - int progressPercent = -1; - long lastNotified = System.currentTimeMillis(); - - while ((len = inputStream.read(this.mBufferSize)) > 0) - { - outputStream.write(this.mBufferSize, 0, len); - outputStream.flush(); - - if (Send.this.notifyDelay == -1 || (System.currentTimeMillis() - lastNotified) > Send.this.notifyDelay) - { - int currentPercent = (int) (((float) 100 / this.mFile.length()) * inputStream.getChannel().position()); - - if (currentPercent > progressPercent) - { - Send.this.onNotify(socket, this.mServerIp, this.mPort, this.mFile, currentPercent, this.mExtra); - progressPercent = currentPercent; - } - - lastNotified = System.currentTimeMillis(); - } - - if (this.isBreakRequested()) - break; - } - - outputStream.close(); - inputStream.close(); - socket.close(); - - Send.this.onTransferCompleted(this.mServerIp, this.mPort, this.mFile, this.mExtra); - } catch (Exception e) - { - Send.this.onError(this.mServerIp, this.mPort, this.mFile, e, this.mExtra); - } finally - { - Send.this.onStop(this.mServerIp, this.mPort, this.mFile, this.mExtra); - Send.this.getProcesses().remove(this); - } - } - } - } - - public abstract static class Receive - { - private ArrayBlockingQueue mProcess = new ArrayBlockingQueue(100, true); - private int notifyDelay = CoolTransfer.DELAY_DISABLED; - - public abstract void onError(int port, File file, Exception error, T extra); - - public abstract void onNotify(Socket socket, int port, File file, int percent, T extra); - - public abstract void onTransferCompleted(int port, File file, T extra); - - public abstract void onSocketReady(ServerSocket socket, int port, File file, T extra); - - public abstract boolean onStart(int port, File file, T extra); - - public boolean onBreakRequest(int port, File file, T extra) - { - return false; - } - - public void onStop(int port, File file, T extra) - { - } - - public ArrayBlockingQueue getProcesses() - { - return this.mProcess; - } - - public ReceiveHandler receive(int port, File file, long fileSize, byte[] bufferSize, int timeOut, T extra) - { - ReceiveHandler handler = new ReceiveHandler(port, file, fileSize, bufferSize, timeOut, extra); - Thread thread = new Thread(handler); - - thread.start(); - - return handler; - } - - public ReceiveHandler receiveOnCurrentThread(int port, File file, long fileSize, byte[] bufferSize, int timeOut, T extra) - { - ReceiveHandler handler = new ReceiveHandler(port, file, fileSize, bufferSize, timeOut, extra); - - handler.onRun(); - handler.transferCompleted(); - - return handler; - } - - public void setNotifyDelay(int delay) - { - this.notifyDelay = delay; - } - - public class ReceiveHandler extends TransferHandler - { - protected int mPort; - protected File mFile; - protected long mFileSize; - protected byte[] mBufferSize; - protected int mTimeout; - protected T mExtra; - - public ReceiveHandler(int port, File file, long fileSize, byte[] bufferSize, int timeout, T extra) - { - this.mPort = port; - this.mFile = file; - this.mFileSize = fileSize; - this.mBufferSize = bufferSize; - this.mTimeout = timeout; - this.mExtra = extra; - } - - @Override - public boolean isBreakRequested() - { - return super.isBreakRequested() || Receive.this.onBreakRequest(this.mPort, this.mFile, this.mExtra); - } - - @Override - protected void onRun() - { - Receive.this.getProcesses().offer(this); - - if (!Receive.this.onStart(this.mPort, this.mFile, this.mExtra)) - return; - - try - { - ServerSocket serverSocket = new ServerSocket(this.mPort); - - Receive.this.onSocketReady(serverSocket, this.mPort, this.mFile, this.mExtra); - - Socket socket = serverSocket.accept(); - InputStream inputStream = socket.getInputStream(); - FileOutputStream outputStream = new FileOutputStream(this.mFile); - - int len; - int progressPercent = -1; - long lastRead = System.currentTimeMillis(); - long lastNotified = System.currentTimeMillis(); - - while (this.mFile.length() != this.mFileSize) - { - if ((len = inputStream.read(this.mBufferSize)) > 0) - { - outputStream.write(this.mBufferSize, 0, len); - outputStream.flush(); - - lastRead = System.currentTimeMillis(); - } - - if (Receive.this.notifyDelay == -1 || (System.currentTimeMillis() - lastNotified) > Receive.this.notifyDelay) - { - int currentPercent = (int) (((float) 100 / this.mFileSize) * outputStream.getChannel().position()); - - if (currentPercent > progressPercent) - { - Receive.this.onNotify(socket, this.mPort, this.mFile, currentPercent, this.mExtra); - progressPercent = currentPercent; - } - - lastNotified = System.currentTimeMillis(); - } - - if ((this.mTimeout > 0 && (System.currentTimeMillis() - lastRead) > this.mTimeout) || this.isBreakRequested()) - break; - } - - outputStream.close(); - inputStream.close(); - socket.close(); - serverSocket.close(); - - if (this.mFile.length() != this.mFileSize && !this.isBreakRequested()) - Receive.this.onError(this.mPort, this.mFile, new NotYetBoundException(), this.mExtra); - - Receive.this.onTransferCompleted(this.mPort, this.mFile, this.mExtra); - } catch (Exception e) - { - Receive.this.onError(this.mPort, this.mFile, e, this.mExtra); - } finally - { - Receive.this.onStop(this.mPort, this.mFile, this.mExtra); - Receive.this.getProcesses().remove(this); - } - } - } - } - - public abstract static class TransferHandler implements Runnable - { - private boolean mIsBreakRequested = false; - private boolean mIsCompleted = false; - - protected abstract void onRun(); - - public boolean isBreakRequested() - { - return this.mIsBreakRequested; - } - - public boolean isCompleted() - { - return this.mIsCompleted; - } - - public void requestBreak() - { - this.mIsBreakRequested = true; - } - - @Override - public void run() - { - this.onRun(); - this.transferCompleted(); - } - - public void transferCompleted() - { - this.mIsCompleted = true; - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/GApplication.java b/app/src/main/java/com/genonbeta/TrebleShot/GApplication.java deleted file mode 100644 index d588a9eef..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/GApplication.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.genonbeta.TrebleShot; - -import android.app.Application; - -public class GApplication extends Application -{ -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/activity/PreferencesActivity.java b/app/src/main/java/com/genonbeta/TrebleShot/activity/PreferencesActivity.java deleted file mode 100644 index 288b40384..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/activity/PreferencesActivity.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.genonbeta.TrebleShot.activity; - -import android.os.Bundle; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.app.GActivity; - -public class PreferencesActivity extends GActivity -{ - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_preferences); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/activity/ShareActivity.java b/app/src/main/java/com/genonbeta/TrebleShot/activity/ShareActivity.java deleted file mode 100644 index 0ce4982ec..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/activity/ShareActivity.java +++ /dev/null @@ -1,332 +0,0 @@ -package com.genonbeta.TrebleShot.activity; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Looper; -import android.util.Log; -import android.view.View; -import android.widget.AdapterView; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.TextView; -import android.widget.Toast; - -import com.genonbeta.CoolSocket.CoolCommunication; -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.app.GActivity; -import com.genonbeta.TrebleShot.config.AppConfig; -import com.genonbeta.TrebleShot.fragment.NetworkDeviceListFragment; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; -import com.genonbeta.TrebleShot.helper.AwaitedFileSender; -import com.genonbeta.TrebleShot.helper.FileUtils; -import com.genonbeta.TrebleShot.helper.JsonResponseHandler; -import com.genonbeta.TrebleShot.helper.NetworkDevice; -import com.genonbeta.TrebleShot.service.CommunicationService; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.w3c.dom.Text; - -import java.io.File; -import java.net.Socket; -import java.util.ArrayList; - -public class ShareActivity extends GActivity -{ - public static final String TAG = "ShareActivity"; - - public static final int REQUEST_CODE_EDIT_BOX = 1; - - public static final String ACTION_SEND = "genonbeta.intent.action.TREBLESHOT_SEND"; - public static final String ACTION_SEND_TEXT = "genonbeta.intent.action.TREBLESHOT_SEND_TEXT"; - public static final String ACTION_SEND_MULTIPLE = "genonbeta.intent.action.TREBLESHOT_SEND_MULTIPLE"; - - public enum ResultType - { - NOT_CONTINUE, - SINGLE_FILE_SHARE, - TEXT_SHARE, - MULTI_FILE_SHARE - } - - private EditText mStatusText; - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - ResultType resultType = ResultType.NOT_CONTINUE; - String info = ""; - - if (getIntent() != null) - if (ACTION_SEND_TEXT.equals(getIntent().getAction())) - { - resultType = ResultType.TEXT_SHARE; - } - else if (getIntent().hasExtra(Intent.EXTRA_STREAM) || getIntent().hasExtra(Intent.EXTRA_TEXT)) - { - if (Intent.ACTION_SEND.equals(getIntent().getAction()) || ShareActivity.ACTION_SEND.equals(getIntent().getAction())) - { - if (getIntent().hasExtra(Intent.EXTRA_STREAM)) - { - Uri fileUri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM); - File file = ApplicationHelper.getFileFromUri(this, fileUri); - - if (file != null) - { - info = file.getName(); - - resultType = ResultType.SINGLE_FILE_SHARE; - } - } - else if (getIntent().hasExtra(Intent.EXTRA_TEXT)) - { - info = getIntent().getStringExtra(Intent.EXTRA_TEXT); - resultType = ResultType.TEXT_SHARE; - } - } - else if (Intent.ACTION_SEND_MULTIPLE.equals(getIntent().getAction()) || ShareActivity.ACTION_SEND_MULTIPLE.equals(getIntent().getAction())) - { - ArrayList fileUris = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM); - info = getString(R.string.item_selected, String.valueOf(fileUris.size())); - - resultType = ResultType.MULTI_FILE_SHARE; - } - } - - if (resultType == ResultType.NOT_CONTINUE) - { - Toast.makeText(this, R.string.type_not_supported_msg, Toast.LENGTH_SHORT).show(); - finish(); - } - else - { - setContentView(R.layout.activity_share); - - final NetworkDeviceListFragment deviceListFragment = (NetworkDeviceListFragment) getSupportFragmentManager().findFragmentById(R.id.activity_share_fragment); - - mStatusText = (EditText) findViewById(R.id.activity_share_info_text); - final ImageButton editButton = (ImageButton) findViewById(R.id.activity_share_edit_button); - - mStatusText.getText().append(info); - - if (resultType == ResultType.TEXT_SHARE) - editButton.setVisibility(View.VISIBLE); - - final ResultType finalResultType = resultType; - - editButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - startActivityForResult(new Intent(ShareActivity.this, TextEditorActivity.class) - .setAction(TextEditorActivity.ACTION_EDIT_TEXT) - .putExtra(TextEditorActivity.EXTRA_TEXT_INDEX, mStatusText.getText().toString()), REQUEST_CODE_EDIT_BOX); - } - }); - - deviceListFragment.setOnListClickListener(new AdapterView.OnItemClickListener() - { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) - { - final NetworkDevice device = (NetworkDevice) deviceListFragment.getListAdapter().getItem(position); - final String deviceIp = device.ip; - - if (finalResultType == ResultType.SINGLE_FILE_SHARE) - { - Uri fileUri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM); - final File file = ApplicationHelper.getFileFromUri(getApplicationContext(), fileUri); - - if (file != null && file.isFile()) - { - final int requestId = ApplicationHelper.getUniqueNumber(); - final String fileMime = (getIntent().getType() == null) ? FileUtils.getFileContentType(file.getAbsolutePath()) : getIntent().getType(); - - CoolCommunication.Messenger.send(deviceIp, AppConfig.COMMUNATION_SERVER_PORT, null, - new JsonResponseHandler() - { - @Override - public void onJsonMessage(Socket socket, com.genonbeta.CoolSocket.CoolCommunication.Messenger.Process process, JSONObject json) - { - try - { - json.put("request", "file_transfer_request"); - json.put("fileName", file.getName()); - json.put("fileSize", file.length()); - json.put("fileMime", fileMime); - json.put("requestId", requestId); - - JSONObject response = new JSONObject(process.waitForResponse()); - - if (response.getBoolean("result")) - { - ApplicationHelper.getSenders().put(requestId, new AwaitedFileSender(deviceIp, file, requestId)); - } - else - showToast(getString(R.string.file_sending_error_msg, getString(R.string.not_allowed_error))); - } catch (JSONException e) - { - showToast(getString(R.string.file_sending_error_msg, getString(R.string.communication_problem))); - } - } - - @Override - public void onError(Exception e) - { - showToast(getString(R.string.file_sending_error_msg, getString(R.string.connection_problem))); - } - } - ); - } - } - else if (finalResultType == ResultType.MULTI_FILE_SHARE) - { - final ArrayList uris = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM); - - CoolCommunication.Messenger.send(deviceIp, AppConfig.COMMUNATION_SERVER_PORT, null, - new JsonResponseHandler() - { - @Override - public void onConfigure(CoolCommunication.Messenger.Process process) - { - process.setSocketTimeout(AppConfig.DEFAULT_SOCKET_LARGE_TIMEOUT); - } - - @Override - public void onJsonMessage(Socket socket, com.genonbeta.CoolSocket.CoolCommunication.Messenger.Process process, JSONObject json) - { - JSONArray filesArray = new JSONArray(); - - try - { - json.put("request", "multifile_transfer_request"); - - for (Uri fileUri : uris) - { - File file = ApplicationHelper.getFileFromUri(getApplicationContext(), fileUri); - - if (file == null) - continue; - - if (file.isFile()) - { - int requestId = ApplicationHelper.getUniqueNumber(); - AwaitedFileSender sender = new AwaitedFileSender(deviceIp, file, requestId); - JSONObject thisJson = new JSONObject(); - - try - { - String fileMime = FileUtils.getFileContentType(file.getAbsolutePath()); - - thisJson.put("fileName", file.getName()); - thisJson.put("fileSize", file.length()); - thisJson.put("requestId", requestId); - thisJson.put("fileMime", fileMime); - - filesArray.put(thisJson); - - ApplicationHelper.getSenders().put(sender.requestId, sender); - } catch (Exception e) - { - Log.e(TAG, "Sender error on file: " + e.getClass().getName() + " : " + file.getName()); - } - } - } - - json.put("filesJson", filesArray); - - JSONObject response = new JSONObject(process.waitForResponse()); - - if (!response.getBoolean("result")) - { - Log.d(TAG, "Server did not accept the request remove pre-added senders"); - - for (int i = 0; i < filesArray.length(); i++) - { - int requestId = filesArray.getJSONObject(i).getInt("requestId"); - ApplicationHelper.getSenders().remove(requestId); - } - - showToast(getString(R.string.file_sending_error_msg, getString(R.string.not_allowed_error))); - } - } catch (JSONException e) - { - showToast(getString(R.string.file_sending_error_msg, getString(R.string.communication_problem))); - } - } - - @Override - public void onError(Exception e) - { - showToast(getString(R.string.file_sending_error_msg, getString(R.string.connection_problem))); - } - } - ); - } - else - { - CoolCommunication.Messenger.send(deviceIp, AppConfig.COMMUNATION_SERVER_PORT, null, - new JsonResponseHandler() - { - @Override - public void onJsonMessage(Socket socket, CoolCommunication.Messenger.Process process, JSONObject json) - { - try - { - json.put("request", "request_clipboard"); - json.put("clipboardText", mStatusText.getText().toString()); - - JSONObject response = new JSONObject(process.waitForResponse()); - - if (response.getBoolean("result")) - { - - } - else - showToast(getString(R.string.file_sending_error_msg, getString(R.string.not_allowed_error))); - } catch (JSONException e) - { - showToast(getString(R.string.file_sending_error_msg, getString(R.string.communication_problem))); - } - } - - @Override - public void onError(Exception e) - { - e.printStackTrace(); - showToast(getString(R.string.file_sending_error_msg, getString(R.string.connection_problem))); - } - } - ); - } - } - }); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - super.onActivityResult(requestCode, resultCode, data); - - if (resultCode == RESULT_OK) - if (requestCode == REQUEST_CODE_EDIT_BOX && data != null && data.hasExtra(TextEditorActivity.EXTRA_TEXT_INDEX)) - { - mStatusText.setText(data.getStringExtra(TextEditorActivity.EXTRA_TEXT_INDEX)); - } - - } - - protected void showToast(String msg) - { - Looper.prepare(); - Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); - Looper.loop(); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/activity/TextEditorActivity.java b/app/src/main/java/com/genonbeta/TrebleShot/activity/TextEditorActivity.java deleted file mode 100644 index 384fc7a23..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/activity/TextEditorActivity.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.genonbeta.TrebleShot.activity; - -import android.content.Intent; -import android.os.Bundle; -import android.os.PersistableBundle; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.EditText; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.app.GActivity; - -/** - * Created by: veli - * Date: 1/19/17 5:05 PM - */ - -public class TextEditorActivity extends GActivity -{ - public static final String ACTION_EDIT_TEXT = "genonbeta.intent.action.EDIT_TEXT"; - public static final String EXTRA_TEXT_INDEX = "extraText"; - - private EditText mEditTextEditor; - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - if (getIntent() == null || !ACTION_EDIT_TEXT.equals(getIntent().getAction()) || !getIntent().hasExtra(EXTRA_TEXT_INDEX)) - finish(); - else - { - setContentView(R.layout.layout_text_editor_activity); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - setContentView(R.layout.layout_text_editor_activity); - - mEditTextEditor = (EditText) findViewById(R.id.layout_text_editor_activity_text_text_box); - mEditTextEditor.getText().append(getIntent().getStringExtra(EXTRA_TEXT_INDEX)); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - getMenuInflater().inflate(R.menu.action_text_editor, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - int id = item.getItemId(); - - if (id == R.id.menu_action_done) - { - Intent intent = new Intent(); - intent.putExtra(EXTRA_TEXT_INDEX, mEditTextEditor.getText().toString()); - - setResult(RESULT_OK, intent); - finish(); - - return true; - } - else if (id == android.R.id.home) - { - setResult(RESULT_CANCELED); - finish(); - - return true; - } - - return super.onOptionsItemSelected(item); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/activity/TrebleShotActivity.java b/app/src/main/java/com/genonbeta/TrebleShot/activity/TrebleShotActivity.java deleted file mode 100644 index de2351c7e..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/activity/TrebleShotActivity.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.genonbeta.TrebleShot.activity; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.design.widget.NavigationView; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.view.GravityCompat; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.widget.Toolbar; -import android.view.MenuItem; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.app.GActivity; -import com.genonbeta.TrebleShot.config.AppConfig; -import com.genonbeta.TrebleShot.fragment.ApplicationListFragment; -import com.genonbeta.TrebleShot.fragment.MusicListFragment; -import com.genonbeta.TrebleShot.fragment.NetworkDeviceListFragment; -import com.genonbeta.TrebleShot.fragment.ReceivedFilesListFragment; -import com.genonbeta.TrebleShot.fragment.VideoListFragment; -import com.genonbeta.TrebleShot.fragment.dialog.AboutDialog; -import com.genonbeta.TrebleShot.helper.FileUtils; -import com.genonbeta.TrebleShot.support.FragmentTitle; - -import java.io.File; - -import velitasali.updatewithgithub.GithubUpdater; - -public class TrebleShotActivity extends GActivity implements NavigationView.OnNavigationItemSelectedListener -{ - public static final String OPEN_RECEIVED_FILES_ACTION = "genonbeta.intent.action.OPEN_RECEIVED_FILES"; - - Fragment mFragmentDeviceList; - Fragment mFragmentReceivedFiles; - Fragment mFragmentShareApplication; - Fragment mFragmentShareMusic; - Fragment mFragmentShareVideo; - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); - ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); - drawer.addDrawerListener(toggle); - toggle.syncState(); - - NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); - navigationView.setNavigationItemSelectedListener(this); - - mFragmentDeviceList = Fragment.instantiate(this, NetworkDeviceListFragment.class.getName()); - mFragmentReceivedFiles = Fragment.instantiate(this, ReceivedFilesListFragment.class.getName()); - mFragmentShareApplication = Fragment.instantiate(this, ApplicationListFragment.class.getName()); - mFragmentShareMusic = Fragment.instantiate(this, MusicListFragment.class.getName()); - mFragmentShareVideo = Fragment.instantiate(this, VideoListFragment.class.getName()); - - changeFragment(mFragmentDeviceList); - } - - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) - { - if (R.id.menu_activity_main_device_list == item.getItemId()) - { - changeFragment(mFragmentDeviceList); - } - else if (R.id.menu_activity_main_received_files == item.getItemId()) - { - changeFragment(mFragmentReceivedFiles); - } - else if (R.id.menu_activity_main_share_app == item.getItemId()) - { - changeFragment(mFragmentShareApplication); - } - else if (R.id.menu_activity_main_share_music == item.getItemId()) - { - changeFragment(mFragmentShareMusic); - } - else if (R.id.menu_activity_main_share_video == item.getItemId()) - { - changeFragment(mFragmentShareVideo); - } - else if (R.id.menu_activity_main_about == item.getItemId()) - { - new AboutDialog().show(getSupportFragmentManager(), "aboutDialog"); - } - else if (R.id.menu_activity_main_send_application == item.getItemId()) - { - sendTheApplication(); - } - else if (R.id.menu_activity_main_preferences == item.getItemId()) - { - startActivity(new Intent(this, PreferencesActivity.class)); - } - else if (R.id.menu_activity_main_share_clipboard_text == item.getItemId()) - { - startActivity(new Intent(this, ShareActivity.class).setAction(ShareActivity.ACTION_SEND_TEXT)); - } - else if (R.id.menu_activity_main_check_for_updates == item.getItemId()) - { - GithubUpdater updater = new GithubUpdater(this, AppConfig.APP_UPDATE_REPO, R.style.AppTheme); - updater.checkForUpdates(); - } - else - return false; - - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); - drawer.closeDrawer(GravityCompat.START); - - return true; - } - - - @Override - public void onBackPressed() - { - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); - - if (drawer.isDrawerOpen(GravityCompat.START)) - drawer.closeDrawer(GravityCompat.START); - else - super.onBackPressed(); - } - - private void sendTheApplication() - { - File apkFile = new File(getPackageCodePath()); - - Intent sendIntent = new Intent(Intent.ACTION_SEND); - - sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(apkFile)); - sendIntent.setType(FileUtils.getFileContentType(apkFile.getAbsolutePath())); - - startActivity(Intent.createChooser(sendIntent, getString(R.string.file_share_app_chooser_msg))); - } - - public void changeFragment(Fragment fragment) - { - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - - ft.replace(R.id.content_frame, fragment); - ft.commit(); - - if (fragment instanceof FragmentTitle) - setTitle(((FragmentTitle) fragment).getFragmentTitle(this)); - else - setTitle(R.string.app_name); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/adapter/AbstractEditableListAdapter.java b/app/src/main/java/com/genonbeta/TrebleShot/adapter/AbstractEditableListAdapter.java deleted file mode 100644 index f260f35df..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/adapter/AbstractEditableListAdapter.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.genonbeta.TrebleShot.adapter; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -import com.genonbeta.TrebleShot.R; - -abstract public class AbstractEditableListAdapter extends BaseAdapter -{ - protected boolean mLockRequested = false; - protected Context mContext; - protected LayoutInflater mInflater; - - protected abstract void onSearch(String word); - protected abstract void onUpdate(); - - public AbstractEditableListAdapter(Context context) - { - this.mContext = context; - this.mInflater = LayoutInflater.from(context); - } - - public Context getContext() - { - return this.mContext; - } - public LayoutInflater getInflater() { return this.mInflater; } - - public boolean search(String word) - { - this.onSearch(word); - return true; - } - - public boolean update() - { - if (mLockRequested) - return false; - - mLockRequested = true; - - this.onUpdate(); - - mLockRequested = false; - - return true; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/adapter/ApplicationListAdapter.java b/app/src/main/java/com/genonbeta/TrebleShot/adapter/ApplicationListAdapter.java deleted file mode 100644 index a61b72401..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/adapter/ApplicationListAdapter.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.genonbeta.TrebleShot.adapter; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -public class ApplicationListAdapter extends AbstractEditableListAdapter -{ - private boolean mShowSysApps = false; - private String mSearchWord; - private PackageManager mManager; - private ArrayList mList = new ArrayList(); - private ArrayList mPendingList = new ArrayList(); - private Comparator mComparator = new Comparator() - { - @Override - public int compare(ApplicationListAdapter.AppInfo compareFrom, ApplicationListAdapter.AppInfo compareTo) - { - return compareFrom.label.toLowerCase().compareTo(compareTo.label.toLowerCase()); - } - }; - - public ApplicationListAdapter(Context context, boolean showSystemApps) - { - super(context); - this.mShowSysApps = showSystemApps; - this.mManager = this.mContext.getPackageManager(); - } - - @Override - protected void onUpdate() - { - this.mPendingList.clear(); - - for (PackageInfo packageInfo : this.mContext.getPackageManager().getInstalledPackages(PackageManager.GET_META_DATA)) - { - ApplicationInfo appInfo = packageInfo.applicationInfo; - - if (((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 1) || this.mShowSysApps) - { - String label = (String) appInfo.loadLabel(this.mManager); - - if (this.mSearchWord == null || (this.mSearchWord != null && ApplicationHelper.searchWord(label, this.mSearchWord))) - this.mPendingList.add(new AppInfo(appInfo.loadLogo(this.mManager), label, packageInfo.versionName, appInfo.sourceDir, packageInfo.packageName)); - } - } - - Collections.sort(this.mPendingList, this.mComparator); - } - - @Override - protected void onSearch(String word) - { - this.mSearchWord = word; - } - - @Override - public int getCount() - { - return this.mList.size(); - } - - @Override - public Object getItem(int position) - { - return this.mList.get(position); - } - - @Override - public long getItemId(int p1) - { - return 0; - } - - @Override - public View getView(int position, View view, ViewGroup viewGroup) - { - return this.getViewAt(view == null ? LayoutInflater.from(getContext()).inflate(R.layout.list_application, viewGroup, false) : view, position); - } - - public View getViewAt(View view, int position) - { - AppInfo info = (AppInfo) this.getItem(position); - TextView text = (TextView) view.findViewById(R.id.text); - TextView text2 = (TextView) view.findViewById(R.id.text2); - - text.setText(info.label); - text2.setText(info.version); - - return view; - } - - @Override - public void notifyDataSetChanged() - { - if (mPendingList.size() > 0) - { - this.mList.clear(); - this.mList.addAll(this.mPendingList); - - this.mPendingList.clear(); - } - - super.notifyDataSetChanged(); - } - - public void showSystemApps(boolean show) - { - this.mShowSysApps = show; - } - - public static class AppInfo - { - public Drawable icon; - public String label; - public String version; - public String codePath; - public String packageName; - - public AppInfo(Drawable icon, String label, String version, String codePath, String packageName) - { - this.icon = icon; - this.label = label; - this.version = version; - this.codePath = codePath; - this.packageName = packageName; - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/adapter/MusicListAdapter.java b/app/src/main/java/com/genonbeta/TrebleShot/adapter/MusicListAdapter.java deleted file mode 100644 index 6fda22b8d..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/adapter/MusicListAdapter.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.genonbeta.TrebleShot.adapter; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.MediaStore; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -public class MusicListAdapter extends AbstractEditableListAdapter -{ - private ContentResolver mResolver; - private String mSearchWord; - private ArrayList mList = new ArrayList(); - private ArrayList mPendingList = new ArrayList(); - private Comparator mComparator = new Comparator() - { - @Override - public int compare(MusicListAdapter.MusicInfo compareFrom, MusicListAdapter.MusicInfo compareTo) - { - return compareFrom.song.toLowerCase().compareTo(compareTo.song.toLowerCase()); - } - }; - - public MusicListAdapter(Context context) - { - super(context); - this.mResolver = context.getContentResolver(); - } - - protected void onUpdate() - { - this.mPendingList.clear(); - - Cursor cursor = this.mResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, null); - - if (cursor.moveToFirst()) - { - int idIndex = cursor.getColumnIndex(MediaStore.Audio.Media._ID); - int artistIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST); - int songIndex = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE); - - do - { - MusicInfo info = new MusicInfo(cursor.getString(artistIndex), cursor.getString(songIndex), Uri.parse(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + cursor.getInt(idIndex))); - - if (this.mSearchWord == null || (this.mSearchWord != null && (ApplicationHelper.searchWord(info.artist, this.mSearchWord) || ApplicationHelper.searchWord(info.song, this.mSearchWord)))) - this.mPendingList.add(info); - } - while (cursor.moveToNext()); - - Collections.sort(this.mPendingList, this.mComparator); - } - - cursor.close(); - } - - @Override - protected void onSearch(String word) - { - this.mSearchWord = word; - } - - @Override - public int getCount() - { - return this.mList.size(); - } - - @Override - public Object getItem(int position) - { - return this.mList.get(position); - } - - @Override - public long getItemId(int p1) - { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) - { - return getViewAt(convertView == null ? LayoutInflater.from(getContext()).inflate(R.layout.list_music, parent, false) : convertView, position); - } - - public View getViewAt(View view, int position) - { - MusicInfo info = (MusicInfo) this.getItem(position); - TextView text = (TextView) view.findViewById(R.id.text); - TextView text2 = (TextView) view.findViewById(R.id.text2); - - text.setText(info.song); - text2.setText(info.artist); - - return view; - } - - @Override - public void notifyDataSetChanged() - { - if (mPendingList.size() > 0) - { - this.mList.clear(); - this.mList.addAll(this.mPendingList); - - this.mPendingList.clear(); - } - - super.notifyDataSetChanged(); - } - - - public static class MusicInfo - { - public String artist; - public String song; - public Uri uri; - - public MusicInfo(String artist, String song, Uri uri) - { - this.artist = artist; - this.song = song; - this.uri = uri; - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/adapter/NetworkDeviceListAdapter.java b/app/src/main/java/com/genonbeta/TrebleShot/adapter/NetworkDeviceListAdapter.java deleted file mode 100644 index acdf58b88..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/adapter/NetworkDeviceListAdapter.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.genonbeta.TrebleShot.adapter; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.AnimationUtils; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; -import com.genonbeta.TrebleShot.helper.GAnimater; -import com.genonbeta.TrebleShot.helper.NetworkDevice; - -import java.util.ArrayList; - -public class NetworkDeviceListAdapter extends BaseAdapter -{ - private Context mContext; - private ArrayList mDeviceList = new ArrayList(); - - public NetworkDeviceListAdapter(Context context) - { - this.mContext = context; - } - - @Override - public void notifyDataSetChanged() - { - mDeviceList.clear(); - - for (NetworkDevice device : ApplicationHelper.getDeviceList().values()) - { - if (device.user != null && device.model != null && device.brand != null) - mDeviceList.add(device); - } - - super.notifyDataSetChanged(); - } - - @Override - public int getCount() - { - return mDeviceList.size(); - } - - @Override - public Object getItem(int itemId) - { - return mDeviceList.get(itemId); - } - - @Override - public long getItemId(int p1) - { - return 0; - } - - @Override - public View getView(int position, View view, ViewGroup viewGroup) - { - if (view == null) - { - view = LayoutInflater.from(mContext).inflate(R.layout.list_network_device, viewGroup, false); - AnimationSet set = GAnimater.getAnimation(GAnimater.APPEAR); - view.setAnimation(set); - } - - return getViewAt(view, position); - } - - public View getViewAt(View view, int position) - { - TextView deviceText = (TextView) view.findViewById(R.id.network_device_list_device_text); - TextView ipText = (TextView) view.findViewById(R.id.network_device_list_device_ip_text); - TextView userText = (TextView) view.findViewById(R.id.network_device_list_device_user_text); - - NetworkDevice device = (NetworkDevice) getItem(position); - - deviceText.setText(device.model); - userText.setText(device.user); - ipText.setText(device.ip); - - return view; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/adapter/P2pDeviceListAdapter.java b/app/src/main/java/com/genonbeta/TrebleShot/adapter/P2pDeviceListAdapter.java deleted file mode 100644 index 7dfd07368..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/adapter/P2pDeviceListAdapter.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.genonbeta.TrebleShot.adapter; - -import android.content.Context; -import android.net.wifi.p2p.WifiP2pDevice; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.genonbeta.TrebleShot.R; - -import java.util.ArrayList; - -public class P2pDeviceListAdapter extends BaseAdapter -{ - private Context mContext; - private ArrayList mPeers; - private int[] mDeviceStatus = new int[]{R.string.connected, R.string.invited, R.string.connection_failured, R.string.avaiable, R.string.not_avaiable}; - - public P2pDeviceListAdapter(Context context, ArrayList peerList) - { - this.mContext = context; - this.mPeers = peerList; - } - - @Override - public int getCount() - { - return mPeers.size(); - } - - @Override - public Object getItem(int itemId) - { - return mPeers.get(itemId); - } - - @Override - public long getItemId(int p1) - { - return 0; - } - - @Override - public View getView(int position, View view, ViewGroup viewGroup) - { - return getViewAt(LayoutInflater.from(mContext).inflate(R.layout.list_p2p_device, viewGroup, false), position); - } - - public View getViewAt(View view, int position) - { - TextView deviceText = (TextView) view.findViewById(R.id.p2p_device_list_device_text); - TextView deviceStatusText = (TextView) view.findViewById(R.id.p2p_device_list_device_status_text); - WifiP2pDevice device = (WifiP2pDevice) getItem(position); - - deviceText.setText(device.deviceName); - deviceStatusText.setText(mDeviceStatus[device.status]); - - return view; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/adapter/PendingProcessListAdapter.java b/app/src/main/java/com/genonbeta/TrebleShot/adapter/PendingProcessListAdapter.java deleted file mode 100644 index eb0ea3de9..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/adapter/PendingProcessListAdapter.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.genonbeta.TrebleShot.adapter; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; -import com.genonbeta.TrebleShot.helper.AwaitedFileReceiver; -import com.genonbeta.TrebleShot.helper.AwaitedFileSender; - -import java.util.ArrayList; -import java.util.HashSet; - -public class PendingProcessListAdapter extends BaseAdapter -{ - private Context mContext; - private String mIp; - private ArrayList mList = new ArrayList(); - - public PendingProcessListAdapter(Context context, String forIp) - { - this.mContext = context; - this.mIp = forIp; - - loadList(); - } - - public void loadList() - { - mList.clear(); - - for (AwaitedFileSender sender : ApplicationHelper.getSenders().values()) - { - if (this.mIp != null && !sender.ip.equals(this.mIp)) - continue; - - addToList(sender); - } - - for (AwaitedFileReceiver receiver : ApplicationHelper.getPendingReceivers()) - { - if (this.mIp != null && !receiver.ip.equals(this.mIp)) - continue; - - addToList(receiver); - } - - for (AwaitedFileReceiver receiver : ApplicationHelper.getReceivers()) - { - if (this.mIp != null && !receiver.ip.equals(this.mIp)) - continue; - - addToList(receiver); - } - } - - private void addToList(AwaitedFileReceiver r) - { - ItemHolder holder = new ItemHolder(); - - holder.file = r.fileName; - - holder.type = true; - holder.deviceIp = r.ip; - - mList.add(holder); - } - - private void addToList(AwaitedFileSender s) - { - ItemHolder holder = new ItemHolder(); - - holder.file = s.file.getName(); - - holder.type = false; - holder.deviceIp = s.ip; - - mList.add(holder); - } - - public void clearQueue() - { - HashSet keyList = new HashSet(); - - for (int key : ApplicationHelper.getSenders().keySet()) - { - AwaitedFileSender sender = ApplicationHelper.getSenders().get(key); - - if (sender.ip.equals(this.mIp)) - keyList.add(key); - } - - for (int currentNumber : keyList) - ApplicationHelper.getSenders().remove(currentNumber); - - for (AwaitedFileReceiver receiver : ApplicationHelper.getReceivers()) - { - if (receiver.ip.equals(this.mIp)) - ApplicationHelper.getReceivers().remove(receiver); - } - - for (AwaitedFileReceiver receiver : ApplicationHelper.getPendingReceivers()) - { - if (receiver.ip.equals(this.mIp)) - ApplicationHelper.getPendingReceivers().remove(receiver); - } - } - - @Override - public void notifyDataSetChanged() - { - loadList(); - super.notifyDataSetChanged(); - } - - @Override - public int getCount() - { - return mList.size(); - } - - @Override - public Object getItem(int itemId) - { - return mList.get(itemId); - } - - @Override - public long getItemId(int p1) - { - return 0; - } - - @Override - public View getView(int position, View view, ViewGroup viewGroup) - { - return getViewAt(LayoutInflater.from(mContext).inflate(R.layout.list_pending_queue, viewGroup, false), position); - } - - public View getViewAt(View view, int position) - { - TextView filenameText = (TextView) view.findViewById(R.id.pending_queue_list_filename); - TextView processTypeText = (TextView) view.findViewById(R.id.pending_queue_list_process_type_text); - - ItemHolder item = (ItemHolder) getItem(position); - - filenameText.setText(item.file); - processTypeText.setText((!item.type) ? R.string.send : R.string.receive); - - return view; - } - - private class ItemHolder - { - public String file; - public String deviceIp; - public boolean type = false; // send, receive - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/adapter/ReceivedFilesListAdapter.java b/app/src/main/java/com/genonbeta/TrebleShot/adapter/ReceivedFilesListAdapter.java deleted file mode 100644 index c7bfd46e8..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/adapter/ReceivedFilesListAdapter.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.genonbeta.TrebleShot.adapter; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; -import com.genonbeta.TrebleShot.helper.FileUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -public class ReceivedFilesListAdapter extends AbstractEditableListAdapter -{ - private String mSearchWord; - public ArrayList mList = new ArrayList(); - private Comparator mComparator = new Comparator() - { - @Override - public int compare(ReceivedFilesListAdapter.FileInfo compareFrom, ReceivedFilesListAdapter.FileInfo compareTo) - { - return compareFrom.fileName.toLowerCase().compareTo(compareTo.fileName.toLowerCase()); - } - }; - - public ReceivedFilesListAdapter(Context context) - { - super(context); - } - - @Override - protected void onSearch(String word) - { - this.mSearchWord = word; - } - - @Override - protected void onUpdate() - { - this.mList.clear(); - - for (File file : ApplicationHelper.getApplicationDirectory(mContext).listFiles()) - { - if ((this.mSearchWord == null || (this.mSearchWord != null && ApplicationHelper.searchWord(file.getName(), this.mSearchWord))) && file.isFile()) - this.mList.add(new FileInfo(file.getName(), FileUtils.sizeExpression(file.length(), false), file)); - } - - Collections.sort(mList, this.mComparator); - } - - @Override - public int getCount() - { - return this.mList.size(); - } - - @Override - public Object getItem(int itemId) - { - return this.mList.get(itemId); - } - - @Override - public long getItemId(int p1) - { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) - { - return getViewAt(convertView == null ? LayoutInflater.from(getContext()).inflate(R.layout.list_received_file, parent, false) : convertView, position); - } - - public View getViewAt(View view, int position) - { - TextView fileNameText = (TextView) view.findViewById(R.id.text); - TextView sizeText = (TextView) view.findViewById(R.id.text2); - FileInfo fileInfo = (FileInfo) getItem(position); - - fileNameText.setText(fileInfo.fileName); - sizeText.setText(fileInfo.fileSize); - - return view; - } - - public static class FileInfo - { - public String fileName; - public String fileSize; - public File file; - - public FileInfo(String name, String size, File file) - { - this.fileName = name; - this.fileSize = size; - this.file = file; - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/adapter/SimpleFragmentPagerAdapter.java b/app/src/main/java/com/genonbeta/TrebleShot/adapter/SimpleFragmentPagerAdapter.java deleted file mode 100644 index 4634c227d..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/adapter/SimpleFragmentPagerAdapter.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.genonbeta.TrebleShot.adapter; - -import android.content.Context; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; - -public class SimpleFragmentPagerAdapter extends FragmentPagerAdapter -{ - private Context mContext; - private Fragment[] mFragments; - private String[] mTitles; - - public SimpleFragmentPagerAdapter(FragmentManager fm, Context context, Fragment[] fragments, String[] titles) - { - super(fm); - - this.mContext = context; - this.mFragments = fragments; - this.mTitles = titles; - } - - @Override - public int getCount() - { - return this.mFragments.length; - } - - @Override - public Fragment getItem(int id) - { - return this.mFragments[id]; - } - - @Override - public CharSequence getPageTitle(int position) - { - return this.mTitles[position]; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/adapter/VideoListAdapter.java b/app/src/main/java/com/genonbeta/TrebleShot/adapter/VideoListAdapter.java deleted file mode 100644 index e149192a8..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/adapter/VideoListAdapter.java +++ /dev/null @@ -1,265 +0,0 @@ -package com.genonbeta.TrebleShot.adapter; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.AsyncTask; -import android.provider.MediaStore; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -public class VideoListAdapter extends AbstractEditableListAdapter -{ - private ContentResolver mResolver; - private String mSearchWord; - private Bitmap mPlaceHolderBitmap; - private ArrayList mList = new ArrayList(); - private ArrayList mPendingList = new ArrayList(); - private Comparator mComparator = new Comparator() - { - @Override - public int compare(VideoInfo compareFrom, VideoInfo compareTo) - { - return compareFrom.title.toLowerCase().compareTo(compareTo.title.toLowerCase()); - } - }; - - public VideoListAdapter(Context context) - { - super(context); - this.mResolver = context.getContentResolver(); - this.mPlaceHolderBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_autorenew_white); - } - - protected void onUpdate() - { - this.mPendingList.clear(); - - Cursor cursor = this.mResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null, null, null); - - if (cursor.moveToFirst()) - { - int idIndex = cursor.getColumnIndex(MediaStore.Video.Media._ID); - int titleIndex = cursor.getColumnIndex(MediaStore.Video.Media.TITLE); - - do - { - VideoInfo info = new VideoInfo(cursor.getInt(idIndex), cursor.getString(titleIndex), null, Uri.parse(MediaStore.Video.Media.EXTERNAL_CONTENT_URI + "/" + cursor.getInt(idIndex))); - - if (this.mSearchWord == null || (this.mSearchWord != null && ApplicationHelper.searchWord(info.title, this.mSearchWord))) - this.mPendingList.add(info); - } - while (cursor.moveToNext()); - - Collections.sort(this.mPendingList, this.mComparator); - } - - cursor.close(); - } - - @Override - protected void onSearch(String word) - { - this.mSearchWord = word; - } - - @Override - public int getCount() - { - return this.mList.size(); - } - - @Override - public Object getItem(int position) - { - return this.mList.get(position); - } - - @Override - public long getItemId(int p1) - { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) - { - final VideoInfo info = (VideoInfo) this.getItem(position); - final ViewHolder holder; - - if (convertView == null) - { - convertView = getInflater().inflate(R.layout.list_video, null); - holder = new ViewHolder(); - holder.titleView = (TextView) convertView.findViewById(R.id.text); - holder.imageView = (ImageView) convertView.findViewById(R.id.image); - convertView.setTag(holder); - } - else - holder = (ViewHolder) convertView.getTag(); - - holder.titleView.setText(info.title); - - if (holder.imageView != null) - loadBitmap(info.id, holder.imageView); - - return convertView; - - } - - @Override - public void notifyDataSetChanged() - { - if (mPendingList.size() > 0) - { - this.mList.clear(); - this.mList.addAll(this.mPendingList); - - this.mPendingList.clear(); - } - - super.notifyDataSetChanged(); - } - - public static class VideoInfo - { - public long id; - public String title; - public String thumbnail; - public Uri uri; - - public VideoInfo(int id, String title, String thumbnail, Uri uri) - { - this.id = id; - this.title = title; - this.thumbnail = thumbnail; - this.uri = uri; - } - } - - private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) - { - if (imageView != null) - { - final Drawable drawable = imageView.getDrawable(); - - if (drawable instanceof AsyncDrawable) - { - final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; - return asyncDrawable.getBitmapWorkerTask(); - } - } - - return null; - } - - public static boolean cancelPotentialWork(long data, ImageView imageView) - { - final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); - - if (bitmapWorkerTask != null) - { - final long bitmapData = bitmapWorkerTask.getData(); - // If bitmapData is not yet set or it differs from the new data - if (bitmapData == 0 || bitmapData != data) - { - // Cancel previous task - bitmapWorkerTask.cancel(true); - } - else - { - // The same work is already in progress - return false; - } - } - // No task associated with the ImageView, or an existing task was cancelled - return true; - } - - public void loadBitmap(long id, ImageView imageView) - { - if (cancelPotentialWork(id, imageView)) - { - final BitmapWorkerTask task = new BitmapWorkerTask(imageView); - final AsyncDrawable asyncDrawable = new AsyncDrawable(getContext().getResources(), mPlaceHolderBitmap, task); - imageView.setImageDrawable(asyncDrawable); - task.execute(id); - } - } - - static class AsyncDrawable extends BitmapDrawable - { - private final WeakReference bitmapWorkerTaskReference; - - public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) - { - super(res, bitmap); - bitmapWorkerTaskReference = new WeakReference(bitmapWorkerTask); - } - - public BitmapWorkerTask getBitmapWorkerTask() - { - return bitmapWorkerTaskReference.get(); - } - } - - private class ViewHolder - { - TextView titleView; - ImageView imageView; - } - - private class BitmapWorkerTask extends AsyncTask - { - private final WeakReference imageViewReference; - private long mId = 0; - - public BitmapWorkerTask(ImageView imageView) - { - imageViewReference = new WeakReference(imageView); - } - - @Override - protected Bitmap doInBackground(Long... params) - { - this.mId = params[0]; - return MediaStore.Video.Thumbnails.getThumbnail(mResolver, params[0], MediaStore.Video.Thumbnails.MINI_KIND, null); - } - - public long getData() - { - return this.mId; - } - - @Override - protected void onPostExecute(Bitmap bitmap) - { - if (isCancelled()) - bitmap = null; - - if (imageViewReference != null) - { - ImageView imageView = imageViewReference.get(); - - if (imageView != null) - imageView.setImageBitmap(bitmap == null ? mPlaceHolderBitmap : bitmap); - } - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/app/GActivity.java b/app/src/main/java/com/genonbeta/TrebleShot/app/GActivity.java deleted file mode 100644 index 43856aa69..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/app/GActivity.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.genonbeta.TrebleShot.app; - -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.v7.app.AppCompatActivity; -import android.widget.Toast; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.service.CommunicationService; - -public class GActivity extends AppCompatActivity -{ - private SharedPreferences mPreferences; - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - if (this.getDefaultPreferences().getBoolean("serviceLock", false)) - { - this.getDefaultPreferences().edit().putBoolean("serviceLock", false).commit(); - Toast.makeText(this, R.string.service_unlocked_notice, Toast.LENGTH_SHORT).show(); - } - } - - @Override - protected void onStart() - { - super.onStart(); - startService(new Intent(this, CommunicationService.class)); - } - - protected SharedPreferences getDefaultPreferences() - { - if (this.mPreferences == null) - this.mPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - return this.mPreferences; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/config/AppConfig.java b/app/src/main/java/com/genonbeta/TrebleShot/config/AppConfig.java deleted file mode 100644 index 661c53a5d..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/config/AppConfig.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.genonbeta.TrebleShot.config; - -public class AppConfig -{ - public final static int COMMUNATION_SERVER_PORT = 1128; - public final static byte[] DEFAULT_BUFFER_SIZE = new byte[8096]; - public final static byte[] SMALL_BUFFER_SIZE = new byte[1024]; - public final static int DEFAULT_SOCKET_TIMEOUT = 5000; - public final static int DEFAULT_SOCKET_LARGE_TIMEOUT = 20000; - public final static String[] DEFAULT_DISABLED_INTERFACES = new String[]{"rmnet"}; - public final static String APP_UPDATE_REPO = "https://api.github.com/repos/genonbeta/TrebleShot/releases"; - public final static String APPLICATION_REPO = "http://github.com/genonbeta/TrebleShot"; -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/exception/ThreadCancelledException.java b/app/src/main/java/com/genonbeta/TrebleShot/exception/ThreadCancelledException.java deleted file mode 100644 index 46e33eb71..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/exception/ThreadCancelledException.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.genonbeta.TrebleShot.exception; - -public class ThreadCancelledException extends Exception -{ -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/fragment/AbstractEditableListFragment.java b/app/src/main/java/com/genonbeta/TrebleShot/fragment/AbstractEditableListFragment.java deleted file mode 100644 index 2e6a1bb16..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/fragment/AbstractEditableListFragment.java +++ /dev/null @@ -1,288 +0,0 @@ -package com.genonbeta.TrebleShot.fragment; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.ListFragment; -import android.support.v4.util.ArrayMap; -import android.support.v7.widget.SearchView; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.widget.AbsListView; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.activity.ShareActivity; -import com.genonbeta.TrebleShot.adapter.AbstractEditableListAdapter; -import com.genonbeta.TrebleShot.helper.GAnimater; - -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; - -public abstract class AbstractEditableListFragment extends ListFragment -{ - private T mAdapter; - private ActionMode mActionMode; - private ActionModeListener mActionModeListener; - private SearchView mSearchView; - private boolean mIsLoading = false; - - private Runnable mNotifyListChanges = new Runnable() - { - @Override - public void run() - { - getAdapter().notifyDataSetChanged(); - setEmptyText(getString(R.string.list_empty_msg)); - - if (mActionMode != null) - for (int i = 0; i < getListView().getCount(); i++) - if (getListView().isItemChecked(i)) - mActionModeListener.onItemCheckedStateChanged(mActionMode, i, 0, true); - - } - }; - - private Runnable mUpdateList = new Runnable() - { - @Override - public void run() - { - boolean updateSucceed = getAdapter().update(); - - if (updateSucceed && getActivity() != null && !isDetached()) - getActivity().runOnUiThread(mNotifyListChanges); - - mIsLoading = false; - } - }; - - private SearchView.OnQueryTextListener mSearchComposer = new SearchView.OnQueryTextListener() - { - @Override - public boolean onQueryTextSubmit(String word) - { - return false; - } - - @Override - public boolean onQueryTextChange(String word) - { - search(word); - return false; - } - }; - - - protected abstract T onAdapter(); - - protected abstract ActionModeListener onActionModeListener(); - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - this.setHasOptionsMenu(true); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) - { - super.onActivityCreated(savedInstanceState); - - this.mAdapter = this.onAdapter(); - this.mActionModeListener = this.onActionModeListener(); - - this.setListAdapter(mAdapter); - - this.getListView().setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL); - this.getListView().setMultiChoiceModeListener(this.mActionModeListener); - - GAnimater.applyLayoutAnimation(getListView(), GAnimater.APPEAR); - } - - @Override - public void onResume() - { - super.onResume(); - this.updateInBackground(); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) - { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.search_menu, menu); - - mSearchView = (SearchView) menu.findItem(R.id.search).getActionView(); - mSearchView.setOnQueryTextListener(mSearchComposer); - } - - @Override - public void onDetach() - { - super.onDetach(); - - if (mActionMode != null) - { - mActionMode.finish(); - mActionMode = null; - } - } - - protected T getAdapter() - { - return this.mAdapter; - } - public boolean isLoading() - { - return mIsLoading; - } - - public void openFile(Uri uri, String type, String chooserText) - { - Intent openIntent = new Intent(Intent.ACTION_VIEW); - - openIntent.setDataAndType(uri, type); - - this.startActivity(Intent.createChooser(openIntent, chooserText)); - } - - public void search(String word) - { - if (word.equals("")) - word = null; - else - word = word.toLowerCase(); - - getAdapter().search(word); - updateInBackground(); - } - - public void setItemsChecked(boolean check) - { - for (int i = 0; i < getListView().getCount(); i++) - getListView().setItemChecked(i, check); - } - - public boolean updateInBackground() - { - if (getActivity() == null || isLoading()) - return false; - - this.mIsLoading = true; - - setEmptyText(getString(R.string.loading)); - - new Thread(this.mUpdateList).start(); - - return true; - } - - protected abstract class ActionModeListener implements AbsListView.MultiChoiceModeListener - { - private ArrayList mCheckedList = new ArrayList<>(); - private MenuItem mSelectAll; - - public abstract Uri onItemChecked(ActionMode mode, int position, long id, boolean isChecked); - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) - { - mode.getMenuInflater().inflate(R.menu.share_actions, menu); - - mSelectAll = menu.findItem(R.id.file_actions_select); - mActionMode = mode; - - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) - { - mCheckedList.clear(); - - return true; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) - { - if (item.getItemId() == R.id.file_actions_share || item.getItemId() == R.id.file_actions_share_trebleshot) - { - Intent shareIntent = null; - String action = (item.getItemId() == R.id.file_actions_share) ? (mCheckedList.size() > 1 ? Intent.ACTION_SEND_MULTIPLE : Intent.ACTION_SEND) : (mCheckedList.size() > 1 ? ShareActivity.ACTION_SEND_MULTIPLE : ShareActivity.ACTION_SEND); - - if (mCheckedList.size() > 1) - { - shareIntent = new Intent(action); - - shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, mCheckedList); - shareIntent.setType("*/*"); - } - else if (mCheckedList.size() == 1) - { - Uri fileUri = (Uri) mCheckedList.toArray()[0]; - - shareIntent = new Intent(action); - - shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri); - shareIntent.setType("*/*"); - } - - if (shareIntent != null) - { - startActivity((item.getItemId() == R.id.file_actions_share) ? Intent.createChooser(shareIntent, getString(R.string.file_share_app_chooser_msg)) : shareIntent); - return true; - } - } - else if (item.getItemId() == R.id.file_actions_select) - { - setItemsChecked(mCheckedList.size() != getListView().getCount()); - return true; - } - - return false; - } - - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean isChecked) - { - Uri uri = onItemChecked(mode, position, id, isChecked); - - if (isChecked) - mCheckedList.add(uri); - else - mCheckedList.remove(uri); - - mSelectAll.setIcon((mCheckedList.size() == getListView().getCount()) ? R.drawable.ic_unselect : R.drawable.ic_select); - - mode.setTitle(String.valueOf(getListView().getCheckedItemCount())); - } - - @Override - public void onDestroyActionMode(ActionMode p1) - { - mCheckedList.clear(); - mActionMode = null; - } - - public MenuItem getQuickSelectMenuItem() - { - return mSelectAll; - } - - public ArrayList getSharedItemList() - { - return mCheckedList; - } - - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/fragment/ApplicationListFragment.java b/app/src/main/java/com/genonbeta/TrebleShot/fragment/ApplicationListFragment.java deleted file mode 100644 index 3c421767b..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/fragment/ApplicationListFragment.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.genonbeta.TrebleShot.fragment; - -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.v7.app.AlertDialog; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.ListView; -import android.widget.Toast; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.adapter.ApplicationListAdapter; -import com.genonbeta.TrebleShot.support.FragmentTitle; - -public class ApplicationListFragment extends AbstractEditableListFragment implements FragmentTitle -{ - private SharedPreferences mPreferences; - - @Override - public void onActivityCreated(Bundle savedInstanceState) - { - super.onActivityCreated(savedInstanceState); - - this.setHasOptionsMenu(true); - this.mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); - } - - @Override - protected ApplicationListAdapter onAdapter() - { - return new ApplicationListAdapter(getActivity(), PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("show_system_apps", false)); - } - - @Override - protected ActionModeListener onActionModeListener() - { - return new ChoiceListener(); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) - { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.application_list, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - if (item.getItemId() == R.id.show_system_apps) - { - this.mPreferences.edit().putBoolean("show_system_apps", !this.mPreferences.getBoolean("show_system_apps", false)).commit(); - this.getAdapter().showSystemApps(this.mPreferences.getBoolean("show_system_apps", false)); - this.updateInBackground(); - - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - public void onPrepareOptionsMenu(Menu menu) - { - super.onPrepareOptionsMenu(menu); - - MenuItem menuSystemApps = menu.findItem(R.id.show_system_apps); - menuSystemApps.setChecked(this.mPreferences.getBoolean("show_system_apps", false)); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) - { - super.onListItemClick(l, v, position, id); - - final ApplicationListAdapter.AppInfo appInfo = (ApplicationListAdapter.AppInfo) getAdapter().getItem(position); - final Intent launchIntent = getActivity().getPackageManager().getLaunchIntentForPackage(appInfo.packageName); - - if (launchIntent != null) - { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()); - - dialogBuilder.setMessage(R.string.launch_application_question); - dialogBuilder.setNegativeButton(R.string.cancel, null); - dialogBuilder.setPositiveButton(R.string.launch, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - startActivity(launchIntent); - } - }); - - dialogBuilder.show(); - } - else - Toast.makeText(getActivity(), R.string.launch_application_error, Toast.LENGTH_SHORT).show(); - } - - @Override - public CharSequence getFragmentTitle(Context context) - { - return context.getString(R.string.application); - } - - private class ChoiceListener extends ActionModeListener - { - public Uri onItemChecked(ActionMode mode, int pos, long id, boolean isChecked) - { - ApplicationListAdapter.AppInfo info = (ApplicationListAdapter.AppInfo) getAdapter().getItem(pos); - return Uri.parse("file://" + info.codePath); - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/fragment/MusicListFragment.java b/app/src/main/java/com/genonbeta/TrebleShot/fragment/MusicListFragment.java deleted file mode 100644 index dfbbcfe6d..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/fragment/MusicListFragment.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.genonbeta.TrebleShot.fragment; - -import android.content.Context; -import android.net.Uri; -import android.view.ActionMode; -import android.view.View; -import android.widget.ListView; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.adapter.AbstractEditableListAdapter; -import com.genonbeta.TrebleShot.adapter.MusicListAdapter; -import com.genonbeta.TrebleShot.support.FragmentTitle; - -public class MusicListFragment extends AbstractEditableListFragment implements FragmentTitle -{ - @Override - protected MusicListAdapter onAdapter() - { - return new MusicListAdapter(getActivity()); - } - - @Override - protected ActionModeListener onActionModeListener() { return new ChoiceListener(); } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) - { - super.onListItemClick(l, v, position, id); - - MusicListAdapter.MusicInfo musicInfo = (MusicListAdapter.MusicInfo) getAdapter().getItem(position); - - this.openFile(musicInfo.uri, "audio/*", getString(R.string.file_open_app_chooser_msg)); - } - - @Override - public CharSequence getFragmentTitle(Context context) - { - return context.getString(R.string.music); - } - - private class ChoiceListener extends ActionModeListener - { - public Uri onItemChecked(ActionMode mode, int pos, long id, boolean isChecked) - { - MusicListAdapter.MusicInfo info = (MusicListAdapter.MusicInfo) getAdapter().getItem(pos); - - return info.uri; - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/fragment/NetworkDeviceListFragment.java b/app/src/main/java/com/genonbeta/TrebleShot/fragment/NetworkDeviceListFragment.java deleted file mode 100644 index a20066452..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/fragment/NetworkDeviceListFragment.java +++ /dev/null @@ -1,360 +0,0 @@ -package com.genonbeta.TrebleShot.fragment; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.os.Looper; -import android.preference.PreferenceManager; -import android.support.design.widget.Snackbar; -import android.support.v4.app.ListFragment; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.SwitchCompat; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.animation.Animation; -import android.view.animation.LinearInterpolator; -import android.view.animation.RotateAnimation; -import android.widget.AbsListView; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import com.genonbeta.CoolSocket.CoolCommunication; -import com.genonbeta.CoolSocket.CoolJsonCommunication; -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.adapter.NetworkDeviceListAdapter; -import com.genonbeta.TrebleShot.adapter.PendingProcessListAdapter; -import com.genonbeta.TrebleShot.config.AppConfig; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; -import com.genonbeta.TrebleShot.helper.NetworkDevice; -import com.genonbeta.TrebleShot.helper.NotificationPublisher; -import com.genonbeta.TrebleShot.receiver.DeviceScannerProvider; -import com.genonbeta.TrebleShot.support.FragmentTitle; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.net.Socket; - -public class NetworkDeviceListFragment extends ListFragment implements FragmentTitle -{ - private IntentFilter mIntentFilter = new IntentFilter(); - private SelfReceiver mReceiver = new SelfReceiver(); - private PokeHandler mPokeHandler = new PokeHandler(); - private NetworkDeviceListAdapter mListAdapter; - private NotificationPublisher mPublisher; - private SharedPreferences mPreferences; - private MenuItem mAnimatedSearchMenuItem; - private AbsListView.OnItemClickListener mClickListener; - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - mIntentFilter.addAction(DeviceScannerProvider.ACTION_DEVICE_FOUND); - mIntentFilter.addAction(DeviceScannerProvider.ACTION_SCAN_STARTED); - mIntentFilter.addAction(DeviceScannerProvider.ACTION_DEVICE_SCAN_COMPLETED); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) - { - super.onActivityCreated(savedInstanceState); - - mListAdapter = new NetworkDeviceListAdapter(getActivity()); - mPublisher = new NotificationPublisher(getActivity()); - mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); - - setListAdapter(mListAdapter); - setHasOptionsMenu(true); - - setEmptyText(getString(R.string.find_device_hint)); - - if (mPreferences.getBoolean("developer_mode", false)) - getActivity().sendBroadcast(new Intent(DeviceScannerProvider.ACTION_ADD_IP).putExtra(DeviceScannerProvider.EXTRA_DEVICE_IP, "127.0.0.1")); - - if (mPreferences.getBoolean("scan_devices_auto", false)) - getActivity().sendBroadcast(new Intent(DeviceScannerProvider.ACTION_SCAN_DEVICES)); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) - { - super.onListItemClick(l, v, position, id); - - final NetworkDevice device = (NetworkDevice) mListAdapter.getItem(position); - - if (mClickListener != null) - { - this.mClickListener.onItemClick(l, v, position, id); - } - else if (device.brand != null && device.model != null) - { - AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity()); - - dialog.setTitle(device.user); - - View rootView = getActivity().getLayoutInflater().inflate(R.layout.layout_device_info, null); - - TextView modelText = (TextView) rootView.findViewById(R.id.device_info_brand_and_model); - TextView ipText = (TextView) rootView.findViewById(R.id.device_info_ip_address); - SwitchCompat accessSwitch = (SwitchCompat) rootView.findViewById(R.id.device_info_access_switcher); - - modelText.setText(device.brand.toUpperCase() + " " + device.model.toUpperCase()); - ipText.setText(device.ip); - accessSwitch.setChecked(!device.isRestricted); - - accessSwitch.setOnCheckedChangeListener( - new OnCheckedChangeListener() - { - @Override - public void onCheckedChanged(CompoundButton button, boolean isChecked) - { - device.isRestricted = !isChecked; - } - } - ); - - dialog.setNegativeButton(R.string.close, null); - - dialog.setPositiveButton(R.string.poke, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialogInterface, int p2) - { - CoolJsonCommunication.Messenger.send(device.ip, AppConfig.COMMUNATION_SERVER_PORT, null, mPokeHandler); - } - } - ); - - dialog.setNeutralButton(R.string.thread_queue_short, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialogInterface, int p2) - { - AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity()); - - final PendingProcessListAdapter adapter = new PendingProcessListAdapter(getActivity(), device.ip); - - dialog.setTitle(getString(R.string.thread_queue) + " - " + device.user); - - if (adapter.getCount() < 1) - dialog.setMessage(R.string.list_empty_msg); - else - dialog.setAdapter(adapter, null); - - dialog.setNegativeButton(R.string.close, null); - dialog.setNeutralButton(R.string.clear_queue, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialogInterface, int p2) - { - adapter.clearQueue(); - } - } - ); - - - dialog.show(); - } - } - ); - - dialog.setView(rootView); - dialog.show(); - } - } - - @Override - public void onResume() - { - super.onResume(); - - mListAdapter.notifyDataSetChanged(); - getActivity().registerReceiver(mReceiver, mIntentFilter); - } - - @Override - public void onPrepareOptionsMenu(Menu menu) - { - super.onPrepareOptionsMenu(menu); - this.checkScanStatus(); - } - - @Override - public void onPause() - { - super.onPause(); - getActivity().unregisterReceiver(mReceiver); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) - { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.network_devices_options, menu); - - mAnimatedSearchMenuItem = menu.findItem(R.id.network_devices_scan); - - ImageView menuIcon = (ImageView) mAnimatedSearchMenuItem.getActionView(); - - menuIcon.setImageResource(R.drawable.ic_retry_alpha); - menuIcon.setPadding(5, 5, 5, 5); - - menuIcon.setOnClickListener( - new OnClickListener() - { - @Override - public void onClick(View view) - { - boolean isAvailable = ApplicationHelper.getNetworkDeviceScanner().isScannerAvaiable(); - - if (isAvailable) - getActivity().sendBroadcast(new Intent(DeviceScannerProvider.ACTION_SCAN_DEVICES)); - else - { - mPublisher.makeToast(R.string.stopping_msg); - ApplicationHelper.getNetworkDeviceScanner().interrupt(); - } - } - } - ); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case R.id.network_devices_scan: - getActivity().sendBroadcast(new Intent(DeviceScannerProvider.ACTION_SCAN_DEVICES)); - return true; - case R.id.network_devices_clear_list: - ApplicationHelper.getDeviceList().clear(); - mListAdapter.notifyDataSetChanged(); - mPublisher.makeToast(R.string.device_list_cleared_msg); - return true; - } - - return super.onOptionsItemSelected(item); - } - - private void checkScanStatus() - { - if (mAnimatedSearchMenuItem == null) - return; - - ImageView reloadImage = (ImageView) mAnimatedSearchMenuItem.getActionView(); - - if (!ApplicationHelper.getNetworkDeviceScanner().isScannerAvaiable()) - { - RotateAnimation anim = new RotateAnimation(0.0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); - - anim.setInterpolator(new LinearInterpolator()); - anim.setRepeatCount(Animation.INFINITE); - anim.setDuration(700); - - reloadImage.startAnimation(anim); - } - else - reloadImage.setAnimation(null); - } - - @Override - public CharSequence getFragmentTitle(Context context) - { - return context.getString(R.string.device_list); - } - - public void setOnListClickListener(AbsListView.OnItemClickListener listener) - { - this.mClickListener = listener; - } - - private void showSnackbar(int resId) - { - Snackbar.make(NetworkDeviceListFragment.this.getActivity().findViewById(android.R.id.content), resId, Snackbar.LENGTH_SHORT).show(); - } - - private class SelfReceiver extends BroadcastReceiver - { - @Override - public void onReceive(Context context, Intent intent) - { - NetworkDeviceListFragment.this.checkScanStatus(); - - if (DeviceScannerProvider.ACTION_DEVICE_FOUND.equals(intent.getAction())) - { - mListAdapter.notifyDataSetChanged(); - } - else if (DeviceScannerProvider.ACTION_SCAN_STARTED.equals(intent.getAction()) && intent.hasExtra(DeviceScannerProvider.EXTRA_SCAN_STATUS)) - { - String scanStatus = intent.getStringExtra(DeviceScannerProvider.EXTRA_SCAN_STATUS); - - if (DeviceScannerProvider.STATUS_OK.equals(scanStatus)) - showSnackbar(R.string.devices_scanning_msg); - else if (DeviceScannerProvider.STATUS_NO_NETWORK_INTERFACE.equals(scanStatus)) - showSnackbar(R.string.no_network_interface_msg); - } - else if (DeviceScannerProvider.ACTION_DEVICE_SCAN_COMPLETED.equals(intent.getAction())) - { - showSnackbar(R.string.scan_completed); - } - } - } - - protected class PokeHandler extends CoolJsonCommunication.JsonResponseHandler - { - @Override - public void onJsonMessage(Socket socket, CoolCommunication.Messenger.Process process, JSONObject json) - { - try - { - json.put("request", "poke_the_device"); - - JSONObject response = new JSONObject(process.waitForResponse()); - - if (response.getBoolean("result")) - { - showToast(getString(R.string.poke_sent)); - return; - } - - showToast(getString(R.string.poke_error, getString(R.string.not_allowed_error))); - } catch (JSONException e) - { - showToast(getString(R.string.poke_error, getString(R.string.communication_problem))); - } - } - - @Override - public void onError(Exception exception) - { - super.onError(exception); - - showToast(getString(R.string.poke_error, getString(R.string.connection_problem))); - - Looper.loop(); - } - - private void showToast(String text) - { - Looper.prepare(); - - if (getActivity() != null) - mPublisher.makeToast(text); - - Looper.loop(); - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/fragment/P2pDeviceListFragment.java b/app/src/main/java/com/genonbeta/TrebleShot/fragment/P2pDeviceListFragment.java deleted file mode 100644 index 00fae6efe..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/fragment/P2pDeviceListFragment.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.genonbeta.TrebleShot.fragment; - -import android.app.ListFragment; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.NetworkInfo; -import android.net.wifi.WpsInfo; -import android.net.wifi.p2p.WifiP2pConfig; -import android.net.wifi.p2p.WifiP2pDevice; -import android.net.wifi.p2p.WifiP2pDeviceList; -import android.net.wifi.p2p.WifiP2pManager; -import android.os.Bundle; -import android.view.View; -import android.widget.ListView; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.adapter.P2pDeviceListAdapter; -import com.genonbeta.TrebleShot.helper.NotificationPublisher; - -import java.util.ArrayList; - -public class P2pDeviceListFragment extends ListFragment -{ - private P2pDeviceListAdapter mListAdapter; - private NotificationPublisher mPublisher; - private DefaultClassListener mListener = new DefaultClassListener(); - private WifiP2pManager mManager; - private WifiP2pManager.Channel mChannel; - private IntentFilter mIntentFilter = new IntentFilter(); - private P2pStatusReceiver mReceiver = new P2pStatusReceiver(); - private ArrayList mPeers = new ArrayList(); - - @Override - public void onActivityCreated(Bundle savedInstanceState) - { - super.onActivityCreated(savedInstanceState); - - mListAdapter = new P2pDeviceListAdapter(getActivity(), mPeers); - setListAdapter(mListAdapter); - - mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); - mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); - mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); - mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); - - mManager = (WifiP2pManager) getActivity().getSystemService(Context.WIFI_P2P_SERVICE); - mChannel = mManager.initialize(getActivity(), getActivity().getMainLooper(), null); - mPublisher = new NotificationPublisher(getActivity()); - } - - @Override - public void onResume() - { - super.onResume(); - getActivity().registerReceiver(mReceiver, mIntentFilter); - } - - @Override - public void onPause() - { - super.onPause(); - getActivity().unregisterReceiver(mReceiver); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) - { - super.onListItemClick(l, v, position, id); - - final WifiP2pDevice device = mPeers.get(position); - - if (device == null) - return; - - WifiP2pConfig config = new WifiP2pConfig(); - config.deviceAddress = device.deviceAddress; - config.wps.setup = WpsInfo.PBC; - - if (device.status == WifiP2pDevice.INVITED) - mManager.cancelConnect(mChannel, mListener); - else if (device.status != WifiP2pDevice.UNAVAILABLE && device.status != WifiP2pDevice.CONNECTED) - mManager.connect(mChannel, config, mListener); - else - { - mPublisher.makeToast(getActivity().getString(R.string.device_not_be_managed_msg, device.deviceName)); - } - } - - protected class DefaultClassListener implements WifiP2pManager.PeerListListener, WifiP2pManager.ActionListener - { - @Override - public void onSuccess() - { - mPublisher.makeToast(R.string.in_process_msg); - } - - @Override - public void onFailure(int p1) - { - mPublisher.makeToast(R.string.process_failure_msg_p2p); - } - - @Override - public void onPeersAvailable(WifiP2pDeviceList peerList) - { - mPeers.clear(); - mPeers.addAll(peerList.getDeviceList()); - - mListAdapter.notifyDataSetChanged(); - } - } - - public void discoverPeers() - { - mManager.discoverPeers(mChannel, mListener); - } - - public class P2pStatusReceiver extends BroadcastReceiver - { - - public void onReceive(Context context, Intent intent) - { - String action = intent.getAction(); - - if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) - { - int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); - - if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) - { - discoverPeers(); - } - } - else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) - if (mManager != null) - mManager.requestPeers(mChannel, mListener); - else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) - { - NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); - - // if (networkInfo.isConnected()) - - } - else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) - { - WifiP2pDevice thisDevice = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE); - } - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/fragment/PendingProcessListFragment.java b/app/src/main/java/com/genonbeta/TrebleShot/fragment/PendingProcessListFragment.java deleted file mode 100644 index d086f1f10..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/fragment/PendingProcessListFragment.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.genonbeta.TrebleShot.fragment; - -import android.os.Bundle; -import android.support.v4.app.ListFragment; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.adapter.PendingProcessListAdapter; - -public class PendingProcessListFragment extends ListFragment -{ - private PendingProcessListAdapter mAdapter; - - @Override - public void onActivityCreated(Bundle savedInstanceState) - { - super.onActivityCreated(savedInstanceState); - mAdapter = new PendingProcessListAdapter(getActivity(), null); - - setEmptyText(getString(R.string.list_empty_msg)); - setListAdapter(mAdapter); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/fragment/PreferencesFragment.java b/app/src/main/java/com/genonbeta/TrebleShot/fragment/PreferencesFragment.java deleted file mode 100644 index 401242391..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/fragment/PreferencesFragment.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.genonbeta.TrebleShot.fragment; - -import android.os.Bundle; -import android.preference.PreferenceFragment; - -import com.genonbeta.TrebleShot.R; - -public class PreferencesFragment extends PreferenceFragment -{ - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.preferences_main); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/fragment/ReceivedFilesListFragment.java b/app/src/main/java/com/genonbeta/TrebleShot/fragment/ReceivedFilesListFragment.java deleted file mode 100644 index b800b6279..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/fragment/ReceivedFilesListFragment.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.genonbeta.TrebleShot.fragment; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.Uri; -import android.os.Bundle; -import android.support.v7.widget.SearchView; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.ListView; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.adapter.ReceivedFilesListAdapter; -import com.genonbeta.TrebleShot.fragment.dialog.FileDeleteDialogFragment; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; -import com.genonbeta.TrebleShot.helper.FileUtils; -import com.genonbeta.TrebleShot.helper.GAnimater; -import com.genonbeta.TrebleShot.helper.NotificationPublisher; -import com.genonbeta.TrebleShot.service.ServerService; -import com.genonbeta.TrebleShot.support.FragmentTitle; - -public class ReceivedFilesListFragment extends AbstractEditableListFragment implements FragmentTitle -{ - public static final String TAG = "ReceivedFilesListFragment"; - - private NotificationPublisher mPublisher; - private IntentFilter mIntentFilter = new IntentFilter(); - private BroadcastReceiver mReceiver = new BroadcastReceiver() - { - @Override - public void onReceive(Context context, Intent intent) - { - if (ServerService.ACTION_FILE_LIST_CHANGED.equals(intent.getAction())) - ReceivedFilesListFragment.this.updateInBackground(); - } - }; - - @Override - protected ReceivedFilesListAdapter onAdapter() - { - return new ReceivedFilesListAdapter(getActivity()); - } - - @Override - protected AbstractEditableListFragment.ActionModeListener onActionModeListener() - { - return new ChoiceListener(); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) - { - super.onActivityCreated(savedInstanceState); - - mPublisher = new NotificationPublisher(getActivity()); - mIntentFilter.addAction(ServerService.ACTION_FILE_LIST_CHANGED); - - GAnimater.applyLayoutAnimation(getListView(), GAnimater.APPEAR); - } - - @Override - public void onResume() - { - super.onResume(); - this.getActivity().registerReceiver(mReceiver, mIntentFilter); - } - - @Override - public void onPause() - { - super.onPause(); - this.getActivity().unregisterReceiver(mReceiver); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) - { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.received_files_options, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case (R.id.received_device_options_refresh): - this.updateInBackground(); - return true; - case (R.id.received_device_options_open_in_file_manager): - this.openFile(Uri.fromFile(ApplicationHelper.getApplicationDirectory(getActivity())), "*/*", getString(R.string.pick_file_manager)); - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) - { - super.onListItemClick(l, v, position, id); - - ReceivedFilesListAdapter.FileInfo fileInfo = (ReceivedFilesListAdapter.FileInfo) getAdapter().getItem(position); - - this.openFile(Uri.fromFile(fileInfo.file), FileUtils.getFileContentType(fileInfo.file.getAbsolutePath()), getString(R.string.file_open_app_chooser_msg)); - } - - @Override - public CharSequence getFragmentTitle(Context context) - { - return context.getString(R.string.received_files); - } - - private class ChoiceListener extends ActionModeListener - { - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) - { - if (!super.onCreateActionMode(mode, menu)) - return false; - - mode.getMenuInflater().inflate(R.menu.file_actions, menu); - - return true; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) - { - if (!super.onActionItemClicked(mode, item)) - if (item.getItemId() == R.id.file_actions_delete) - { - FileDeleteDialogFragment df = new FileDeleteDialogFragment(); - - df.setItems(getSharedItemList().toArray()); - - df.setOnDeleteCompletedListener( - new FileDeleteDialogFragment.OnDeleteCompletedListener() - { - @Override - public void onFilesDeleted(FileDeleteDialogFragment fragment, int fileSize) - { - fragment.getContext().sendBroadcast(new Intent(ServerService.ACTION_FILE_LIST_CHANGED)); - } - } - ); - - df.show(getFragmentManager(), "delete"); - - mode.finish(); - - return true; - } - - return false; - } - - @Override - public Uri onItemChecked(ActionMode mode, int position, long id, boolean isChecked) - { - ReceivedFilesListAdapter.FileInfo fileInfo = (ReceivedFilesListAdapter.FileInfo) getAdapter().getItem(position); - - return Uri.fromFile(fileInfo.file); - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/fragment/VideoListFragment.java b/app/src/main/java/com/genonbeta/TrebleShot/fragment/VideoListFragment.java deleted file mode 100644 index e5cd070b8..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/fragment/VideoListFragment.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.genonbeta.TrebleShot.fragment; - -import android.content.Context; -import android.net.Uri; -import android.view.ActionMode; -import android.view.View; -import android.widget.ListView; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.adapter.VideoListAdapter; -import com.genonbeta.TrebleShot.support.FragmentTitle; - -public class VideoListFragment extends AbstractEditableListFragment implements FragmentTitle -{ - @Override - protected VideoListAdapter onAdapter() - { - return new VideoListAdapter(getActivity()); - } - - @Override - protected ActionModeListener onActionModeListener() { return new ChoiceListener(); } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) - { - super.onListItemClick(l, v, position, id); - - VideoListAdapter.VideoInfo videoInfo = (VideoListAdapter.VideoInfo) this.getAdapter().getItem(position); - - this.openFile(videoInfo.uri, "video/*", getString(R.string.file_open_app_chooser_msg)); - } - - @Override - public CharSequence getFragmentTitle(Context context) - { - return context.getString(R.string.video); - } - - private class ChoiceListener extends ActionModeListener - { - public Uri onItemChecked(ActionMode mode, int pos, long id, boolean isChecked) - { - VideoListAdapter.VideoInfo info = (VideoListAdapter.VideoInfo) getAdapter().getItem(pos); - - return info.uri; - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/fragment/dialog/AboutDialog.java b/app/src/main/java/com/genonbeta/TrebleShot/fragment/dialog/AboutDialog.java deleted file mode 100644 index a09651880..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/fragment/dialog/AboutDialog.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.genonbeta.TrebleShot.fragment.dialog; - -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentSender; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.config.AppConfig; - -public class AboutDialog extends DialogFragment -{ - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) - { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - builder.setTitle(R.string.about); - builder.setMessage(R.string.about_summary); - builder.setNegativeButton(R.string.close, null); - builder.setPositiveButton(R.string.see_source_code, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(AppConfig.APPLICATION_REPO))); - } - }); - - return builder.create(); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/fragment/dialog/FileDeleteDialogFragment.java b/app/src/main/java/com/genonbeta/TrebleShot/fragment/dialog/FileDeleteDialogFragment.java deleted file mode 100644 index 5cc0c18bc..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/fragment/dialog/FileDeleteDialogFragment.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.genonbeta.TrebleShot.fragment.dialog; - -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.os.Looper; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.AlertDialog; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.helper.NotificationPublisher; - -import java.io.File; -import java.net.URI; -import java.util.ArrayList; -import java.util.List; - -public class FileDeleteDialogFragment extends DialogFragment -{ - private ArrayList mFiles = new ArrayList(); - private OnDeleteCompletedListener mDeleteListener = null; - private Context mContext; - - public void setItems(List items) - { - mFiles.clear(); - mFiles.addAll(items); - } - - public void setItems(Object[] items) - { - mFiles.clear(); - - for (Object path : items) - mFiles.add(URI.create(path.toString())); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) - { - this.mContext = getActivity(); - - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()); - final NotificationPublisher publisher = new NotificationPublisher(getActivity().getApplicationContext()); - - dialogBuilder.setTitle(R.string.delete_confirm); - dialogBuilder.setMessage(getString(R.string.delete_warning, mFiles.size())); - - dialogBuilder.setNegativeButton(R.string.cancel, null); - - dialogBuilder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dailog, int p2) - { - new Thread(new Runnable() - { - @Override - public void run() - { - Looper.prepare(); - - for (URI filePath : mFiles) - { - File file = new File(filePath); - file.delete(); - } - - if (mDeleteListener != null) - mDeleteListener.onFilesDeleted(FileDeleteDialogFragment.this, mFiles.size()); - - try - { - publisher.makeToast(getString(R.string.delete_completed, mFiles.size())); - } catch (IllegalStateException e) - { - e.printStackTrace(); - } - - Looper.loop(); - } - } - ).start(); - } - } - ); - - return dialogBuilder.show(); - } - - @Override - public void onPause() - { - super.onPause(); - - FragmentTransaction ft = getFragmentManager().beginTransaction(); - - ft.detach(this); - ft.commit(); - } - - public Context getContext() - { - return this.mContext; - } - - public void setOnDeleteCompletedListener(OnDeleteCompletedListener listener) - { - mDeleteListener = listener; - } - - public static interface OnDeleteCompletedListener - { - public void onFilesDeleted(FileDeleteDialogFragment fragment, int fileSize); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/ApplicationHelper.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/ApplicationHelper.java deleted file mode 100644 index bb0e79b25..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/ApplicationHelper.java +++ /dev/null @@ -1,212 +0,0 @@ -package com.genonbeta.TrebleShot.helper; - -import android.content.Context; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.net.Uri; -import android.os.Environment; -import android.preference.PreferenceManager; -import android.provider.MediaStore; -import android.util.Log; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.core.util.NetworkDeviceScanner; - -import java.io.File; -import java.net.URI; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.concurrent.ArrayBlockingQueue; - -public class ApplicationHelper -{ - public static final String TAG = "ApplicatiobHelper"; - - private static HashMap mDeviceList = new HashMap(); - private static HashMap mSenders = new HashMap(); - private static ArrayBlockingQueue mReceivers = new ArrayBlockingQueue(2000, true); - private static ArrayBlockingQueue mPendingReceivers = new ArrayBlockingQueue(2000, true); - private static NetworkDeviceScanner mDeviceScanner = new NetworkDeviceScanner(); - private static int mUniqueNumber = 0; - - public static int acceptPendingReceivers(int acceptId) - { - int count = 0; - - Log.d(TAG, "Receiver count " + getReceivers().size() + "; pending receiver count = " + getPendingReceivers().size() + "; copiedReceivers = " + getPendingReceivers().size()); - - for (AwaitedFileReceiver receiver : getPendingReceivers()) - { - Log.d(TAG, "Accept requested id = " + acceptId + "; current receivers id " + receiver.acceptId); - - if (receiver.acceptId != acceptId) - continue; - - getReceivers().offer(receiver); - getPendingReceivers().remove(receiver); - - count++; - } - - Log.d(TAG, "After accepting pendingReceivers, current receivers count " + getReceivers().size()); - - return count; - } - - public static File getApplicationDirectory(Context context) - { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - File testPath = new File(prefs.getString("storage_path", Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + context.getString(R.string.app_name))); - - if (testPath.isDirectory()) - return testPath; - else if (!testPath.exists() && testPath.mkdirs()) - return testPath; - - File appDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + context.getString(R.string.app_name)); - - if (!appDir.exists()) - appDir.mkdirs(); - - return appDir; - } - - public static ArrayBlockingQueue getReceivers() - { - return mReceivers; - } - - public static ArrayBlockingQueue getPendingReceivers() - { - return mPendingReceivers; - } - - public static HashMap getSenders() - { - return mSenders; - } - - public static HashMap getDeviceList() - { - return mDeviceList; - } - - public static File getFileFromUri(Context context, Uri fileUri) - { - String fileUriString = fileUri.toString(); - File file = null; - - if (fileUriString.startsWith("content")) - { - Cursor cursor = context.getContentResolver().query(fileUri, null, null, null, null); - - if (cursor != null) - { - if (cursor.moveToFirst()) - { - int dataIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA); - String dataPath = cursor.getString(dataIndex); - - file = new File(dataPath); - } - - cursor.close(); - } - } - else if (fileUriString.startsWith("file")) - file = new File(URI.create(fileUriString)); - - return file; - } - - public static NetworkDeviceScanner getNetworkDeviceScanner() - { - return mDeviceScanner; - } - - public static ArrayList getPendingReceiversByAcceptId(int acceptId) - { - ArrayList list = new ArrayList(); - - for (AwaitedFileReceiver receiver : getPendingReceivers()) - { - if (receiver.acceptId == acceptId) - list.add(receiver); - } - - return list; - } - - public static int getUniqueNumber() - { - return mUniqueNumber++; - } - - public static boolean isReceiverExist(AwaitedFileReceiver receiver) - { - if (getReceivers().contains(receiver)) - return true; - - return false; - } - - public static boolean searchWord(String word, String searchThis) - { - return word.toLowerCase().contains(searchThis); - } - - public static int removePendingReceivers(int acceptId) - { - int count = 0; - - for (AwaitedFileReceiver receiver : getPendingReceivers()) - { - if (receiver.acceptId != acceptId) - continue; - - getPendingReceivers().remove(receiver); - - count++; - } - - return count; - } - - public static int removeReceivers(int acceptId) - { - int count = 0; - - for (AwaitedFileReceiver receiver : getReceivers()) - { - if (receiver.acceptId != acceptId) - continue; - - getReceivers().remove(receiver); - - count++; - } - - return count; - } - - public static boolean removeReceiver(AwaitedFileReceiver receiver) - { - if (!isReceiverExist(receiver)) - return false; - - getReceivers().remove(receiver); - - return true; - } - - public static boolean removeSender(AwaitedFileSender sender) - { - if (!ApplicationHelper.getSenders().containsKey(sender.requestId)) - return false; - - ApplicationHelper.getSenders().remove(sender.requestId); - - return true; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/AwaitedFileReceiver.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/AwaitedFileReceiver.java deleted file mode 100644 index 76ab38873..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/AwaitedFileReceiver.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.genonbeta.TrebleShot.helper; - -public class AwaitedFileReceiver -{ - public String ip; - public String fileName; - public String fileMimeType; - public int requestId; - public int acceptId; - public long fileSize; - public boolean processCancelled = false; - - public AwaitedFileReceiver(String ip, int requestId, int acceptId, String fileName, long fileSize, String fileMime) - { - this.ip = ip; - this.requestId = requestId; - this.acceptId = acceptId; - this.fileName = fileName; - this.fileSize = fileSize; - this.fileMimeType = fileMime; - } - - public AwaitedFileReceiver() - { - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/AwaitedFileSender.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/AwaitedFileSender.java deleted file mode 100644 index 21ac939e8..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/AwaitedFileSender.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.genonbeta.TrebleShot.helper; - -import java.io.File; - -public class AwaitedFileSender -{ - public String ip; - public int port; - public int requestId; - public boolean isCancelled = false; - public File file; - - public AwaitedFileSender(String ip, File file, int requestId) - { - this.ip = ip; - this.file = file; - this.requestId = requestId; - } - - public void setPort(int port) - { - this.port = port; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/FileUtils.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/FileUtils.java deleted file mode 100644 index b073869f3..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/FileUtils.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.genonbeta.TrebleShot.helper; - -import android.content.Context; - -import java.io.File; -import java.net.FileNameMap; -import java.net.URLConnection; - -public class FileUtils -{ - public static String getFileContentType(String fileUrl) - { - FileNameMap nameMap = URLConnection.getFileNameMap(); - String fileType = nameMap.getContentTypeFor(fileUrl); - - return (fileType == null) ? "*/*" : fileType; - } - - public static String sizeExpression(long bytes, boolean si) - { - int unit = si ? 1000 : 1024; - - if (bytes < unit) - return bytes + " B"; - - int expression = (int) (Math.log(bytes) / Math.log(unit)); - String prefix = (si ? "kMGTPE" : "KMGTPE").charAt(expression - 1) + (si ? "i" : ""); - - return String.format("%.1f %sB", bytes / Math.pow(unit, expression), prefix); - } - - public static File getUniqueFile(File file) - { - String path = file.getName(); - int pathStartPosition = path.lastIndexOf("."); - - if (pathStartPosition != -1) - { - String fileName = path.substring(0, pathStartPosition); - String fileExtension = path.substring(pathStartPosition); - - return new File(file.getParent() + File.separator + fileName + " [" + System.currentTimeMillis() + "]" + fileExtension); - } - - return new File(file.getParent() + File.separator + file.getName() + " [" + System.currentTimeMillis() + "]"); - } - - public static String getSaveLocationForFile(Context context, String file) - { - return ApplicationHelper.getApplicationDirectory(context).getAbsolutePath() + "/" + file; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/GAnimater.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/GAnimater.java deleted file mode 100644 index e408c3759..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/GAnimater.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.genonbeta.TrebleShot.helper; - -import android.view.ViewGroup; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.LayoutAnimationController; -import android.view.animation.TranslateAnimation; - -public class GAnimater -{ - public static int APPEAR = 1; - public static int DISAPPEAR = 2; - - public static void applyLayoutAnimation(ViewGroup viewGroup, int mode) - { - LayoutAnimationController controller = new LayoutAnimationController(getAnimation(mode), 0.5f); - viewGroup.setLayoutAnimation(controller); - } - - public static AnimationSet getAnimation(int mode) - { - AnimationSet set = new AnimationSet(true); - - if (mode == APPEAR) - { - Animation animation = new AlphaAnimation(0.0f, 1.0f); - animation.setDuration(300); - set.addAnimation(animation); - - animation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, - Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f - ); - - animation.setDuration(300); - set.addAnimation(animation); - } - else if (mode == DISAPPEAR) - { - Animation animation = new AlphaAnimation(1.0f, 0.0f); - animation.setDuration(300); - set.addAnimation(animation); - - animation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, - Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 1.0f - ); - - animation.setDuration(300); - set.addAnimation(animation); - } - - return set; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/JsonResponseHandler.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/JsonResponseHandler.java deleted file mode 100644 index 9a56373aa..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/JsonResponseHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.genonbeta.TrebleShot.helper; - -import com.genonbeta.CoolSocket.CoolCommunication; -import com.genonbeta.CoolSocket.CoolJsonCommunication; -import com.genonbeta.TrebleShot.config.AppConfig; - -import org.json.JSONObject; - -import java.net.Socket; - -public abstract class JsonResponseHandler extends CoolJsonCommunication.JsonResponseHandler -{ - @Override - public void onConfigure(CoolCommunication.Messenger.Process process) - { - process.setSocketTimeout(AppConfig.DEFAULT_SOCKET_TIMEOUT); - } - - @Override - public void onJsonMessage(Socket socket, CoolCommunication.Messenger.Process process, JSONObject json) - { - } - - @Override - public void onError(Exception exception) - { - } - - @Override - public void onResponseAvaiable(String response) - { - } - - @Override - public void onFinal() - { - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/NetworkDevice.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/NetworkDevice.java deleted file mode 100644 index 2fa077713..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/NetworkDevice.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.genonbeta.TrebleShot.helper; - -public class NetworkDevice -{ - public String ip; - public String brand; - public String model; - public String user; - public boolean isRestricted = false; - public boolean isLocalAddress = false; - - public NetworkDevice(String ip, String brand, String model, String user) - { - this.ip = ip; - this.brand = brand; - this.model = model; - this.user = user; - } - - public NetworkDevice() - { - } - - public String toString() - { - return (this.model != null) ? this.model + " - " + this.ip : this.ip; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/NetworkDeviceInfoLoader.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/NetworkDeviceInfoLoader.java deleted file mode 100644 index 439c33b13..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/NetworkDeviceInfoLoader.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.genonbeta.TrebleShot.helper; - -import android.content.Context; -import android.os.Build; -import android.util.Log; - -import com.genonbeta.CoolSocket.CoolCommunication; -import com.genonbeta.TrebleShot.config.AppConfig; - -import org.json.JSONObject; - -import java.net.Socket; - -public class NetworkDeviceInfoLoader -{ - private OnInfoAvailableListener mOnInfoAvaiableListener; - - public NetworkDeviceInfoLoader(OnInfoAvailableListener listener) - { - setOnInfoAvailableListener(listener); - } - - public boolean startLoading(final Context context, final String deviceIp, final boolean dontDeleteSelfIps) - { - CoolCommunication.Messenger.send(deviceIp, AppConfig.COMMUNATION_SERVER_PORT, null, - new JsonResponseHandler() - { - @Override - public void onConfigure(CoolCommunication.Messenger.Process process) - { - super.onConfigure(process); - - try - { - Thread.sleep(1500); - } catch (InterruptedException e) - { - e.printStackTrace(); - } - } - - @Override - public void onJsonMessage(Socket socket, CoolCommunication.Messenger.Process process, JSONObject json) - { - } - - @Override - public void onResponseAvaiable(String response) - { - try - { - Log.d("DeviceInfo", deviceIp + ": " + response); - - JSONObject json = new JSONObject(response).getJSONObject("deviceInfo"); - - NetworkDevice device = new NetworkDevice(deviceIp, null, null, null); - - device.brand = json.getString("brand"); - device.model = json.getString("model"); - device.user = json.getString("deviceName"); - - if (device.user == null || device.model == null || device.brand == null) - return; - - if (dontDeleteSelfIps == false) - if (Build.DISPLAY.equals(json.getString("display"))) - return; - - if (mOnInfoAvaiableListener != null) - mOnInfoAvaiableListener.onInfoAvailable(device); - } catch (Exception e) - { - this.onError(e); - } - } - } - ); - - return true; - } - - public void setOnInfoAvailableListener(OnInfoAvailableListener listener) - { - mOnInfoAvaiableListener = listener; - } - - public static interface OnInfoAvailableListener - { - public void onInfoAvailable(NetworkDevice device); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/NotificationPublisher.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/NotificationPublisher.java deleted file mode 100644 index 374cd8420..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/NotificationPublisher.java +++ /dev/null @@ -1,435 +0,0 @@ -package com.genonbeta.TrebleShot.helper; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.preference.PreferenceManager; -import android.support.v7.app.NotificationCompat; -import android.util.Log; -import android.widget.Toast; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.activity.TrebleShotActivity; -import com.genonbeta.TrebleShot.receiver.DialogEventReceiver; -import com.genonbeta.TrebleShot.service.ClientService; -import com.genonbeta.TrebleShot.service.CommunicationService; -import com.genonbeta.TrebleShot.service.ServerService; - -import java.io.File; - -public class NotificationPublisher -{ - public static final String TAG = "NotificationPublisher"; - public static final String EXTRA_NOTIFICATION_ID = "notificationId"; - public static final int NOTIFICATION_SERVICE_STARTED = 232434; - public static final int NOTIFICATION_OPPOSITE_DEVICE_PING = 232438; - public static final int NOTIFICATION_ID_RECEIVED = 20000; - public static final int NOTIFICATION_ID_SENT = 20001; - public static final int NOTIFICATION_ID_RECEIVING = 20002; - public static final int NOTIFICATION_ID_RECEIVE_ERROR = 20003; - - private Context mContext; - private NotificationManager mManager; - private SharedPreferences mPreferences; - - public NotificationPublisher(Context context) - { - this.mContext = context; - this.mManager = (NotificationManager) mContext.getSystemService(Service.NOTIFICATION_SERVICE); - this.mPreferences = PreferenceManager.getDefaultSharedPreferences(this.mContext); - } - - public NotificationManager getNotificationManager() - { - return this.mManager; - } - - public SharedPreferences getSharedPreferences() - { - return this.mPreferences; - } - - public void notifyConnectionRequest(String clientIp) - { - NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); - int uniqueNotificationId = ApplicationHelper.getUniqueNumber(); - Intent acceptIntent = new Intent(mContext, CommunicationService.class); - Intent dialogIntent = new Intent(mContext, DialogEventReceiver.class); - - acceptIntent.setAction(CommunicationService.ACTION_ALLOW_IP); - acceptIntent.putExtra(CommunicationService.EXTRA_DEVICE_IP, clientIp); - acceptIntent.putExtra(EXTRA_NOTIFICATION_ID, uniqueNotificationId); - - Intent rejectIntent = ((Intent) acceptIntent.clone()); - rejectIntent.setAction(CommunicationService.ACTION_REJECT_IP); - - PendingIntent positiveIntent = PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), acceptIntent, 0); - PendingIntent negativeIntent = PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), rejectIntent, 0); - - dialogIntent.setAction(DialogEventReceiver.ACTION_DIALOG); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_TITLE, mContext.getString(R.string.connection_permission)); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_MESSAGE, mContext.getString(R.string.allow_device_to_connect)); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_POSITIVE_INTENT, positiveIntent); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_NEGATIVE_INTENT, negativeIntent); - - Log.i(TAG, "clientIp = " + clientIp); - - builder.setSmallIcon(android.R.drawable.stat_notify_error) - .setContentTitle(mContext.getString(R.string.connection_permission)) - .setContentText(mContext.getString(R.string.allow_device_to_connect)) - .setContentInfo(clientIp) - .setContentIntent(PendingIntent.getBroadcast(mContext, ApplicationHelper.getUniqueNumber(), dialogIntent, 0)) - .setDefaults(this.getNotificationDefaults()) - .setDeleteIntent(negativeIntent) - .addAction(android.R.drawable.ic_menu_send, mContext.getString(R.string.accept), positiveIntent) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, mContext.getString(R.string.reject), negativeIntent) - .setTicker(mContext.getString(R.string.connection_permission)); - - mManager.notify(uniqueNotificationId, builder.build()); - } - - public void notifyTransferRequest(int acceptId, NetworkDevice device, AwaitedFileReceiver receiver, boolean halfRestriction) - { - int uniqueNotificationId = ApplicationHelper.getUniqueNumber(); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); - Intent acceptIntent = new Intent(mContext, CommunicationService.class); - Intent dialogIntent = new Intent(mContext, DialogEventReceiver.class); - - acceptIntent.setAction(CommunicationService.ACTION_FILE_TRANSFER_ACCEPT); - - acceptIntent.putExtra(CommunicationService.EXTRA_ACCEPT_ID, acceptId); - acceptIntent.putExtra(CommunicationService.EXTRA_DEVICE_IP, receiver.ip); - acceptIntent.putExtra(EXTRA_NOTIFICATION_ID, uniqueNotificationId); - - Intent rejectIntent = ((Intent) acceptIntent.clone()); - rejectIntent.setAction(CommunicationService.ACTION_FILE_TRANSFER_REJECT); - - int acceptText; - int rejectText; - - if (halfRestriction) - { - acceptIntent.putExtra(CommunicationService.EXTRA_HALF_RESTRICT, true); - rejectIntent.putExtra(CommunicationService.EXTRA_HALF_RESTRICT, true); - - acceptText = R.string.receive_restricted; - rejectText = R.string.reject_restricted; - } - else - { - acceptText = R.string.accept; - rejectText = R.string.reject; - } - - PendingIntent positiveIntent = PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), acceptIntent, 0); - PendingIntent negativeIntent = PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), rejectIntent, 0); - - dialogIntent.setAction(DialogEventReceiver.ACTION_DIALOG); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_TITLE, mContext.getString(R.string.receive_the_file_que)); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_MESSAGE, receiver.fileName); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_POSITIVE_INTENT, positiveIntent); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_NEGATIVE_INTENT, negativeIntent); - - Log.i(TAG, "clientIp = " + device.ip + "; fileName = " + receiver.fileName + " ; fileType = " + receiver.fileMimeType + " ; requestId = " + receiver.requestId); - - builder.setSmallIcon(android.R.drawable.stat_sys_download_done) - .setContentTitle(mContext.getString(R.string.receive_the_file_que)) - .setContentText(receiver.fileName) - .setContentInfo(device.user) - .setContentIntent(PendingIntent.getBroadcast(mContext, ApplicationHelper.getUniqueNumber(), dialogIntent, 0)) - .setDefaults(this.getNotificationDefaults()) - .setDeleteIntent(negativeIntent) - .addAction(android.R.drawable.ic_menu_send, mContext.getString(acceptText), positiveIntent) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, mContext.getString(rejectText), negativeIntent) - .setTicker(mContext.getString(R.string.receive_the_file_que)); - - mManager.notify(uniqueNotificationId, builder.build()); - } - - public void notifyMultiTransferRequest(int numberOfFiles, int acceptId, NetworkDevice device, boolean halfRestriction) - { - int uniqueNotificationId = ApplicationHelper.getUniqueNumber(); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); - Intent acceptIntent = new Intent(mContext, CommunicationService.class); - Intent dialogIntent = new Intent(mContext, DialogEventReceiver.class); - - acceptIntent.setAction(CommunicationService.ACTION_FILE_TRANSFER_ACCEPT); - acceptIntent.putExtra(CommunicationService.EXTRA_ACCEPT_ID, acceptId); - acceptIntent.putExtra(CommunicationService.EXTRA_DEVICE_IP, device.ip); - acceptIntent.putExtra(EXTRA_NOTIFICATION_ID, uniqueNotificationId); - - Intent rejectIntent = ((Intent) acceptIntent.clone()); - rejectIntent.setAction(CommunicationService.ACTION_FILE_TRANSFER_REJECT); - - int acceptText; - int rejectText; - - if (halfRestriction) - { - acceptIntent.putExtra(CommunicationService.EXTRA_HALF_RESTRICT, true); - rejectIntent.putExtra(CommunicationService.EXTRA_HALF_RESTRICT, true); - - acceptText = R.string.receive_restricted; - rejectText = R.string.reject_restricted; - } - else - { - acceptText = R.string.accept; - rejectText = R.string.reject; - } - - PendingIntent positiveIntent = PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), acceptIntent, 0); - PendingIntent negativeIntent = PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), rejectIntent, 0); - - dialogIntent.setAction(DialogEventReceiver.ACTION_DIALOG); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_TITLE, mContext.getString(R.string.receive_the_file_que)); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_MESSAGE, mContext.getString(R.string.multi_transfer_que, numberOfFiles)); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_POSITIVE_INTENT, positiveIntent); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_NEGATIVE_INTENT, negativeIntent); - - builder.setSmallIcon(android.R.drawable.stat_sys_download_done) - .setContentTitle(mContext.getString(R.string.receive_the_file_que)) - .setContentText(mContext.getString(R.string.multi_transfer_que, numberOfFiles)) - .setContentInfo(device.user) - .setContentIntent(PendingIntent.getBroadcast(mContext, ApplicationHelper.getUniqueNumber(), dialogIntent, 0)) - .setDefaults(this.getNotificationDefaults()) - .setDeleteIntent(negativeIntent) - .addAction(android.R.drawable.ic_menu_send, mContext.getString(acceptText), positiveIntent) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, mContext.getString(rejectText), negativeIntent) - .setTicker(mContext.getString(R.string.receive_the_file_que)); - - mManager.notify(uniqueNotificationId, builder.build()); - } - - public Notification notifyServiceStarted() - { - NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); - - builder.setSmallIcon(R.mipmap.p2p) - .setContentTitle(mContext.getString(R.string.app_name)) - .setContentText(mContext.getString(R.string.ongoing)) - .setContentIntent(PendingIntent.getActivity(mContext, ApplicationHelper.getUniqueNumber(), new Intent(mContext, TrebleShotActivity.class), 0)) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, mContext.getString(R.string.stop_service), PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), new Intent(mContext, CommunicationService.class).setAction(CommunicationService.ACTION_STOP_SERVICE), 0)) - .addAction(android.R.drawable.ic_menu_revert, mContext.getString(R.string.lock), PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), new Intent(mContext, CommunicationService.class).setAction(CommunicationService.ACTION_STOP_SERVICE).putExtra(CommunicationService.EXTRA_SERVICE_LOCK_REQUEST, true), 0)); - - return builder.build(); - } - - public NotificationCompat.Builder notifyFileSending(AwaitedFileSender sender, NetworkDevice device, int progress) - { - NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); - Intent cancelIntent = new Intent(mContext, ClientService.class); - Intent dialogIntent = new Intent(mContext, DialogEventReceiver.class); - - cancelIntent.setAction(ClientService.ACTION_CANCEL_SENDING); - cancelIntent.putExtra(CommunicationService.EXTRA_REQUEST_ID, sender.requestId); - cancelIntent.putExtra(EXTRA_NOTIFICATION_ID, sender.requestId); - - PendingIntent positiveIntent = PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), cancelIntent, 0); - - dialogIntent.setAction(DialogEventReceiver.ACTION_DIALOG); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_TITLE, mContext.getString(R.string.cancel_sending)); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_MESSAGE, sender.file.getName()); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_POSITIVE_INTENT, positiveIntent); - - builder.setSmallIcon(android.R.drawable.stat_sys_upload) - .setContentTitle(sender.file.getName()) - .setContentText(mContext.getString(R.string.sending_msg)) - .setContentInfo(device.user) - .setContentIntent(PendingIntent.getBroadcast(mContext, ApplicationHelper.getUniqueNumber(), dialogIntent, 0)) - .setProgress(100, progress, false) - .setOngoing(true) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, mContext.getString(R.string.cancel_sending), PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), cancelIntent, 0)); - - Log.d(TAG, "sending(): requestId = " + sender.requestId); - - notify(sender.requestId, builder.build()); - - return builder; - } - - public NotificationCompat.Builder notifyFileReceiving(AwaitedFileReceiver receiver, NetworkDevice device, int progress) - { - NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); - Intent cancelIntent = new Intent(mContext, ServerService.class); - Intent dialogIntent = new Intent(mContext, DialogEventReceiver.class); - - cancelIntent.setAction(ServerService.ACTION_CANCEL_RECEIVING); - cancelIntent.putExtra(CommunicationService.EXTRA_DEVICE_IP, receiver.ip); - cancelIntent.putExtra(CommunicationService.EXTRA_REQUEST_ID, receiver.requestId); - cancelIntent.putExtra(CommunicationService.EXTRA_ACCEPT_ID, receiver.acceptId); - cancelIntent.putExtra(EXTRA_NOTIFICATION_ID, NOTIFICATION_ID_RECEIVING); - - PendingIntent positiveIntent = PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), cancelIntent, 0); - - dialogIntent.setAction(DialogEventReceiver.ACTION_DIALOG); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_TITLE, mContext.getString(R.string.cancel_receiving)); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_MESSAGE, receiver.fileName); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_POSITIVE_INTENT, positiveIntent); - - builder.setSmallIcon(android.R.drawable.stat_sys_download) - .setContentTitle(receiver.fileName) - .setContentText(mContext.getString(R.string.receiving_msg)) - .setContentInfo(device.user) - .setContentIntent(PendingIntent.getBroadcast(mContext, ApplicationHelper.getUniqueNumber(), dialogIntent, 0)) - .setProgress(100, progress, false) - .setOngoing(true) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, mContext.getString(R.string.cancel_receiving), PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), cancelIntent, 0)); - - Log.d(TAG, "receiving(): requestId = " + receiver.requestId); - - notify(NOTIFICATION_ID_RECEIVING, builder.build()); - - return builder; - } - - public void notifyOppositeDevicePing(NetworkDevice device) - { - NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); - - builder.setSmallIcon(android.R.drawable.stat_notify_chat) - .setContentTitle(mContext.getString(R.string.poke_notify)) - .setContentText(mContext.getString(R.string.sent_signal_msg)) - .setContentInfo(device.user) - .setAutoCancel(true) - .setContentIntent(PendingIntent.getActivity(mContext, ApplicationHelper.getUniqueNumber(), new Intent(mContext, TrebleShotActivity.class), 0)) - .setDefaults(this.getNotificationDefaults()) - .setTicker(mContext.getString(R.string.poke_notify)); - - mManager.notify(NOTIFICATION_OPPOSITE_DEVICE_PING, builder.build()); - } - - public void notifyFileReceived(AwaitedFileReceiver receiver, File file, NetworkDevice device) - { - Notification.Builder builder = new Notification.Builder(mContext); - Intent openIntent = new Intent(Intent.ACTION_VIEW); - - openIntent.setDataAndType(Uri.fromFile(file), FileUtils.getFileContentType(file.getAbsolutePath())); - - builder.setSmallIcon(android.R.drawable.stat_sys_download_done) - .setContentTitle(receiver.fileName) - .setContentText(mContext.getString(R.string.file_received_msg)) - .setContentInfo(device.user) - .setAutoCancel(true) - .setContentIntent(PendingIntent.getActivity(mContext, ApplicationHelper.getUniqueNumber(), openIntent, 0)); - - notify(NOTIFICATION_ID_RECEIVED, builder.build()); - } - - public void notifyFileReceivedMulti(int numberOfFiles) - { - Notification.Builder builder = new Notification.Builder(mContext); - Intent openIntent = new Intent(mContext, TrebleShotActivity.class).setAction(TrebleShotActivity.OPEN_RECEIVED_FILES_ACTION); - - builder.setSmallIcon(android.R.drawable.stat_sys_download_done) - .setContentTitle(mContext.getString(R.string.multiple_receive)) - .setContentText(mContext.getString(R.string.multiple_receive_done_summary, numberOfFiles)) - .setAutoCancel(true) - .setContentIntent(PendingIntent.getActivity(mContext, ApplicationHelper.getUniqueNumber(), openIntent, 0)); - - mManager.notify(NOTIFICATION_ID_RECEIVED, builder.build()); - } - - public void notifyReceiveError(String fileName) - { - Notification.Builder builder = new Notification.Builder(mContext); - Intent openIntent = new Intent(mContext, TrebleShotActivity.class).setAction(TrebleShotActivity.OPEN_RECEIVED_FILES_ACTION); - - builder.setSmallIcon(android.R.drawable.stat_notify_error) - .setContentTitle(mContext.getString(R.string.error)) - .setContentText(mContext.getString(R.string.file_receiving_error_msg, fileName)) - .setAutoCancel(true) - .setContentIntent(PendingIntent.getActivity(mContext, ApplicationHelper.getUniqueNumber(), openIntent, 0)); - - notify(NOTIFICATION_ID_RECEIVE_ERROR, builder.build()); - } - - public void notifyClipboardRequest(String clientIp, CharSequence text) - { - NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); - int uniqueNotificationId = ApplicationHelper.getUniqueNumber(); - - Intent acceptIntent = new Intent(mContext, CommunicationService.class); - Intent dialogIntent = new Intent(mContext, DialogEventReceiver.class); - - acceptIntent.setAction(CommunicationService.ACTION_CLIPBOARD); - acceptIntent.putExtra(CommunicationService.EXTRA_DEVICE_IP, clientIp); - acceptIntent.putExtra(EXTRA_NOTIFICATION_ID, uniqueNotificationId); - - Intent rejectIntent = ((Intent) acceptIntent.clone()); - - acceptIntent.putExtra(CommunicationService.EXTRA_CLIPBOARD_ACCEPTED, true); - rejectIntent.putExtra(CommunicationService.EXTRA_CLIPBOARD_ACCEPTED, false); - - PendingIntent positiveIntent = PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), acceptIntent, 0); - PendingIntent negativeIntent = PendingIntent.getService(mContext, ApplicationHelper.getUniqueNumber(), rejectIntent, 0); - - dialogIntent.setAction(DialogEventReceiver.ACTION_DIALOG); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_TITLE, mContext.getString(R.string.copy_to_clipboard_question)); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_MESSAGE, text); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_POSITIVE_INTENT, positiveIntent); - dialogIntent.putExtra(DialogEventReceiver.EXTRA_NEGATIVE_INTENT, negativeIntent); - - Log.i(TAG, "clientIp = " + clientIp); - - builder.setSmallIcon(android.R.drawable.stat_sys_download_done) - .setContentTitle(mContext.getString(R.string.received_text)) - .setContentText(mContext.getString(R.string.copy_to_clipboard_question)) - .setContentInfo(clientIp) - .setContentIntent(PendingIntent.getBroadcast(mContext, ApplicationHelper.getUniqueNumber(), dialogIntent, 0)) - .setDefaults(this.getNotificationDefaults()) - .setDeleteIntent(negativeIntent) - .addAction(android.R.drawable.ic_menu_send, mContext.getString(R.string.accept), positiveIntent) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, mContext.getString(R.string.reject), negativeIntent) - .setTicker(mContext.getString(R.string.received_text_info)); - - mManager.notify(uniqueNotificationId, builder.build()); - } - - - public int getNotificationDefaults() - { - int makeSound = (mPreferences.getBoolean("notification_sound", false)) ? NotificationCompat.DEFAULT_SOUND : 0; - int vibrate = (mPreferences.getBoolean("notification_vibrate", false)) ? NotificationCompat.DEFAULT_VIBRATE : 0; - int light = (mPreferences.getBoolean("notification_light", false)) ? NotificationCompat.DEFAULT_LIGHTS : 0; - - return makeSound | vibrate | light; - } - - public void notify(int notificationId, Notification notification) - { - mManager.notify(notificationId, notification); - } - - public void makeToast(String toastText) - { - Toast.makeText(mContext, toastText, Toast.LENGTH_SHORT).show(); - } - - public void makeToast(int toastTextRes) - { - Toast.makeText(mContext, toastTextRes, Toast.LENGTH_SHORT).show(); - } - - public void makeToast(String text, int lenght) - { - Toast.makeText(mContext, text, lenght).show(); - } - - public void makeToast(int textRes, int lenght) - { - Toast.makeText(mContext, textRes, lenght).show(); - } - - public void cancelNotification(int notificationId) - { - mManager.cancel(notificationId); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/ResponseHandler.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/ResponseHandler.java deleted file mode 100644 index f31ecdd47..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/ResponseHandler.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.genonbeta.TrebleShot.helper; - -import com.genonbeta.CoolSocket.CoolCommunication; -import com.genonbeta.TrebleShot.config.AppConfig; - -public abstract class ResponseHandler extends CoolCommunication.Messenger.ResponseHandler -{ - @Override - public void onConfigure(CoolCommunication.Messenger.Process process) - { - process.setSocketTimeout(AppConfig.DEFAULT_SOCKET_TIMEOUT); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/TrebleShotHelper.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/TrebleShotHelper.java deleted file mode 100644 index bdbd0e1f6..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/TrebleShotHelper.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.genonbeta.TrebleShot.helper; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; - -import java.util.ArrayList; - -public class TrebleShotHelper extends SQLiteOpenHelper -{ - public final static String DATABASE_NAME = "OptimizedData"; - - public final static String TABLE_DEVICE = "device"; - public final static String TABLE_SENDER = "sender"; - public final static String TABLE_MULTIPLESENDER = "multipleSender"; - public final static String TABLE_RECEIVER = "receiver"; - - public final static String DEVICES_IP = "ip"; - public final static String DEVICES_BRAND = "brand"; - public final static String DEVICES_MODEL = "model"; - public final static String DEVICES_USER = "user"; - public final static String DEVICES_ISRESTRICTED = "isRestricted"; - public final static String DEVICES_ISLOCAL = "isLocalAddress"; - - public final static String SENDER_IP = "ip"; - public final static String SENDER_PORT = "port"; - public final static String SENDER_REQUESTID = "requestId"; - public final static String SENDER_FILE = "file"; - public final static String SENDER_ISCANCELLED = "isCancelled"; - - private Context mContext; - - public TrebleShotHelper(Context context) - { - super(context, TrebleShotHelper.DATABASE_NAME, null, 1); - - this.mContext = context; - } - - @Override - public void onCreate(SQLiteDatabase p1) - { - } - - @Override - public void onUpgrade(SQLiteDatabase p1, int p2, int p3) - { - - } - - public boolean isDeviceExist(String ip) - { - return false; - } - - public boolean putDevice(NetworkDevice device) - { - return false; - } - - public boolean removeDevice(String ip) - { - return false; - } - - public NetworkDevice getDevice(String ip) - { - return null; - } - - public ArrayList getDeviceList() - { - return null; - } - - public boolean removeAllDevices() - { - return false; - } - - public boolean putSender() - { - return false; - } - - public boolean putReceiver() - { - return false; - } - - public boolean removeSender() - { - return false; - } - - public boolean removeReceiver() - { - return false; - } - - public boolean isSenderExist() - { - return false; - } - - public boolean isReceiverExist() - { - return false; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/helper/preference/StorageSetterPreference.java b/app/src/main/java/com/genonbeta/TrebleShot/helper/preference/StorageSetterPreference.java deleted file mode 100644 index 077f71484..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/helper/preference/StorageSetterPreference.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.genonbeta.TrebleShot.helper.preference; - -import android.preference.EditTextPreference; -import android.text.Editable; -import android.view.View; -import android.widget.EditText; -import android.widget.Toast; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; - -import java.io.File; - -public class StorageSetterPreference extends EditTextPreference -{ - public StorageSetterPreference(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes) - { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public StorageSetterPreference(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr) - { - super(context, attrs, defStyleAttr, defStyleAttr); - } - - public StorageSetterPreference(android.content.Context context, android.util.AttributeSet attrs) - { - super(context, attrs); - } - - public StorageSetterPreference(android.content.Context context) - { - super(context); - } - - @Override - protected void onAddEditTextToDialogView(View dialogView, EditText editText) - { - super.onAddEditTextToDialogView(dialogView, editText); - - Editable editor = editText.getText(); - - if (editor.toString().length() == 0) - { - editor.append(ApplicationHelper.getApplicationDirectory(getContext()).getAbsolutePath()); - } - } - - @Override - protected void onDialogClosed(boolean positiveResult) - { - super.onDialogClosed(positiveResult); - - Editable editable = getEditText().getText(); - File appDir = ApplicationHelper.getApplicationDirectory(getContext()); - - if (!editable.toString().equals(appDir.getAbsolutePath()) && editable.toString().length() > 0 && positiveResult) - Toast.makeText(getContext(), R.string.error_rejected_path, Toast.LENGTH_LONG).show(); - else if (!appDir.isDirectory()) - Toast.makeText(getContext(), getContext().getString(R.string.error_default_path, appDir.getAbsolutePath()), Toast.LENGTH_LONG).show(); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/receiver/DeviceScannerProvider.java b/app/src/main/java/com/genonbeta/TrebleShot/receiver/DeviceScannerProvider.java deleted file mode 100644 index 2bcca0ead..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/receiver/DeviceScannerProvider.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.genonbeta.TrebleShot.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.widget.Toast; - -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.config.AppConfig; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; -import com.genonbeta.TrebleShot.helper.NetworkDevice; -import com.genonbeta.TrebleShot.helper.NetworkDeviceInfoLoader; -import com.genonbeta.core.util.NetworkDeviceScanner; -import com.genonbeta.core.util.NetworkUtils; - -import java.net.InetAddress; -import java.util.ArrayList; - -public class DeviceScannerProvider extends BroadcastReceiver implements NetworkDeviceScanner.ScannerHandler, NetworkDeviceInfoLoader.OnInfoAvailableListener -{ - public static final String ACTION_SCAN_DEVICES = "genonbeta.intent.action.SCAN_DEVICES"; - public static final String ACTION_SCAN_STARTED = "genonbeta.intent.action.SCAN_STARTED"; - public static final String ACTION_DEVICE_FOUND = "genonbeta.intent.action.DEVICE_FOUND"; - public static final String ACTION_DEVICE_SCAN_COMPLETED = "genonbeta.intent.action.DEVICE_SCAN_COMPLETED"; - public static final String ACTION_ADD_IP = "genonbeta.intent.action.ADD_IP"; - - public static final String EXTRA_DEVICE_IP = "genonbeta.intent.extra.DEVICE_IP"; - public static final String EXTRA_NETWORK_INTERFACE_PREFIX = "genonbeta.intent.extra.NETWORK_INTERFACE_PREFIX"; - public static final String EXTRA_SCAN_STATUS = "genonbeta.intent.extra.SCAN_STATUS"; - - public static final String STATUS_OK = "genonbeta.intent.status.OK"; - public static final String STATUS_NO_NETWORK_INTERFACE = "genonbeta.intent.status.NO_NETWORK_INTERFACE"; - - private Context mContext; - private NetworkDeviceInfoLoader mInfoLoader = new NetworkDeviceInfoLoader(this); - private SharedPreferences mPreferences; - - @Override - public void onReceive(Context context, Intent intent) - { - this.mContext = context; - - mPreferences = PreferenceManager.getDefaultSharedPreferences(this.mContext); - - if (ACTION_SCAN_DEVICES.equals(intent.getAction())) - { - if (ApplicationHelper.getNetworkDeviceScanner().isScannerAvaiable()) - { - ArrayList list = NetworkUtils.getInterfacesWithOnlyIp(true, AppConfig.DEFAULT_DISABLED_INTERFACES); - - for (String ip : list) - { - if (!ApplicationHelper.getDeviceList().containsKey(ip)) - { - NetworkDevice device = new NetworkDevice(ip, null, null, null); - device.isLocalAddress = true; - - ApplicationHelper.getDeviceList().put(ip, device); - } - } - - context.sendBroadcast(new Intent(ACTION_SCAN_STARTED).putExtra(EXTRA_SCAN_STATUS, (ApplicationHelper.getNetworkDeviceScanner().scan(list, this)) ? STATUS_OK : STATUS_NO_NETWORK_INTERFACE)); - } - else - Toast.makeText(context, R.string.concurrent_caution_scan, Toast.LENGTH_SHORT).show(); - } - else if (ACTION_ADD_IP.equals(intent.getAction()) && intent.hasExtra(EXTRA_DEVICE_IP)) - { - mInfoLoader.startLoading(context, intent.getStringExtra(EXTRA_DEVICE_IP), true); - } - } - - @Override - public void onDeviceFound(InetAddress address) - { - if (ApplicationHelper.getDeviceList().containsKey(address.getHostAddress())) - { - NetworkDevice device = ApplicationHelper.getDeviceList().get(address.getHostAddress()); - - if (device.isLocalAddress && !mPreferences.getBoolean("developer_mode", false)) - return; - } - - mInfoLoader.startLoading(this.mContext, address.getHostAddress(), mPreferences.getBoolean("developer_mode", false)); - } - - @Override - public void onInfoAvailable(NetworkDevice device) - { - if (ApplicationHelper.getDeviceList().containsKey(device.ip)) - { - NetworkDevice oldDevice = ApplicationHelper.getDeviceList().get(device.ip); - - oldDevice.brand = device.brand; - oldDevice.model = device.model; - oldDevice.user = device.user; - } - else - ApplicationHelper.getDeviceList().put(device.ip, device); - - mContext.sendBroadcast(new Intent(ACTION_DEVICE_FOUND).putExtra(EXTRA_DEVICE_IP, device.ip)); - } - - @Override - public void onThreadsCompleted() - { - mContext.sendBroadcast(new Intent(ACTION_DEVICE_SCAN_COMPLETED)); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/receiver/DialogEventReceiver.java b/app/src/main/java/com/genonbeta/TrebleShot/receiver/DialogEventReceiver.java deleted file mode 100644 index 8370dfb26..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/receiver/DialogEventReceiver.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.genonbeta.TrebleShot.receiver; - -import android.app.Dialog; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.support.v7.app.AlertDialog; -import android.view.WindowManager; - -import com.genonbeta.TrebleShot.R; - -public class DialogEventReceiver extends BroadcastReceiver -{ - public final static String ACTION_DIALOG = "com.genonbeta.TrebleShot.action.makeDialog"; - - public final static String EXTRA_TITLE = "title"; - public final static String EXTRA_MESSAGE = "message"; - public final static String EXTRA_POSITIVE_INTENT = "positive"; - public final static String EXTRA_NEGATIVE_INTENT = "negative"; - - @Override - public void onReceive(Context context, Intent intent) - { - if (ACTION_DIALOG.equals(intent.getAction())) - this.showDialog(context, intent.getStringExtra(EXTRA_TITLE), intent.getStringExtra(EXTRA_MESSAGE), (PendingIntent) intent.getParcelableExtra(EXTRA_POSITIVE_INTENT), (PendingIntent) intent.getParcelableExtra(EXTRA_NEGATIVE_INTENT)); - } - - public void showDialog(Context context, String title, String message, final PendingIntent accept, final PendingIntent reject) - { - context.setTheme(R.style.AppTheme); - - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); - - if (title != null) - dialogBuilder.setTitle(title); - - if (message != null) - dialogBuilder.setMessage(message); - - if (accept != null) - dialogBuilder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface p1, int p2) - { - try - { - accept.send(); - } catch (PendingIntent.CanceledException e) - { - e.printStackTrace(); - } - } - } - ); - - if (reject != null) - dialogBuilder.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface p1, int p2) - { - try - { - reject.send(); - } catch (PendingIntent.CanceledException e) - { - e.printStackTrace(); - } - } - } - ); - else - dialogBuilder.setNegativeButton(R.string.close, null); - - Dialog dialog = dialogBuilder.create(); - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - dialog.show(); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/receiver/NetworkStatusReceiver.java b/app/src/main/java/com/genonbeta/TrebleShot/receiver/NetworkStatusReceiver.java deleted file mode 100644 index da12d1103..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/receiver/NetworkStatusReceiver.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.genonbeta.TrebleShot.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.NetworkInfo; -import android.preference.PreferenceManager; - -import com.genonbeta.TrebleShot.service.CommunicationService; - -public class NetworkStatusReceiver extends BroadcastReceiver -{ - @Override - public void onReceive(Context context, Intent intent) - { - if (intent.hasExtra("networkInfo")) - evaluateTheCondition((NetworkInfo) intent.getParcelableExtra("networkInfo"), context); - } - - protected void evaluateTheCondition(NetworkInfo info, Context context) - { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - - if (!preferences.getBoolean("serviceLock", false) && preferences.getBoolean("allow_autoconnect", true) && info.isConnected()) - context.startService(new Intent(context, CommunicationService.class)); - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/service/ClientService.java b/app/src/main/java/com/genonbeta/TrebleShot/service/ClientService.java deleted file mode 100644 index d9de56e0f..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/service/ClientService.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.genonbeta.TrebleShot.service; - -import android.app.Service; -import android.content.Intent; -import android.net.wifi.WifiManager; -import android.os.IBinder; -import android.os.Looper; -import android.util.Log; - -import com.genonbeta.CoolSocket.CoolTransfer; -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.config.AppConfig; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; -import com.genonbeta.TrebleShot.helper.AwaitedFileSender; -import com.genonbeta.TrebleShot.helper.NetworkDevice; -import com.genonbeta.TrebleShot.helper.NotificationPublisher; - -import java.io.File; -import java.net.Socket; - -public class ClientService extends Service -{ - public static final String TAG = "ClientService"; - - public final static String ACTION_SEND = "com.genonbeta.TrebleShot.client.ACTION_SEND"; - public final static String ACTION_CANCEL_SENDING = "com.genonbeta.TrebleShot.client.CANCEL_SENDING"; - - private NotificationPublisher mPublisher; - private WifiManager.WifiLock mWifiLock; - private Send mSend = new Send(); - - @Override - public IBinder onBind(Intent intent) - { - return null; - } - - @Override - public void onCreate() - { - super.onCreate(); - - mWifiLock = ((WifiManager) getSystemService(Service.WIFI_SERVICE)).createWifiLock(TAG); - mPublisher = new NotificationPublisher(this); - - mSend.setNotifyDelay(2000); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) - { - super.onStartCommand(intent, flags, startId); - - if (intent != null) - { - if (ACTION_SEND.equals(intent.getAction()) && intent.hasExtra(CommunicationService.EXTRA_REQUEST_ID)) - { - try - { - int requestId = intent.getIntExtra(CommunicationService.EXTRA_REQUEST_ID, -1); - - if (ApplicationHelper.getSenders().containsKey(requestId)) - { - AwaitedFileSender awaitedSender = ApplicationHelper.getSenders().get(requestId); - - mSend.send(awaitedSender.ip, awaitedSender.port, awaitedSender.file, AppConfig.DEFAULT_BUFFER_SIZE, awaitedSender); - - Log.d(TAG, "Send intent is ok"); - } - - Log.d(TAG, "Send intent is received"); - } catch (Exception e) - { - mPublisher.makeToast(getString(R.string.file_sending_error_msg, getString(R.string.communication_problem))); - } - } - else if (ACTION_CANCEL_SENDING.equals(intent.getAction()) && intent.hasExtra(CommunicationService.EXTRA_REQUEST_ID)) - { - int requestId = intent.getIntExtra(CommunicationService.EXTRA_REQUEST_ID, -1); - - Log.d(TAG, "Sender stop request is received for id = " + requestId); - - if (ApplicationHelper.getSenders().containsKey(requestId)) - { - AwaitedFileSender sender = ApplicationHelper.getSenders().get(requestId); - sender.isCancelled = true; - } - else - mPublisher.cancelNotification(intent.getIntExtra(NotificationPublisher.EXTRA_NOTIFICATION_ID, -1)); - } - } - - return START_STICKY; - } - - private class Send extends CoolTransfer.Send - { - @Override - public boolean onStart(String serverIp, int port, File file, AwaitedFileSender extra) - { - Looper.prepare(); - mWifiLock.acquire(); - - NetworkDevice device = ApplicationHelper.getDeviceList().get(extra.ip); - mPublisher.notifyFileSending(extra, device, 0); - - return true; - } - - @Override - public void onStop(String serverIp, int port, File file, AwaitedFileSender extra) - { - super.onStop(serverIp, port, file, extra); - - mWifiLock.release(); - mPublisher.cancelNotification(extra.requestId); - - ApplicationHelper.removeSender(extra); - } - - @Override - public void onError(String serverIp, int port, File file, Exception error, AwaitedFileSender extra) - { - mPublisher.makeToast(getString(R.string.file_sending_error_msg, "")); - } - - @Override - public void onNotify(Socket socket, String serverIp, int port, File file, int percent, AwaitedFileSender extra) - { - NetworkDevice device = ApplicationHelper.getDeviceList().get(extra.ip); - mPublisher.notifyFileSending(extra, device, percent); - } - - @Override - public void onTransferCompleted(String serverIp, int port, File file, AwaitedFileSender extra) - { - mPublisher.makeToast(getString(((!extra.isCancelled) ? R.string.file_sent_msg : R.string.file_send_cancelled_msg), extra.file.getName())); - } - - @Override - public boolean onBreakRequest(String serverIp, int port, File file, AwaitedFileSender extra) - { - return extra.isCancelled; - } - - @Override - public void onSocketReady(Socket socket, String serverIp, int port, File file, AwaitedFileSender extra) - { - - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/service/CommunicationService.java b/app/src/main/java/com/genonbeta/TrebleShot/service/CommunicationService.java deleted file mode 100644 index c33979533..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/service/CommunicationService.java +++ /dev/null @@ -1,453 +0,0 @@ -package com.genonbeta.TrebleShot.service; - -import android.app.Service; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.os.Build; -import android.os.IBinder; -import android.preference.PreferenceManager; -import android.util.Log; -import android.widget.Toast; - -import com.genonbeta.CoolSocket.CoolCommunication; -import com.genonbeta.CoolSocket.CoolJsonCommunication; -import com.genonbeta.TrebleShot.R; -import com.genonbeta.TrebleShot.config.AppConfig; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; -import com.genonbeta.TrebleShot.helper.AwaitedFileReceiver; -import com.genonbeta.TrebleShot.helper.AwaitedFileSender; -import com.genonbeta.TrebleShot.helper.JsonResponseHandler; -import com.genonbeta.TrebleShot.helper.NetworkDevice; -import com.genonbeta.TrebleShot.helper.NotificationPublisher; -import com.genonbeta.TrebleShot.receiver.DeviceScannerProvider; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.net.Socket; - -public class CommunicationService extends Service -{ - public static final String TAG = "CommunicationService"; - - public static final String ACTION_FILE_TRANSFER_ACCEPT = "com.genonbeta.TrebleShot.FILE_TRANSFER_ACCEPT"; - public static final String ACTION_FILE_TRANSFER_REJECT = "com.genonbeta.TrebleShot.FILE_TRANSFER_REJECT"; - public static final String ACTION_STOP_SERVICE = "com.genonbeta.TrebleShot.STOP_SERVICE"; - public static final String ACTION_ALLOW_IP = "com.genonbeta.TrebleShot.ALLOW_IP"; - public static final String ACTION_REJECT_IP = "com.genonbeta.TrebleShot.REJECT_IP"; - public static final String ACTION_CLIPBOARD = "com.genonbeta.TrebleShot.CLIPBOARD"; - - public static final String EXTRA_DEVICE_IP = "extraDeviceIp"; - public static final String EXTRA_REQUEST_ID = "extraRequestId"; - public static final String EXTRA_ACCEPT_ID = "extraAcceptId"; - public static final String EXTRA_SERVICE_LOCK_REQUEST = "extraServiceStartLock"; - public static final String EXTRA_HALF_RESTRICT = "extraHalfRestrict"; - public static final String EXTRA_CLIPBOARD_ACCEPTED = "extraClipboardAccepted"; - - private CommunicationServer mCommunicationServer = new CommunicationServer(); - private NotificationPublisher mPublisher; - private SharedPreferences mPreferences; - private String mReceivedClipboardIndex; - - @Override - public IBinder onBind(Intent intent) - { - return null; - } - - @Override - public void onCreate() - { - super.onCreate(); - - if (!mCommunicationServer.start()) - stopSelf(); - - mPublisher = new NotificationPublisher(this); - mPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - if (mPreferences.getBoolean("notify_com_server_started", false)) - startForeground(NotificationPublisher.NOTIFICATION_SERVICE_STARTED, mPublisher.notifyServiceStarted()); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) - { - super.onStartCommand(intent, flags, startId); - - if (intent != null) - Log.d(TAG, "onStart() : action = " + intent.getAction()); - - if (intent != null) - { - if (ACTION_STOP_SERVICE.equals(intent.getAction())) - { - if (intent.getBooleanExtra(EXTRA_SERVICE_LOCK_REQUEST, false)) - { - mPreferences.edit().putBoolean("serviceLock", true).apply(); - mPublisher.makeToast(R.string.service_lock_notice, Toast.LENGTH_LONG); - } - - stopSelf(); - } - else if (ACTION_FILE_TRANSFER_ACCEPT.equals(intent.getAction())) - { - final String oppositeIp = intent.getStringExtra(EXTRA_DEVICE_IP); - final int acceptId = intent.getIntExtra(EXTRA_ACCEPT_ID, -1); - final int notificationId = intent.getIntExtra(NotificationPublisher.EXTRA_NOTIFICATION_ID, -1); - - mPublisher.cancelNotification(notificationId); - - Log.d(TAG, "fileTransferAccepted ; ip = " + oppositeIp + " ; acceptId = " + acceptId + "; notificationId = " + notificationId); - - if (ApplicationHelper.getDeviceList().containsKey(oppositeIp)) - ApplicationHelper.getDeviceList().get(oppositeIp).isRestricted = false; - - if (ApplicationHelper.acceptPendingReceivers(acceptId) < 1) - { - mPublisher.makeToast(R.string.something_went_wrong); - - return START_NOT_STICKY; - } - - startService(new Intent(this, ServerService.class).setAction(ServerService.ACTION_CHECK_AVAILABLES)); - - CoolCommunication.Messenger.send(oppositeIp, AppConfig.COMMUNATION_SERVER_PORT, null, - new JsonResponseHandler() - { - @Override - public void onJsonMessage(Socket socket, com.genonbeta.CoolSocket.CoolCommunication.Messenger.Process process, JSONObject json) - { - try - { - json.put("request", "file_transfer_request_accepted"); - json.put("requestId", acceptId); - } catch (JSONException e) - { - e.printStackTrace(); - } - } - } - ); - } - else if (ACTION_FILE_TRANSFER_REJECT.equals(intent.getAction()) && intent.hasExtra(NotificationPublisher.EXTRA_NOTIFICATION_ID)) - { - final String oppositeIp = intent.getStringExtra(EXTRA_DEVICE_IP); - final int acceptId = intent.getIntExtra(EXTRA_ACCEPT_ID, -1); - final int notificationId = intent.getIntExtra(NotificationPublisher.EXTRA_NOTIFICATION_ID, -1); - - mPublisher.cancelNotification(notificationId); - - if (ApplicationHelper.getDeviceList().containsKey(oppositeIp) && !intent.hasExtra(EXTRA_HALF_RESTRICT)) - ApplicationHelper.getDeviceList().get(oppositeIp).isRestricted = false; - - CoolCommunication.Messenger.send(oppositeIp, AppConfig.COMMUNATION_SERVER_PORT, null, - new JsonResponseHandler() - { - @Override - public void onJsonMessage(Socket socket, com.genonbeta.CoolSocket.CoolCommunication.Messenger.Process process, JSONObject json) - { - try - { - JSONArray idList = new JSONArray(); - - json.put("request", "file_transfer_request_rejected"); - - for (AwaitedFileReceiver receiver : ApplicationHelper.getPendingReceiversByAcceptId(acceptId)) - { - idList.put(receiver.requestId); - } - - json.put("requestIds", idList); - } catch (JSONException e) - { - e.printStackTrace(); - } finally - { - ApplicationHelper.removePendingReceivers(acceptId); - } - } - } - ); - } - else if (ACTION_ALLOW_IP.equals(intent.getAction())) - { - String oppositeIp = intent.getStringExtra(EXTRA_DEVICE_IP); - int notificationId = intent.getIntExtra(NotificationPublisher.EXTRA_NOTIFICATION_ID, -1); - - mPublisher.cancelNotification(notificationId); - - if (!ApplicationHelper.getDeviceList().containsKey(oppositeIp)) - return START_NOT_STICKY; - - ApplicationHelper.getDeviceList().get(oppositeIp).isRestricted = false; - } - else if (ACTION_REJECT_IP.equals(intent.getAction())) - { - String oppositeIp = intent.getStringExtra(EXTRA_DEVICE_IP); - int notificationId = intent.getIntExtra(NotificationPublisher.EXTRA_NOTIFICATION_ID, -1); - - mPublisher.cancelNotification(notificationId); - - if (!ApplicationHelper.getDeviceList().containsKey(oppositeIp)) - return START_NOT_STICKY; - - ApplicationHelper.getDeviceList().get(oppositeIp).isRestricted = true; - } - else if (ACTION_CLIPBOARD.equals(intent.getAction()) && intent.hasExtra(EXTRA_CLIPBOARD_ACCEPTED)) - { - String oppositeIp = intent.getStringExtra(EXTRA_DEVICE_IP); - int notificationId = intent.getIntExtra(NotificationPublisher.EXTRA_NOTIFICATION_ID, -1); - - mPublisher.cancelNotification(notificationId); - - if (!ApplicationHelper.getDeviceList().containsKey(oppositeIp)) - return START_NOT_STICKY; - - ApplicationHelper.getDeviceList().get(oppositeIp).isRestricted = false; - - if (intent.getBooleanExtra(EXTRA_CLIPBOARD_ACCEPTED, false)) - { - ((ClipboardManager) getSystemService(CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("receivedText", mReceivedClipboardIndex)); - Toast.makeText(this, R.string.clipboard_text_copied, Toast.LENGTH_SHORT).show(); - } - } - - } - - return START_STICKY; - } - - @Override - public void onDestroy() - { - super.onDestroy(); - - mCommunicationServer.stop(); - stopForeground(true); - } - - public class CommunicationServer extends CoolJsonCommunication - { - public CommunicationServer() - { - super(AppConfig.COMMUNATION_SERVER_PORT); - this.setSocketTimeout(AppConfig.DEFAULT_SOCKET_LARGE_TIMEOUT); - } - - @Override - public void onJsonMessage(Socket socket, JSONObject receivedMessage, JSONObject response, String clientIp) - { - NetworkDevice device = null; - - try - { - if (receivedMessage != null) - Log.d(TAG, "receivedMessage = " + receivedMessage.toString()); - - JSONObject deviceInformation = new JSONObject(); - JSONObject appInfo = new JSONObject(); - boolean result = false; - boolean shouldContinue = true; - boolean halfRestriction = false; - - PackageInfo packageInfo = getPackageManager().getPackageInfo(getApplicationInfo().packageName, 0); - - appInfo.put("versionCode", packageInfo.versionCode); - appInfo.put("versionName", packageInfo.versionName); - - deviceInformation.put("device", Build.DEVICE); - deviceInformation.put("brand", Build.BRAND); - deviceInformation.put("display", Build.DISPLAY); - deviceInformation.put("model", Build.MODEL); - deviceInformation.put("manufacturer", Build.MANUFACTURER); - deviceInformation.put("deviceName", mPreferences.getString("device_name", Build.BOARD)); - - response.put("appInfo", appInfo); - response.put("deviceInfo", deviceInformation); - - if (receivedMessage.has("request") && !receivedMessage.getString("request").equals(null)) - if (!ApplicationHelper.getDeviceList().containsKey(clientIp)) - { - device = new NetworkDevice(clientIp, null, null, null); - device.isRestricted = true; - - ApplicationHelper.getDeviceList().put(clientIp, device); - sendBroadcast(new Intent(DeviceScannerProvider.ACTION_ADD_IP).putExtra(DeviceScannerProvider.EXTRA_DEVICE_IP, clientIp)); - - if (receivedMessage.getString("request").equals("file_transfer_request") || receivedMessage.getString("request").equals("multifile_transfer_request")) - { - shouldContinue = true; - halfRestriction = true; - } - else - mPublisher.notifyConnectionRequest(clientIp); - } - else - { - device = ApplicationHelper.getDeviceList().get(clientIp); - - if (device.isRestricted == true) - shouldContinue = false; - } - - if (shouldContinue && receivedMessage.has("request")) - { - switch (receivedMessage.getString("request")) - { - case ("file_transfer_request"): - if (receivedMessage.has("fileSize") && receivedMessage.has("fileMime") && receivedMessage.has("fileName") && receivedMessage.has("requestId")) - { - long fileSize = receivedMessage.getLong("fileSize"); - String fileName = receivedMessage.getString("fileName"); - String fileMime = receivedMessage.getString("fileMime"); - int requestId = receivedMessage.getInt("requestId"); - - int acceptId = ApplicationHelper.getUniqueNumber(); - - AwaitedFileReceiver receiver = new AwaitedFileReceiver(device.ip, requestId, acceptId, fileName, fileSize, fileMime); - ApplicationHelper.getPendingReceivers().offer(receiver); - - device.isRestricted = true; - - mPublisher.notifyTransferRequest(acceptId, device, receiver, halfRestriction); - - result = true; - } - break; - case ("multifile_transfer_request"): - if (receivedMessage.has("filesJson")) - { - String jsonIndex = receivedMessage.getString("filesJson"); - - Log.d(TAG, jsonIndex); - - JSONArray jsonArray = new JSONArray(jsonIndex); - - int count = 0; - int acceptId = ApplicationHelper.getUniqueNumber(); - - Log.d(TAG, "First PendingReceiver count " + ApplicationHelper.getPendingReceivers().size()); - - for (int i = 0; i < jsonArray.length(); i++) - { - if (!(jsonArray.get(i) instanceof JSONObject)) - continue; - - JSONObject requestIndex = jsonArray.getJSONObject(i); - - if (requestIndex != null && requestIndex.has("fileName") && requestIndex.has("fileSize") && requestIndex.has("fileMime") && requestIndex.has("requestId")) - { - count++; - AwaitedFileReceiver receiver = new AwaitedFileReceiver(clientIp, requestIndex.getInt("requestId"), acceptId, requestIndex.getString("fileName"), requestIndex.getLong("fileSize"), requestIndex.getString("fileMime")); - - receiver.acceptId = acceptId; - - Log.d(TAG, "Received acceptId test they must be the same = " + receiver.acceptId); - - ApplicationHelper.getPendingReceivers().offer(receiver); - } - } - - Log.d(TAG, "Last PendingReceiver count " + ApplicationHelper.getPendingReceivers().size()); - - if (count > 0) - { - mPublisher.notifyMultiTransferRequest(count, acceptId, device, halfRestriction); - result = true; - device.isRestricted = true; - } - } - break; - case ("file_transfer_request_accepted"): - if (receivedMessage.has("requestId")) - { - int requestId = receivedMessage.getInt("requestId"); - - if (ApplicationHelper.getSenders().containsKey(requestId)) - { - AwaitedFileSender sender = ApplicationHelper.getSenders().get(requestId); - - if (sender.ip.equals(clientIp)) - { - result = true; - } - } - } - break; - case ("file_transfer_request_rejected"): - if (receivedMessage.has("requestIds")) - { - JSONArray requestIds = receivedMessage.getJSONArray("requestIds"); - - for (int i = 0; i < requestIds.length(); i++) - { - int requestId = requestIds.getInt(i); - - if (ApplicationHelper.getSenders().containsKey(requestId)) - { - AwaitedFileSender sender = ApplicationHelper.getSenders().get(requestId); - - if (sender.ip.equals(clientIp)) - { - ApplicationHelper.getSenders().remove(requestId); - result = true; - } - } - } - } - break; - case ("file_transfer_notify_server_ready"): - if (receivedMessage.has("requestId") && receivedMessage.has("socketPort")) - { - int requestId = receivedMessage.getInt("requestId"); - int socketPort = receivedMessage.getInt("socketPort"); - - if (ApplicationHelper.getSenders().containsKey(requestId)) - { - AwaitedFileSender sender = ApplicationHelper.getSenders().get(requestId); - - sender.setPort(socketPort); - startService(new Intent(getApplicationContext(), ClientService.class).setAction(ClientService.ACTION_SEND).putExtra(EXTRA_REQUEST_ID, requestId)); - - result = true; - } - } - break; - case ("request_clipboard"): - if (receivedMessage.has("clipboardText")) - { - mReceivedClipboardIndex = receivedMessage.getString("clipboardText"); - mPublisher.notifyClipboardRequest(clientIp, mReceivedClipboardIndex); - - device.isRestricted = true; - - result = true; - } - break; - case ("poke_the_device"): - if (mPreferences.getBoolean("allow_poke", true)) - { - mPublisher.notifyOppositeDevicePing(device); - result = true; - } - } - } - - response.put("result", result); - } catch (Exception e) - { - e.printStackTrace(); - } - } - - @Override - protected void onError(Exception exception) - { - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/service/ServerService.java b/app/src/main/java/com/genonbeta/TrebleShot/service/ServerService.java deleted file mode 100644 index 16c9acff0..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/service/ServerService.java +++ /dev/null @@ -1,264 +0,0 @@ -package com.genonbeta.TrebleShot.service; - -import android.app.Service; -import android.content.Intent; -import android.net.wifi.WifiManager; -import android.os.IBinder; -import android.util.Log; - -import com.genonbeta.CoolSocket.CoolCommunication; -import com.genonbeta.CoolSocket.CoolTransfer; -import com.genonbeta.TrebleShot.config.AppConfig; -import com.genonbeta.TrebleShot.helper.ApplicationHelper; -import com.genonbeta.TrebleShot.helper.AwaitedFileReceiver; -import com.genonbeta.TrebleShot.helper.FileUtils; -import com.genonbeta.TrebleShot.helper.JsonResponseHandler; -import com.genonbeta.TrebleShot.helper.NetworkDevice; -import com.genonbeta.TrebleShot.helper.NotificationPublisher; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; - -public class ServerService extends Service -{ - public static final String TAG = "ServerService"; - - public final static String ACTION_CHECK_AVAILABLES = "com.genonbeta.TrebleShot.server.OPEN_NEW_SERVER_SOCKET"; - public final static String ACTION_CANCEL_RECEIVING = "com.genonbeta.TrebleShot.server.CANCEL_RECEIVING"; - public final static String ACTION_FILE_LIST_CHANGED = "com.genonbeta.TrebleShot.action.FILE_LIST_CHANGED"; - - private ServerRunnable mRunnable = new ServerRunnable(); - private boolean mIsBreakRequested = false; - - private NotificationPublisher mPublisher; - private WifiManager.WifiLock mWifiLock; - private Receive mReceive = new Receive(); - private Thread mThread; - - @Override - public IBinder onBind(Intent p1) - { - return null; - } - - @Override - public void onCreate() - { - super.onCreate(); - - mWifiLock = ((WifiManager) getSystemService(Service.WIFI_SERVICE)).createWifiLock(TAG); - mPublisher = new NotificationPublisher(this); - - mReceive.setNotifyDelay(2000); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) - { - super.onStartCommand(intent, flags, startId); - - if (intent != null) - { - if (ACTION_CHECK_AVAILABLES.equals(intent.getAction())) - { - Log.d(TAG, "Thread started for request; status = " + (startEngine() ? "now started" : "already started")); - } - else if (ACTION_CANCEL_RECEIVING.equals(intent.getAction()) && intent.hasExtra(CommunicationService.EXTRA_ACCEPT_ID)) - { - final int acceptId = intent.getIntExtra(CommunicationService.EXTRA_ACCEPT_ID, -1); - - ApplicationHelper.removeReceivers(acceptId); - - mIsBreakRequested = true; - } - } - - return START_STICKY; - } - - public boolean startEngine() - { - if (mThread == null || Thread.State.TERMINATED.equals(mThread.getState())) - { - mThread = new Thread(mRunnable); - mThread.start(); - - return true; - } - - Log.d(TAG, "Thread state = " + mThread.getState()); - - return false; - } - - public File getUniqueFile(File file) throws IOException - { - if (file.isFile()) - { - file = FileUtils.getUniqueFile(file); - file.createNewFile(); - } - - return file; - } - - private class ServerRunnable implements Runnable - { - @Override - public void run() - { - mWifiLock.acquire(); - - Log.d(TAG, "Receiver count " + ApplicationHelper.getReceivers().size()); - - int preNotified = 0; - - for (AwaitedFileReceiver receiver : ApplicationHelper.getReceivers()) - { - preNotified++; - - Log.d(TAG, "ReceiverThread running for receiver = " + receiver.fileName); - - try - { - File file = getUniqueFile(new File(FileUtils.getSaveLocationForFile(getApplicationContext(), receiver.fileName))); - - if (!mIsBreakRequested) - { - mReceive.receiveOnCurrentThread(0, file, receiver.fileSize, AppConfig.DEFAULT_BUFFER_SIZE, 10000, receiver); - - if (!receiver.processCancelled && !mIsBreakRequested) - { - if (preNotified <= 1) - mPublisher.notifyFileReceived(receiver, file, ApplicationHelper.getDeviceList().get(receiver.ip)); - else - mPublisher.notifyFileReceivedMulti(preNotified); - } - } - } catch (Exception e) - { - Log.d(TAG, "ongoing receiving error = " + e.getMessage()); - } finally - { - mPublisher.cancelNotification(NotificationPublisher.NOTIFICATION_ID_RECEIVING); - ApplicationHelper.removeReceiver(receiver); - - sendBroadcast(new Intent(ACTION_FILE_LIST_CHANGED)); - } - } - - mIsBreakRequested = false; - - Log.d(TAG, "Thread done"); - mWifiLock.release(); - - if (ApplicationHelper.getReceivers().size() > 0) - run(); - } - - public boolean requestBreak() - { - if (mIsBreakRequested == true) - return false; - - mIsBreakRequested = true; - - return true; - } - } - - private class Receive extends CoolTransfer.Receive - { - @Override - public void onError(int port, File file, Exception error, AwaitedFileReceiver extra) - { - if (!mIsBreakRequested) - { - mPublisher.notifyReceiveError(extra.fileName); - extra.processCancelled = true; - } - - if (file != null && file.isFile()) - file.delete(); - } - - @Override - public void onNotify(Socket socket, int port, File file, int percent, AwaitedFileReceiver extra) - { - NetworkDevice device = ApplicationHelper.getDeviceList().get(extra.ip); - mPublisher.notifyFileReceiving(extra, device, percent); - } - - @Override - public void onTransferCompleted(int port, File file, AwaitedFileReceiver extra) - { - - } - - @Override - public void onSocketReady(final ServerSocket serverSocket, int port, File file, final AwaitedFileReceiver extra) - { - CoolCommunication.Messenger.sendOnCurrentThread(extra.ip, AppConfig.COMMUNATION_SERVER_PORT, null, - new JsonResponseHandler() - { - @Override - public void onJsonMessage(Socket socket, CoolCommunication.Messenger.Process process, JSONObject json) - { - try - { - json.put("request", "file_transfer_notify_server_ready"); - json.put("requestId", extra.requestId); - json.put("socketPort", serverSocket.getLocalPort()); - - JSONObject response = new JSONObject(process.waitForResponse()); - - if (!response.getBoolean("result")) - this.onError(new Exception("Request rejected")); - } catch (JSONException e) - { - this.onError(e); - } - } - - @Override - public void onError(Exception exception) - { - Log.d(TAG, "Error while receiver is waiting response of sender"); - extra.processCancelled = true; - } - } - ); - - try - { - if (extra.processCancelled) - serverSocket.close(); - } catch (IOException e) - { - } - } - - @Override - public boolean onBreakRequest(int port, File file, AwaitedFileReceiver extra) - { - return super.onBreakRequest(port, file, extra) || mIsBreakRequested || extra.processCancelled; - } - - @Override - public boolean onStart(int port, File file, AwaitedFileReceiver extra) - { - if (extra.processCancelled) - return false; - - NetworkDevice device = ApplicationHelper.getDeviceList().get(extra.ip); - mPublisher.notifyFileReceiving(extra, device, 0); - - return true; - } - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/support/AppCompatPreferenceActivity.java b/app/src/main/java/com/genonbeta/TrebleShot/support/AppCompatPreferenceActivity.java deleted file mode 100644 index 6dd25af66..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/support/AppCompatPreferenceActivity.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.genonbeta.TrebleShot.support; - -import android.content.res.Configuration; -import android.os.Bundle; -import android.preference.PreferenceActivity; -import android.support.annotation.LayoutRes; -import android.support.annotation.Nullable; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatDelegate; -import android.support.v7.widget.Toolbar; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; - -public class AppCompatPreferenceActivity extends PreferenceActivity -{ - private AppCompatDelegate mDelegate; - - @Override - protected void onCreate(Bundle savedInstanceState) - { - getDelegate().installViewFactory(); - getDelegate().onCreate(savedInstanceState); - super.onCreate(savedInstanceState); - } - - @Override - protected void onPostCreate(Bundle savedInstanceState) - { - super.onPostCreate(savedInstanceState); - - getDelegate().onPostCreate(savedInstanceState); - } - - public ActionBar getSupportActionBar() - { - return getDelegate().getSupportActionBar(); - } - - public void setSupportActionBar(@Nullable Toolbar toolbar) - { - getDelegate().setSupportActionBar(toolbar); - } - - @Override - public MenuInflater getMenuInflater() - { - return getDelegate().getMenuInflater(); - } - - @Override - public void setContentView(@LayoutRes int layoutResID) - { - getDelegate().setContentView(layoutResID); - } - - @Override - public void setContentView(View view) - { - getDelegate().setContentView(view); - } - - @Override - public void setContentView(View view, ViewGroup.LayoutParams params) - { - getDelegate().setContentView(view, params); - } - - @Override - public void addContentView(View view, ViewGroup.LayoutParams params) - { - getDelegate().addContentView(view, params); - } - - @Override - protected void onPostResume() - { - super.onPostResume(); - getDelegate().onPostResume(); - } - - @Override - protected void onTitleChanged(CharSequence title, int color) - { - super.onTitleChanged(title, color); - getDelegate().setTitle(title); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) - { - super.onConfigurationChanged(newConfig); - getDelegate().onConfigurationChanged(newConfig); - } - - @Override - protected void onStop() - { - super.onStop(); - getDelegate().onStop(); - } - - @Override - protected void onDestroy() - { - super.onDestroy(); - getDelegate().onDestroy(); - } - - public void invalidateOptionsMenu() - { - getDelegate().invalidateOptionsMenu(); - } - - private AppCompatDelegate getDelegate() - { - if (mDelegate == null) - mDelegate = AppCompatDelegate.create(this, null); - - return mDelegate; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/support/AppCompatPreferenceFragment.java b/app/src/main/java/com/genonbeta/TrebleShot/support/AppCompatPreferenceFragment.java deleted file mode 100644 index cacbcdce8..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/support/AppCompatPreferenceFragment.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.genonbeta.TrebleShot.support; - -import android.content.res.Configuration; -import android.os.Bundle; -import android.preference.PreferenceFragment; -import android.support.annotation.LayoutRes; -import android.support.annotation.Nullable; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatDelegate; -import android.support.v7.widget.Toolbar; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; - -public class AppCompatPreferenceFragment extends PreferenceFragment -{ - private AppCompatDelegate mDelegate; - - @Override - public void onCreate(Bundle savedInstanceState) - { - getDelegate().installViewFactory(); - getDelegate().onCreate(savedInstanceState); - - super.onCreate(savedInstanceState); - } - - public ActionBar getSupportActionBar() - { - return getDelegate().getSupportActionBar(); - } - - public void setSupportActionBar(@Nullable Toolbar toolbar) - { - getDelegate().setSupportActionBar(toolbar); - } - - public MenuInflater getMenuInflater() - { - return getDelegate().getMenuInflater(); - } - - public void setContentView(@LayoutRes int layoutResID) - { - getDelegate().setContentView(layoutResID); - } - - public void setContentView(View view) - { - getDelegate().setContentView(view); - } - - public void setContentView(View view, ViewGroup.LayoutParams params) - { - getDelegate().setContentView(view, params); - } - - public void addContentView(View view, ViewGroup.LayoutParams params) - { - getDelegate().addContentView(view, params); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) - { - super.onConfigurationChanged(newConfig); - getDelegate().onConfigurationChanged(newConfig); - } - - @Override - public void onStop() - { - super.onStop(); - getDelegate().onStop(); - } - - @Override - public void onDestroy() - { - super.onDestroy(); - getDelegate().onDestroy(); - } - - public void invalidateOptionsMenu() - { - getDelegate().invalidateOptionsMenu(); - } - - private AppCompatDelegate getDelegate() - { - if (mDelegate == null) - mDelegate = AppCompatDelegate.create(getActivity(), null); - - return mDelegate; - } -} diff --git a/app/src/main/java/com/genonbeta/TrebleShot/support/FragmentTitle.java b/app/src/main/java/com/genonbeta/TrebleShot/support/FragmentTitle.java deleted file mode 100644 index 757096bbe..000000000 --- a/app/src/main/java/com/genonbeta/TrebleShot/support/FragmentTitle.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.genonbeta.TrebleShot.support; - -import android.content.Context; - -/** - * Created by: veli - * Date: 11/16/16 10:33 AM - */ - -public interface FragmentTitle -{ - public CharSequence getFragmentTitle(Context context); -} diff --git a/app/src/main/java/com/genonbeta/core/util/NetworkDeviceScanner.java b/app/src/main/java/com/genonbeta/core/util/NetworkDeviceScanner.java deleted file mode 100644 index 84162d089..000000000 --- a/app/src/main/java/com/genonbeta/core/util/NetworkDeviceScanner.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.genonbeta.core.util; - -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.concurrent.Executor; - -public class NetworkDeviceScanner -{ - private ArrayList mInterfaces = new ArrayList(); - private ScannerExecutor mExecutor = new ScannerExecutor(); - private Scanner mScanner = new Scanner(); - private boolean mIsBreakRequested = false; - private boolean mIsLockRequested = false; - private ScannerHandler mHandler; - private int mNumberOfThreads = 6; - - public NetworkDeviceScanner() - { - } - - public NetworkDeviceScanner(int numberOfThreads) - { - this.mNumberOfThreads = numberOfThreads; - } - - public boolean interrupt() - { - if (!this.mIsBreakRequested) - this.mIsBreakRequested = true; - else - return false; - - return true; - } - - public boolean isScannerAvaiable() - { - return (mInterfaces.size() == 0 && !mIsLockRequested); - } - - private void nextThread() - { - if (this.mIsLockRequested) - return; - - if (this.isScannerAvaiable()) - { - // this sequence only works when threads complete the job - - this.mIsBreakRequested = false; - - if (this.mHandler != null) - { - this.setLock(true); // lock scanner - this.mHandler.onThreadsCompleted(); - this.setLock(false); // release lock - } - - return; - } - - this.mScanner.updateScanner(); - - for (int threadsStarted = this.mNumberOfThreads; threadsStarted > 0; threadsStarted--) - { - mExecutor.execute(this.mScanner); - } - } - - public boolean scan(ArrayList interfaces, ScannerHandler handler) - { - if (!this.isScannerAvaiable()) - return false; - - this.mInterfaces.addAll(interfaces); - - this.mHandler = handler; - this.nextThread(); - - return true; - } - - public void setLock(boolean lock) - { - mIsLockRequested = lock; - } - - public static interface ScannerHandler - { - public void onDeviceFound(InetAddress address); - - public void onThreadsCompleted(); - } - - protected class Scanner implements Runnable - { - private String mAddressPrefix = "192.168.0."; - private boolean[] mDevices = new boolean[256]; - private int mThreadsExited = 0; - - public Scanner() - { - } - - public void updateScanner() - { - String ipAddress = NetworkDeviceScanner.this.mInterfaces.get(0); - String addressPrefix = ipAddress.substring(0, ipAddress.lastIndexOf(".") + 1); - - this.mAddressPrefix = addressPrefix; - this.mDevices = new boolean[256]; - this.mThreadsExited = NetworkDeviceScanner.this.mNumberOfThreads; - } - - @Override - public void run() - { - for (int mPosition = 0; mPosition < mDevices.length; mPosition++) - { - synchronized (mDevices) - { - if (mDevices[mPosition] == true || mPosition == 0 || NetworkDeviceScanner.this.mIsBreakRequested == true) - continue; - - mDevices[mPosition] = true; - } - - try - { - InetAddress inet = InetAddress.getByName(mAddressPrefix + mPosition); - - if (inet.isReachable(300) && NetworkDeviceScanner.this.mHandler != null) - NetworkDeviceScanner.this.mHandler.onDeviceFound(inet); - } catch (Exception e) - { - e.printStackTrace(); - } - } - - this.mThreadsExited--; - - if (this.mThreadsExited == 0) - { - NetworkDeviceScanner.this.mInterfaces.remove(0); - NetworkDeviceScanner.this.nextThread(); - } - } - } - - protected class ScannerExecutor implements Executor - { - @Override - public void execute(Runnable scanner) - { - new Thread(scanner).start(); - } - } -} diff --git a/app/src/main/java/com/genonbeta/core/util/NetworkUtils.java b/app/src/main/java/com/genonbeta/core/util/NetworkUtils.java deleted file mode 100644 index 329b82e5a..000000000 --- a/app/src/main/java/com/genonbeta/core/util/NetworkUtils.java +++ /dev/null @@ -1,200 +0,0 @@ -package com.genonbeta.core.util; - -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.NetworkInterface; -import java.net.Socket; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; - -public class NetworkUtils -{ - public static String bytesToHex(byte[] bytes) - { - StringBuilder sbuf = new StringBuilder(); - - for (int idx = 0; idx < bytes.length; idx++) - { - int intVal = bytes[idx] & 0xff; - - if (intVal < 0x10) - sbuf.append("0"); - - sbuf.append(Integer.toHexString(intVal).toUpperCase()); - } - return sbuf.toString(); - } - - public static byte[] getUTF8Bytes(String str) - { - try - { - return str.getBytes("UTF-8"); - } catch (Exception ex) - { - return null; - } - } - - public static String loadFileAsString(String filename) throws IOException - { - final int BUFLEN = 1024; - - BufferedInputStream is = new BufferedInputStream(new FileInputStream(filename), BUFLEN); - - try - { - ByteArrayOutputStream baos = new ByteArrayOutputStream(BUFLEN); - byte[] bytes = new byte[BUFLEN]; - boolean isUTF8 = false; - int read, count = 0; - - while ((read = is.read(bytes)) != -1) - { - if (count == 0 && bytes[0] == (byte) 0xEF && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF) - { - isUTF8 = true; - baos.write(bytes, 3, read - 3); // drop UTF8 bom marker - } - else - { - baos.write(bytes, 0, read); - } - - count += read; - } - return isUTF8 ? new String(baos.toByteArray(), "UTF-8") : new String(baos.toByteArray()); - } finally - { - try - { - is.close(); - } catch (Exception e) - { - e.printStackTrace(); - } - } - } - - public static ArrayList getMACAddressList(String interfaceName) - { - ArrayList macAddressList = new ArrayList(); - - try - { - List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); - - for (NetworkInterface intf : interfaces) - { - if (interfaceName != null) - { - if (!intf.getName().equalsIgnoreCase(interfaceName)) - continue; - } - - byte[] mac = intf.getHardwareAddress(); - - if (mac == null) - continue; - - StringBuilder buf = new StringBuilder(); - - for (int idx = 0; idx < mac.length; idx++) - buf.append(String.format("%02X:", mac[idx])); - - if (buf.length() > 0) - buf.deleteCharAt(buf.length() - 1); - - macAddressList.add(buf.toString()); - } - } catch (Exception e) - { - e.printStackTrace(); - } - - return macAddressList; - } - - public static HashMap getInterfaces(boolean useIPv4, String[] avoidInterface) - { - HashMap ipAddressList = new HashMap(); - - try - { - List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); - - for (NetworkInterface intf : interfaces) - { - boolean breaker = false; - - for (String match : avoidInterface) - { - if (intf.getDisplayName().contains(match)) - breaker = true; - } - - if (breaker) - continue; - - List addrs = Collections.list(intf.getInetAddresses()); - - for (InetAddress addr : addrs) - { - if (!addr.isLoopbackAddress()) - { - String sAddr = addr.getHostAddress().toUpperCase(); - boolean isIPv4 = addr instanceof Inet4Address; - - if (useIPv4 && isIPv4) - { - ipAddressList.put(intf, sAddr); - } - else if (!useIPv4) - { - int delim = sAddr.indexOf('%'); // drop ip6 port suffix - ipAddressList.put(intf, (delim < 0 ? sAddr : sAddr.substring(0, delim))); - } - } - } - } - } catch (Exception e) - { - } - - return ipAddressList; - } - - public static ArrayList getInterfacesWithOnlyIp(boolean useIPv4, String[] avoidInterface) - { - ArrayList list = new ArrayList(); - - list.addAll(getInterfaces(useIPv4, avoidInterface).values()); - - return list; - } - - public static boolean testSocket(String ip, int port) - { - InetSocketAddress socketAddress = new InetSocketAddress(ip, port); - Socket socket = new Socket(); - - try - { - socket.bind(null); - socket.connect(socketAddress); - socket.close(); - - return true; - } catch (IOException e) - { - return false; - } - } -} diff --git a/app/src/main/java/org/monora/uprotocol/client/android/App.kt b/app/src/main/java/org/monora/uprotocol/client/android/App.kt new file mode 100755 index 000000000..d3a577763 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/App.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android + +import android.os.Build +import android.text.format.DateFormat +import android.util.Log +import androidx.core.content.edit +import androidx.multidex.MultiDexApplication +import androidx.preference.PreferenceManager +import dagger.hilt.EntryPoint +import dagger.hilt.EntryPoints +import dagger.hilt.InstallIn +import dagger.hilt.android.HiltAndroidApp +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import org.monora.uprotocol.client.android.util.Updater +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.PrintWriter +import java.util.* + +/** + * created by: Veli + * date: 25.02.2018 01:23 + */ +@HiltAndroidApp +class App : MultiDexApplication(), Thread.UncaughtExceptionHandler { + private val applicationScope = CoroutineScope(SupervisorJob()) + + private lateinit var crashFile: File + + private var defaultExceptionHandler: Thread.UncaughtExceptionHandler? = null + + override fun onCreate() { + super.onCreate() + crashFile = applicationContext.getFileStreamPath(FILENAME_UNHANDLED_CRASH_LOG) + defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler() + Thread.setDefaultUncaughtExceptionHandler(this) + initializeSettings() + + val updater = EntryPoints.get(this@App, AppEntryPoint::class.java).updater() + + if (updater.needsToCheckForUpdates()) applicationScope.launch { + try { + updater.checkForUpdates() + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + private fun initializeSettings() { + val preferences = PreferenceManager.getDefaultSharedPreferences(this) + val hasNsdSet = preferences.contains("nsd_enabled") + val hasReferralVersion = preferences.contains("referral_version") + + PreferenceManager.setDefaultValues(this, R.xml.preferences_defaults_main, false) + + if (!hasReferralVersion) preferences.edit { + putInt("referral_version", BuildConfig.VERSION_CODE) + } + + // Some pre-kitkat devices were soft rebooting when this feature was turned on by default. + // So we will disable it for them and it will still remain as an option for the user. + if (!hasNsdSet) preferences.edit { + putBoolean("nsd_enabled", Build.VERSION.SDK_INT >= 19) + } + + val migratedVersion = preferences.getInt("migrated_version", MIGRATION_NONE) + if (migratedVersion < BuildConfig.VERSION_CODE) preferences.edit { + putInt("migrated_version", BuildConfig.VERSION_CODE) + if (migratedVersion > MIGRATION_NONE) putInt("previously_migrated_version", migratedVersion) + } + } + + override fun uncaughtException(t: Thread, e: Throwable) { + try { + if (crashFile.canWrite()) { + Log.d(TAG, "uncaughtException: Check failed") + return + } + + PrintWriter(FileOutputStream(crashFile)).use { printWriter -> + val stackTrace = e.stackTrace + + printWriter.append("--Uprotocol Client Crash Log ---\n") + .append("\nException: ${e.javaClass.simpleName}") + .append("\nMessage: ${e.message}") + .append("\nCause: ${e.cause}") + .append("\nDate: ") + .append(DateFormat.getLongDateFormat(this).format(Date())) + .append("\n\n--Stacktrace--\n\n") + + if (stackTrace.isNotEmpty()) for (element in stackTrace) with(element) { + printWriter.append("$className.$methodName:$lineNumber\n") + } + } + } catch (ex: IOException) { + ex.printStackTrace() + } finally { + defaultExceptionHandler?.uncaughtException(t, e) + } + } + + companion object { + private val TAG = App::class.simpleName + + private const val MIGRATION_NONE = -1 + + const val FILENAME_UNHANDLED_CRASH_LOG = "unhandled_crash_log.txt" + } +} + +@EntryPoint +@InstallIn(SingletonComponent::class) +interface AppEntryPoint { + fun updater(): Updater +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/ApplicationGlideModule.kt b/app/src/main/java/org/monora/uprotocol/client/android/ApplicationGlideModule.kt new file mode 100755 index 000000000..559bd2572 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/ApplicationGlideModule.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.graphics.drawable.Drawable +import com.bumptech.glide.Glide +import com.bumptech.glide.Priority +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.data.DataFetcher +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoader.LoadData +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory +import com.bumptech.glide.module.AppGlideModule +import com.bumptech.glide.signature.ObjectKey + +/** + * created by: Veli + * date: 28.03.2018 17:29 + */ +@GlideModule +class ApplicationGlideModule : AppGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + super.registerComponents(context, glide, registry) + registry.append( + ApplicationInfo::class.java, + Drawable::class.java, + AppIconModelLoaderFactory(context) + ) + } + + internal class AppIconDataFetcher(val context: Context, val model: ApplicationInfo) : DataFetcher { + override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + callback.onDataReady(context.packageManager.getApplicationIcon(model)) + } + + override fun cleanup() { + // Empty Implementation + } + + override fun cancel() { + // Empty Implementation + } + + override fun getDataClass(): Class { + return Drawable::class.java + } + + override fun getDataSource(): DataSource { + return DataSource.LOCAL + } + } + + internal class AppIconModelLoader(private val context: Context) : ModelLoader { + override fun buildLoadData( + applicationInfo: ApplicationInfo, width: Int, height: Int, options: Options, + ): LoadData { + return LoadData(ObjectKey(applicationInfo), AppIconDataFetcher(context, applicationInfo)) + } + + override fun handles(applicationInfo: ApplicationInfo): Boolean { + return true + } + } + + internal class AppIconModelLoaderFactory( + private val context: Context, + ) : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { + return AppIconModelLoader(context) + } + + override fun teardown() { + // Empty Implementation. + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/AboutActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/AboutActivity.kt new file mode 100755 index 000000000..e2a42fea5 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/AboutActivity.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.content.DialogInterface +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.lifecycle.lifecycleScope +import com.google.android.material.snackbar.Snackbar +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import org.monora.uprotocol.client.android.BuildConfig +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.config.AppConfig +import org.monora.uprotocol.client.android.util.Activities +import org.monora.uprotocol.client.android.util.Updater +import javax.inject.Inject + +@AndroidEntryPoint +class AboutActivity : Activity() { + private val googlePlayFlavor = BuildConfig.FLAVOR == "googlePlay" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_about) + setSupportActionBar(findViewById(R.id.toolbar)) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + findViewById(R.id.activity_about_home_button).setOnClickListener { + startActivity(Intent(Intent.ACTION_VIEW).setData(Uri.parse(AppConfig.URI_ORG_HOME))) + } + findViewById(R.id.activity_about_github_button).setOnClickListener { + startActivity(Intent(Intent.ACTION_VIEW).setData(Uri.parse(AppConfig.URI_REPO_APP))) + } + findViewById(R.id.activity_about_localize_button).setOnClickListener { + startActivity(Intent(Intent.ACTION_VIEW).setData(Uri.parse(AppConfig.URI_TRANSLATE))) + } + findViewById(R.id.activity_about_telegram_button).setOnClickListener { + startActivity(Intent(Intent.ACTION_VIEW).setData(Uri.parse(AppConfig.URI_TELEGRAM_CHANNEL))) + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.actions_about, menu) + menu.findItem(R.id.actions_about_check_for_updates).isVisible = !googlePlayFlavor + + return super.onCreateOptionsMenu(menu) + } + + override fun onPrepareOptionsMenu(menu: Menu?): Boolean { + if (!googlePlayFlavor && updater.hasNewVersion()) { + menu?.findItem(R.id.actions_about_check_for_updates)?.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) + } + return super.onPrepareOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.actions_about_feedback -> Activities.startFeedbackActivity(this) + R.id.actions_about_changelog -> { + startActivity(Intent(this, ChangelogActivity::class.java)) + } + R.id.actions_about_third_party_licenses -> { + startActivity(Intent(this, LicensesActivity::class.java)) + } + R.id.actions_about_check_for_updates -> lifecycleScope.launch { + var snack = 0 + + try { + val release = updater.checkForUpdates() + + if (release == null) { + snack = R.string.genfw_uwg_up_to_date + } else { + val visitPage = DialogInterface.OnClickListener { _, _ -> + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(release.url))) + } + + AlertDialog.Builder(this@AboutActivity) + .setTitle(R.string.genfw_uwg_update_available) + .setMessage( + String.format( + getString(R.string.genfw_uwg_update_body), + release.name, + release.tag, + release.publishDate, + release.changelog + ) + ) + .setPositiveButton(R.string.genfw_uwg_visit_page, visitPage) + .setNegativeButton(android.R.string.cancel, null) + .show() + } + } catch (e: Exception) { + snack = R.string.genfw_uwg_version_check_error + } + + if (snack != 0) { + Snackbar.make(window.decorView, snack, Snackbar.LENGTH_LONG).show() + } + } + else -> return false + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/ChangeStoragePathActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/ChangeStoragePathActivity.kt new file mode 100755 index 000000000..141158755 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/ChangeStoragePathActivity.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.content.Intent +import android.os.Parcelable +import android.widget.Toast +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.util.Files + +/** + * Created by: veli + * Date: 5/30/17 6:57 PM + */ +class ChangeStoragePathActivity : Activity() { + override fun onStart() { + super.onStart() + val currentSavePath = Files.getApplicationDirectory(applicationContext) + startActivityForResult( + Intent(this, FilePickerActivity::class.java) + .setAction(FilePickerActivity.ACTION_CHOOSE_DIRECTORY) + .putExtra(FilePickerActivity.EXTRA_START_PATH, currentSavePath.getUri().toString()) + .putExtra(FilePickerActivity.EXTRA_ACTIVITY_TITLE, getString(R.string.text_storagePath)), + REQUEST_CHOOSE_FOLDER + ) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (data != null) { + if (resultCode == RESULT_OK) { + when (requestCode) { + REQUEST_CHOOSE_FOLDER -> if (data.hasExtra(FilePickerActivity.EXTRA_CHOSEN_PATH)) { + defaultPreferences + .edit() + .putString( + "storage_path", + data.getParcelableExtra(FilePickerActivity.EXTRA_CHOSEN_PATH) + .toString() + ) + .apply() + Toast.makeText(this, "\uD83D\uDC4D", Toast.LENGTH_SHORT).show() + } + } + } + } + + finish() + } + + companion object { + const val REQUEST_CHOOSE_FOLDER = 1 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/ChangelogActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/ChangelogActivity.kt new file mode 100644 index 000000000..43c15bb2a --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/ChangelogActivity.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.os.Bundle +import android.view.MenuItem +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity + +/** + * created by: veli + * date: 9/12/18 6:09 PM + */ +class ChangelogActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_changelog) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/ContentSharingActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/ContentSharingActivity.kt new file mode 100755 index 000000000..858349269 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/ContentSharingActivity.kt @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.content.DialogInterface +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.widget.ProgressBar +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.ActionMenuView +import androidx.appcompat.widget.Toolbar +import androidx.fragment.app.Fragment +import androidx.transition.TransitionManager +import androidx.viewpager2.widget.ViewPager2 +import com.genonbeta.android.framework.ui.PerformerMenu +import com.genonbeta.android.framework.util.actionperformer.IPerformerEngine +import com.genonbeta.android.framework.util.actionperformer.PerformerEngine +import com.genonbeta.android.framework.util.actionperformer.PerformerEngineProvider +import com.google.android.material.tabs.TabLayout +import dagger.hilt.android.AndroidEntryPoint +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.adapter.MainFragmentStateAdapter +import org.monora.uprotocol.client.android.adapter.MainFragmentStateAdapter.PageItem +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.app.ListingFragment +import org.monora.uprotocol.client.android.app.ListingFragmentBase +import org.monora.uprotocol.client.android.dialog.ChooseSharingMethodDialog +import org.monora.uprotocol.client.android.fragment.* +import org.monora.uprotocol.client.android.model.ContentModel +import org.monora.uprotocol.client.android.service.backgroundservice.AsyncTask +import org.monora.uprotocol.client.android.service.backgroundservice.AttachedTaskListener +import org.monora.uprotocol.client.android.service.backgroundservice.BaseAttachableAsyncTask +import org.monora.uprotocol.client.android.service.backgroundservice.TaskMessage +import org.monora.uprotocol.client.android.task.OrganizeLocalSharingTask +import org.monora.uprotocol.client.android.ui.callback.LocalSharingCallback +import org.monora.uprotocol.client.android.ui.callback.SharingPerformerMenuCallback +import org.monora.uprotocol.client.android.util.Selections + +/** + * created by: veli + * date: 13/04/18 19:45 + */ +@AndroidEntryPoint +class ContentSharingActivity : Activity(), PerformerEngineProvider, LocalSharingCallback, AttachedTaskListener { + private var backPressedListener: OnBackPressedListener? = null + + private val menuCallback = SharingPerformerMenuCallback(this, this) + + private val performerEngine = PerformerEngine() + + private lateinit var progressBar: ProgressBar + + private lateinit var cardView: ViewGroup + + private lateinit var actionMenuView: ActionMenuView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_content_sharing) + actionMenuView = findViewById(R.id.menu_view) + cardView = findViewById(R.id.activity_content_sharing_cardview) + progressBar = findViewById(R.id.activity_content_sharing_progress_bar) + val toolbar = findViewById(R.id.toolbar) + + setSupportActionBar(toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + val performerMenu = PerformerMenu(this, menuCallback) + menuCallback.localSharingCallback = this + menuCallback.cancellable = false + performerMenu.load(actionMenuView.menu) + performerMenu.setUp(performerEngine) + val tabLayout: TabLayout = findViewById(R.id.activity_content_sharing_tab_layout) + val viewPager: ViewPager2 = findViewById(R.id.activity_content_sharing_view_pager) + val pagerAdapter: MainFragmentStateAdapter = object : MainFragmentStateAdapter( + this, supportFragmentManager, lifecycle + ) { + override fun onItemInstantiated(item: PageItem) { + val fragment: Fragment? = item.fragment + if (fragment is ListingFragmentBase<*>) { + if (viewPager.currentItem == item.currentPosition) { + attachListeners(fragment) + } + } + } + } + val arguments = Bundle() + arguments.putBoolean(ListingFragment.ARG_SELECT_BY_CLICK, true) + arguments.putBoolean(ListingFragment.ARG_HAS_BOTTOM_SPACE, false) + // FIXME: 2/21/21 Sharing fragments were here + pagerAdapter.add( + PageItem( + 0, + R.drawable.ic_short_text_white_24dp, + getString(R.string.text_sharedTexts), + SharedTextFragment::class.qualifiedName!!, + arguments + ) + ) + pagerAdapter.add( + PageItem( + 1, + R.drawable.ic_short_text_white_24dp, + getString(R.string.text_files), + SharedTextFragment::class.qualifiedName!!, + arguments + ) + ) + /*pagerAdapter.add(StableItem(0, ApplicationListFragment::class.qualifiedName!!, arguments)) + pagerAdapter.add( + StableItem(1, FileExplorerFragment::class.qualifiedName!!, arguments, getString(R.string.text_files)) + ) + pagerAdapter.add(StableItem(2, AudioListFragment::class.qualifiedName!!, arguments)) + pagerAdapter.add(StableItem(3, ImageListFragment::class.qualifiedName!!, arguments)) + pagerAdapter.add(StableItem(4, VideoListFragment::class.qualifiedName!!, arguments))*/ + pagerAdapter.createTabs(tabLayout, withIcon = false, withText = true) + viewPager.adapter = pagerAdapter + + tabLayout.addOnTabSelectedListener( + object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab?) { + tab?.let { + viewPager.setCurrentItem(tab.position, true) + } + } + + override fun onTabUnselected(tab: TabLayout.Tab?) { + + } + + override fun onTabReselected(tab: TabLayout.Tab?) { + + } + } + ) + + viewPager.registerOnPageChangeCallback( + object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + tabLayout.getTabAt(position)?.select() + val fragment = pagerAdapter.getItem(position).fragment + if (fragment is ListingFragmentBase<*>) { + val editableListFragment = fragment as ListingFragmentBase<*> + attachListeners(editableListFragment) + Handler(Looper.getMainLooper()).postDelayed( + { editableListFragment.adapterImpl.syncAllAndNotify() }, 200 + ) + } + } + } + ) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + if (canExit()) finish() + } else { + return super.onOptionsItemSelected(item) + } + return true + } + + override fun onAttachTasks(taskList: List) { + super.onAttachTasks(taskList) + for (task in taskList) if (task is OrganizeLocalSharingTask) task.anchor = this + } + + override fun onShareLocal(shareableList: List) { + ChooseSharingMethodDialog( + this, + object : ChooseSharingMethodDialog.PickListener { + override fun onShareMethod(sharingMethod: ChooseSharingMethodDialog.SharingMethod) { + val task = ChooseSharingMethodDialog.createLocalShareOrganizingTask(sharingMethod, shareableList) + runUiTask(task, this@ContentSharingActivity) + } + } + ).show() + } + + override fun onBackPressed() { + if (backPressedListener?.onBackPressed() != true && canExit()) { + super.onBackPressed() + } + } + + fun attachListeners(fragment: ListingFragmentBase<*>) { + menuCallback.foregroundConnection = fragment.engineConnection + backPressedListener = if (fragment is OnBackPressedListener) fragment else null + } + + private fun canExit(): Boolean { + if (Selections.getTotalSize(performerEngine) > 0) { + AlertDialog.Builder(this) + .setMessage(R.string.ques_cancelSelection) + .setNegativeButton(R.string.butn_no, null) + .setPositiveButton(R.string.butn_yes) { dialog: DialogInterface?, which: Int -> finish() } + .show() + return false + } + return true + } + + override fun getPerformerEngine(): IPerformerEngine { + return performerEngine + } + + override fun onTaskStateChange(task: BaseAttachableAsyncTask, state: AsyncTask.State) { + if (task is OrganizeLocalSharingTask) { + when (state) { + AsyncTask.State.Finished -> { + actionMenuView.visibility = View.VISIBLE + progressBar.visibility = View.GONE + TransitionManager.beginDelayedTransition(cardView) + } + AsyncTask.State.Starting -> { + actionMenuView.visibility = View.GONE + progressBar.visibility = View.VISIBLE + TransitionManager.beginDelayedTransition(cardView) + } + } + } + } + + override fun onTaskMessage(taskMessage: TaskMessage): Boolean { + return false + } + + companion object { + private val TAG = ContentSharingActivity::class.simpleName + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/FileExplorerActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/FileExplorerActivity.kt new file mode 100644 index 000000000..3226697cb --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/FileExplorerActivity.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.MenuItem +import androidx.appcompat.widget.Toolbar +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.fragment.FileExplorerFragment +import com.genonbeta.android.framework.io.DocumentFile +import com.genonbeta.android.framework.util.Files +import java.io.FileNotFoundException + +class FileExplorerActivity : Activity() { + private lateinit var fileExplorerFragment: FileExplorerFragment + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_file_explorer) + val toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + fileExplorerFragment = supportFragmentManager.findFragmentById(R.id.activity_file_explorer_fragment_files) + as FileExplorerFragment? ?: throw NullPointerException() + supportActionBar?.setDisplayHomeAsUpEnabled(true) + checkRequestedPath(intent) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + if (id == android.R.id.home) + finish() + else + return super.onOptionsItemSelected(item) + return true + } + + override fun onBackPressed() { + if (!fileExplorerFragment.onBackPressed()) + super.onBackPressed() + } + + fun checkRequestedPath(intent: Intent?) { + if (intent == null) + return + + if (intent.hasExtra(EXTRA_FILE_PATH)) { + val directoryUri = intent.getParcelableExtra(EXTRA_FILE_PATH) + if (directoryUri != null) { + try { + openFolder(Files.fromUri(applicationContext, directoryUri)) + } catch (e: FileNotFoundException) { + e.printStackTrace() + } + } + } else + openFolder(null) + } + + private fun openFolder(requestedFolder: DocumentFile?) { + if (requestedFolder != null) fileExplorerFragment.requestPath(requestedFolder) + } + + companion object { + const val EXTRA_FILE_PATH = "filePath" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/FilePickerActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/FilePickerActivity.kt new file mode 100755 index 000000000..4bad02c8e --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/FilePickerActivity.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.app.ListingFragment.LayoutClickListener +import org.monora.uprotocol.client.android.app.ListingFragmentBase +import org.monora.uprotocol.client.android.fragment.FileExplorerFragment +import com.genonbeta.android.framework.io.DocumentFile +import com.genonbeta.android.framework.util.Files +import com.genonbeta.android.framework.widget.RecyclerViewAdapter.ViewHolder +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.snackbar.Snackbar + +/** + * Created by: veli + * Date: 5/29/17 3:18 PM + */ +class FilePickerActivity : Activity() { + private lateinit var fileExplorerFragment: FileExplorerFragment + + private lateinit var fab: FloatingActionButton + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_filepicker) + fileExplorerFragment = supportFragmentManager.findFragmentById(R.id.activity_filepicker_fragment_files) + as FileExplorerFragment? ?: throw NullPointerException() + fab = findViewById(R.id.content_fab) + } + + override fun onStart() { + super.onStart() + if (intent != null) { + var hasTitlesDefined = false + if (intent != null) { + supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close_white_24dp) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + if (intent.hasExtra(EXTRA_ACTIVITY_TITLE).also { hasTitlesDefined = it }) + supportActionBar?.setTitle(intent.getStringExtra(EXTRA_ACTIVITY_TITLE)) + } + + if (ACTION_CHOOSE_DIRECTORY == intent.action) { + if (supportActionBar != null) { + if (!hasTitlesDefined) supportActionBar!!.setTitle(R.string.text_chooseFolder) else supportActionBar!!.setSubtitle( + R.string.text_chooseFolder + ) + } + fileExplorerFragment.adapter.also { + it.showDirectories = true + it.showFiles = false + } + fileExplorerFragment.refreshList() + fileExplorerFragment.listView.setPadding(0, 0, 0, 200) + fileExplorerFragment.listView.clipToPadding = false + fab.show() + fab.setOnClickListener { v: View -> + val selectedPath = fileExplorerFragment.adapter.path + if (selectedPath != null && selectedPath.canWrite()) + finishWithResult(selectedPath) + else + Snackbar.make(v, R.string.mesg_currentPathUnavailable, Snackbar.LENGTH_SHORT).show() + } + } else if (ACTION_CHOOSE_FILE == intent.action) { + if (!hasTitlesDefined) + supportActionBar?.setTitle(R.string.text_chooseFile) + else + supportActionBar?.setSubtitle(R.string.text_chooseFolder) + fileExplorerFragment.setLayoutClickListener(object : LayoutClickListener { + override fun onLayoutClick( + listFragment: ListingFragmentBase<*>, + holder: ViewHolder, + longClick: Boolean, + ): Boolean { + if (longClick) + return false + // FIXME: 2/20/21 Return selected item + /* + val fileHolder = fileExplorerFragment.adapter.getItem(holder) + val file = fileHolder.file + + if (file?.isDirectory() == true) { + finishWithResult(file) + return true + }*/ + return false + } + }) + } else { + finish() + } + + if (!isFinishing && intent.hasExtra(EXTRA_START_PATH)) try { + fileExplorerFragment.goPath( + Files.fromUri(this, Uri.parse(intent.getStringExtra(EXTRA_START_PATH))) + ) + } catch (e: Exception) { + e.printStackTrace() + } + } else { + finish() + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + if (id == android.R.id.home) finish() else return super.onOptionsItemSelected(item) + return true + } + + override fun onBackPressed() { + if (!fileExplorerFragment.onBackPressed()) + super.onBackPressed() + } + + private fun finishWithResult(file: DocumentFile) { + setResult( + RESULT_OK, Intent(ACTION_CHOOSE_DIRECTORY) + .putExtra(EXTRA_CHOSEN_PATH, file.getUri()) + ) + finish() + } + + companion object { + const val ACTION_CHOOSE_DIRECTORY = "com.genonbeta.intent.action.CHOOSE_DIRECTORY" + const val ACTION_CHOOSE_FILE = "com.genonbeta.intent.action.CHOOSE_FILE" + const val EXTRA_ACTIVITY_TITLE = "activityTitle" + const val EXTRA_START_PATH = "startPath" + + // belongs to returned result intent + const val EXTRA_CHOSEN_PATH = "chosenPath" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/FragmentActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/FragmentActivity.kt new file mode 100644 index 000000000..839edc949 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/FragmentActivity.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.activity + +import androidx.fragment.app.FragmentActivity +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment +import org.monora.uprotocol.client.android.R + +fun FragmentActivity.navController(id: Int): NavController { + return (supportFragmentManager.findFragmentById(id) as NavHostFragment).navController +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/HomeActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/HomeActivity.kt new file mode 100755 index 000000000..e39ce5be5 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/HomeActivity.kt @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.appcompat.app.ActionBarDrawerToggle +import androidx.appcompat.widget.Toolbar +import androidx.core.view.GravityCompat +import androidx.drawerlayout.widget.DrawerLayout +import com.google.android.material.navigation.NavigationView +import dagger.hilt.android.AndroidEntryPoint +import org.monora.uprotocol.client.android.BuildConfig +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.databinding.LayoutUserProfileBinding +import org.monora.uprotocol.client.android.dialog.ShareAppDialog +import org.monora.uprotocol.client.android.util.Activities +import org.monora.uprotocol.client.android.util.Graphics +import org.monora.uprotocol.client.android.viewmodel.UserProfileViewModel + +@AndroidEntryPoint +class HomeActivity : Activity(), NavigationView.OnNavigationItemSelectedListener { + private val userProfileViewModel: UserProfileViewModel by viewModels() + + private val pickPhoto = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> + if (uri != null) { + Graphics.saveClientPictureLocal(applicationContext, uri) + } + } + + private val userProfileBinding by lazy { + LayoutUserProfileBinding.bind(navigationView.getHeaderView(0)).also { + it.viewModel = userProfileViewModel + it.lifecycleOwner = this + it.editProfileClickListener = View.OnClickListener { + editProfile(userProfileViewModel, pickPhoto, this) + } + } + } + + private lateinit var navigationView: NavigationView + + private lateinit var drawerLayout: DrawerLayout + + private var pendingMenuItemId = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + navigationView = findViewById(R.id.nav_view) + drawerLayout = findViewById(R.id.drawer_layout) + val toolbar = findViewById(R.id.toolbar) + + setSupportActionBar(toolbar) + val toggle = ActionBarDrawerToggle( + this, drawerLayout, toolbar, R.string.text_navigationDrawerOpen, R.string.text_navigationDrawerClose + ) + drawerLayout.addDrawerListener(toggle) + toggle.syncState() + drawerLayout.addDrawerListener( + object : DrawerLayout.SimpleDrawerListener() { + override fun onDrawerClosed(drawerView: View) { + applyAwaitingDrawerAction() + } + } + ) + + navigationView.setNavigationItemSelectedListener(this) + if (updater.hasNewVersion()) { + highlightUpdate() + } + if (BuildConfig.FLAVOR == "googlePlay") { + navigationView.menu.findItem(R.id.menu_activity_main_donate).isVisible = true + } + findViewById(R.id.sendLayoutButton).setOnClickListener { + startActivity(Intent(it.context, ContentSharingActivity::class.java)) + } + findViewById(R.id.receiveLayoutButton).setOnClickListener { + startActivity(Intent(it.context, ReceiveActivity::class.java)) + } + + userProfileBinding.executePendingBindings() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.actions_home, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.actions_home_transfer_history) { + startActivity(Intent(this, SharedTextActivity::class.java)) + } else { + return super.onOptionsItemSelected(item) + } + return true + } + + override fun onNavigationItemSelected(item: MenuItem): Boolean { + pendingMenuItemId = item.itemId + drawerLayout.closeDrawer(GravityCompat.START) + return true + } + + private fun applyAwaitingDrawerAction() { + if (pendingMenuItemId == 0) { + return // drawer was opened, but nothing was clicked. + } + + when (pendingMenuItemId) { + R.id.menu_activity_main_manage_devices -> { + startActivity(Intent(this, ManageDevicesActivity::class.java)) + } + R.id.menu_activity_main_about -> startActivity(Intent(this, AboutActivity::class.java)) + R.id.menu_activity_main_send_application -> ShareAppDialog(this).show() + R.id.menu_activity_main_preferences -> { + startActivity(Intent(this, PreferencesActivity::class.java)) + } + R.id.menu_activity_main_donate -> try { + startActivity( + Intent( + applicationContext, + Class.forName("org.monora.uprotocol.client.android.activity.DonationActivity") + ) + ) + } catch (e: ClassNotFoundException) { + e.printStackTrace() + } + R.id.menu_activity_feedback -> Activities.startFeedbackActivity(this) + } + + pendingMenuItemId = 0 + } + + private fun highlightUpdate() { + navigationView.menu.findItem(R.id.menu_activity_main_about).setTitle(R.string.text_newVersionAvailable) + } + + companion object { + const val REQUEST_PERMISSION_ALL = 1 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/LicensesActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/LicensesActivity.kt new file mode 100644 index 000000000..e040c204e --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/LicensesActivity.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import org.monora.uprotocol.client.android.R +import android.os.Bundle +import android.view.* +import androidx.appcompat.app.AlertDialog +import org.monora.uprotocol.client.android.app.Activity + +/** + * created by: veli + * date: 7/20/18 10:19 PM + */ +class LicensesActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_third_party_libraries) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.actions_third_party_libraries, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> onBackPressed() + R.id.menu_action_info -> { + AlertDialog.Builder(this) + .setTitle(R.string.text_help) + .setMessage(R.string.text_thirdPartyLibrariesHelp) + .setPositiveButton(android.R.string.ok, null) + .show() + } + else -> return super.onOptionsItemSelected(item) + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/ManageDevicesActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/ManageDevicesActivity.kt new file mode 100644 index 000000000..31a47e381 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/ManageDevicesActivity.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.os.Bundle +import android.view.MenuItem +import dagger.hilt.android.AndroidEntryPoint +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity + +class ManageDevicesActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_manage_devices) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/PickClientActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/PickClientActivity.kt new file mode 100644 index 000000000..2b3ab7f8c --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/PickClientActivity.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.os.Bundle +import android.view.MenuItem +import androidx.activity.viewModels +import com.google.android.material.snackbar.Snackbar +import dagger.hilt.android.AndroidEntryPoint +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.activity.result.contract.PickClient +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.model.ClientRoute +import org.monora.uprotocol.client.android.viewmodel.ClientPickerViewModel + +@AndroidEntryPoint +class PickClientActivity : Activity() { + private val clientPickerViewModel: ClientPickerViewModel by viewModels() + + private val connectionMode by lazy { + intent?.getSerializableExtra(PickClient.EXTRA_CONNECTION_MODE) ?: PickClient.ConnectionMode.WaitForRequests + } + + private val navController by lazy { + navController(R.id.nav_host_fragment) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setResult(RESULT_CANCELED) + setContentView(R.layout.activity_pick_client) + setSupportActionBar(findViewById(R.id.toolbar)) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + navController.addOnDestinationChangedListener { _, destination, _ -> + title = destination.label + } + + clientPickerViewModel.clientRoute.observe(this) { + handleResult(it) + } + + clientPickerViewModel.client.observe(this) { + Snackbar.make( + findViewById(android.R.id.content), + it.nickname, + Snackbar.LENGTH_LONG + ).show() + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + return navController.popBackStack() + } + return super.onOptionsItemSelected(item) + } + + private fun handleResult(clientRoute: ClientRoute) { + if (PickClient.ConnectionMode.Return == connectionMode) { + PickClient.returnResult(this, clientRoute.client, clientRoute.address) + } else if (PickClient.ConnectionMode.WaitForRequests == connectionMode) { + Snackbar.make(findViewById(android.R.id.content), R.string.mesg_completing, Snackbar.LENGTH_LONG).show() + } + } +} + diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/PreferencesActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/PreferencesActivity.kt new file mode 100755 index 000000000..18759cc1e --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/PreferencesActivity.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import org.monora.uprotocol.client.android.R +import android.content.DialogInterface +import android.os.Bundle +import android.view.* +import androidx.appcompat.app.AlertDialog +import androidx.preference.PreferenceManager +import org.monora.uprotocol.client.android.app.Activity + +class PreferencesActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_preferences) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> onBackPressed() + R.id.actions_preference_main_reset_to_defaults -> { + AlertDialog.Builder(this) + .setTitle(R.string.ques_resetToDefault) + .setMessage(R.string.text_resetPreferencesToDefaultSummary) + .setNegativeButton(R.string.butn_cancel, null) + .setPositiveButton(R.string.butn_proceed) { dialog: DialogInterface?, which: Int -> + PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() + .clear() + .apply() + finish() + } + .show() + } + else -> return super.onOptionsItemSelected(item) + } + return true + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.actions_preferences_main, menu) + return super.onCreateOptionsMenu(menu) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/ReceiveActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/ReceiveActivity.kt new file mode 100644 index 000000000..36550c399 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/ReceiveActivity.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.os.Bundle +import android.view.MenuItem +import androidx.activity.viewModels +import dagger.hilt.android.AndroidEntryPoint +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.viewmodel.ClientPickerViewModel + +@AndroidEntryPoint +class ReceiveActivity : Activity() { + private val clientPickerViewModel: ClientPickerViewModel by viewModels() + + private val navController by lazy { + navController(R.id.nav_host_fragment) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_receive) + + navController.addOnDestinationChangedListener { _, destination, _ -> + title = destination.label + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + return navController.popBackStack() + } + return super.onOptionsItemSelected(item) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/SharedTextActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/SharedTextActivity.kt new file mode 100644 index 000000000..e66538fc1 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/SharedTextActivity.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.os.Bundle +import android.view.MenuItem +import androidx.appcompat.widget.Toolbar +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class SharedTextActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_text_stream) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/SharingActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/SharingActivity.kt new file mode 100755 index 000000000..0a8135d5f --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/SharingActivity.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.util.Log +import android.view.MenuItem +import androidx.activity.OnBackPressedCallback +import androidx.activity.viewModels +import dagger.hilt.android.AndroidEntryPoint +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.fragment.SharingActivityViewModel +import java.util.* + +@AndroidEntryPoint +class SharingActivity : Activity() { + private val sharingActivityViewModel: SharingActivityViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val action: String? = intent?.action + var list: List? = null + + when (action) { + Intent.ACTION_SEND -> if (intent.hasExtra(Intent.EXTRA_TEXT)) { + startActivity( + Intent(this@SharingActivity, TextEditorActivity::class.java) + .setAction(TextEditorActivity.ACTION_EDIT_TEXT) + .putExtra( + TextEditorActivity.EXTRA_TEXT, + intent.getStringExtra(Intent.EXTRA_TEXT) + ) + ) + } else { + val uri: Uri? = intent.getParcelableExtra(Intent.EXTRA_STREAM) + if (uri != null) list = Collections.singletonList(uri) + } + Intent.ACTION_SEND_MULTIPLE -> { + list = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM) + } + } + + if (list.isNullOrEmpty()) { + finish() + return + } + + sharingActivityViewModel.consume(applicationContext, list) + + setContentView(R.layout.activity_sharing) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close_white_24dp) + + navController(R.id.nav_host_fragment).addOnDestinationChangedListener { _, destination, _ -> + title = destination.label + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + finish() + return true + } + + return super.onOptionsItemSelected(item) + } + + companion object { + private val TAG = SharingActivity::class.simpleName + + const val EXTRA_DEVICE_ID = "extraDeviceId" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/TextEditorActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/TextEditorActivity.kt new file mode 100755 index 000000000..d4d58112a --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/TextEditorActivity.kt @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.content.* +import android.graphics.Bitmap +import android.os.Bundle +import android.view.* +import android.widget.EditText +import android.widget.ImageView +import android.widget.Toast +import androidx.lifecycle.coroutineScope +import androidx.lifecycle.lifecycleScope +import com.genonbeta.android.framework.ui.callback.SnackbarPlacementProvider +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.snackbar.Snackbar +import com.google.zxing.BarcodeFormat +import com.google.zxing.MultiFormatWriter +import com.google.zxing.WriterException +import com.google.zxing.common.BitMatrix +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import org.monora.android.codescanner.BarcodeEncoder +import org.monora.uprotocol.client.android.GlideApp +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.activity.result.contract.PickClient +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.database.model.SharedText + +/** + * Created by: veli + * Date: 1/19/17 5:05 PM + */ +@AndroidEntryPoint +class TextEditorActivity : Activity(), SnackbarPlacementProvider { + private lateinit var editText: EditText + + private var text: SharedText? = null + + private var backPressTime: Long = 0 + + private val pickClient = registerForActivityResult(PickClient()) { clientRoute -> + val text: String? = if (editText.text != null) editText.text.toString() else null + + if (clientRoute == null || text == null) return@registerForActivityResult + + // FIXME: 2/26/21 Improve text sharing task with coroutines + //runUiTask(TextShareTask(client, address, text)) + } + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (intent == null || ACTION_EDIT_TEXT != intent.action) finish() else { + setContentView(R.layout.layout_text_editor_activity) + editText = findViewById(R.id.layout_text_editor_activity_text_text_box) + + if (intent.hasExtra(EXTRA_TEXT_MODEL)) { + text = intent.getParcelableExtra(EXTRA_TEXT_MODEL) + text?.let { + editText.text.clear() + editText.text.append(it.text) + } + } else if (intent.hasExtra(EXTRA_TEXT)) { + editText.text.append(intent.getStringExtra(EXTRA_TEXT)) + } + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + super.onCreateOptionsMenu(menu) + menuInflater.inflate(R.menu.actions_text_editor, menu) + return true + } + + override fun onBackPressed() { + val hasObject = text != null + val deletionNeeded = checkDeletionNeeded() + val saveNeeded = checkSaveNeeded() + if (!saveNeeded && !deletionNeeded || System.nanoTime() - backPressTime < 2e9) { + super.onBackPressed() + } else if (deletionNeeded) { + createSnackbar(R.string.ques_deleteEmptiedText) + .setAction(R.string.butn_delete) { + removeText() + finish() + } + .show() + } else { + createSnackbar(if (hasObject) R.string.mesg_clipboardUpdateNotice else R.string.mesg_textSaveNotice) + .setAction(if (hasObject) R.string.butn_update else R.string.butn_save) { + saveText() + finish() + } + .show() + } + backPressTime = System.nanoTime() + } + + override fun onPrepareOptionsMenu(menu: Menu): Boolean { + menu.findItem(R.id.menu_action_save) + .setVisible(!checkDeletionNeeded()).isEnabled = checkSaveNeeded() + menu.findItem(R.id.menu_action_remove).isVisible = text != null + menu.findItem(R.id.menu_action_show_as_qr_code).isEnabled = (editText.length() in 1..1200) + return super.onPrepareOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + if (id == R.id.menu_action_save) { + saveText() + Snackbar.make(findViewById(android.R.id.content), R.string.mesg_textStreamSaved, Snackbar.LENGTH_LONG) + .show() + } else if (id == R.id.menu_action_copy) { + (getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) + .setPrimaryClip(ClipData.newPlainText("copiedText", editText.getText().toString())) + createSnackbar(R.string.mesg_textCopiedToClipboard).show() + } else if (id == R.id.menu_action_share) { + val shareIntent: Intent = Intent(Intent.ACTION_SEND) + .putExtra(Intent.EXTRA_TEXT, editText.getText().toString()) + .setType("text/*") + startActivity(Intent.createChooser(shareIntent, getString(R.string.text_fileShareAppChoose))) + } else if (id == R.id.menu_action_share_trebleshot) { + pickClient.launch(PickClient.ConnectionMode.Return) + } else if (id == R.id.menu_action_show_as_qr_code) { + if (editText.length() in 1..1200) { + val formatWriter = MultiFormatWriter() + try { + val bitMatrix: BitMatrix = formatWriter.encode( + editText.text.toString(), + BarcodeFormat.QR_CODE, 800, 800 + ) + val encoder = BarcodeEncoder() + val bitmap: Bitmap = encoder.createBitmap(bitMatrix) + val dialog = BottomSheetDialog(this) + val view = LayoutInflater.from(this).inflate(R.layout.layout_show_text_as_qr_code, null) + val qrImage = view.findViewById(R.id.layout_show_text_as_qr_code_image) + GlideApp.with(this) + .load(bitmap) + .into(qrImage) + val params = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + dialog.setTitle(R.string.butn_showAsQrCode) + dialog.setContentView(view, params) + dialog.show() + } catch (e: WriterException) { + Toast.makeText(this, R.string.mesg_somethingWentWrong, Toast.LENGTH_SHORT).show() + } + } + } else if (id == android.R.id.home) { + onBackPressed() + } else if (id == R.id.menu_action_remove) { + removeText() + } else return super.onOptionsItemSelected(item) + return true + } + + private fun checkDeletionNeeded(): Boolean { + val editorText: String = editText.text.toString() + return editorText.isEmpty() && !text?.text.isNullOrEmpty() + } + + private fun checkSaveNeeded(): Boolean { + val editorText: String = editText.text.toString() + return editorText.isNotEmpty() && editorText != text?.text + } + + override fun createSnackbar(resId: Int, vararg objects: Any?): Snackbar { + return Snackbar.make(findViewById(android.R.id.content), getString(resId, *objects), Snackbar.LENGTH_LONG) + } + + private fun removeText() { + text?.let { + lifecycle.coroutineScope.launch { + sharedTextRepository.delete(it) + text = null + } + } + } + + private fun saveText() { + val text = editText.text.toString() + val date = System.currentTimeMillis() + var update = false + val item = this.text?.also { + it.modified = date + it.text = text + update = true + } ?: SharedText(0, text, date).also { + this.text = it + } + + lifecycleScope.launch { + if (update) sharedTextRepository.update(item) else sharedTextRepository.insert(item) + } + } + + companion object { + private val TAG = TextEditorActivity::class.simpleName + + const val ACTION_EDIT_TEXT = "genonbeta.intent.action.EDIT_TEXT" + + const val EXTRA_TEXT = "extraText" + + const val EXTRA_TEXT_MODEL = "extraTextModel" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/TransferDetailActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/TransferDetailActivity.kt new file mode 100755 index 000000000..dcbcf0996 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/TransferDetailActivity.kt @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.Button +import android.widget.Toast +import androidx.appcompat.widget.Toolbar +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.genonbeta.android.framework.io.StreamInfo +import com.genonbeta.android.framework.ui.callback.SnackbarPlacementProvider +import com.google.android.material.snackbar.Snackbar +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.data.TransferRepository +import org.monora.uprotocol.client.android.database.model.Transfer +import org.monora.uprotocol.client.android.database.model.UTransferItem +import org.monora.uprotocol.client.android.dialog.Dialogs +import org.monora.uprotocol.client.android.fragment.TransferItemExplorerFragment +import org.monora.uprotocol.client.android.fragment.TransferItemListFragment +import org.monora.uprotocol.client.android.service.backgroundservice.AsyncTask +import org.monora.uprotocol.client.android.service.backgroundservice.AttachedTaskListener +import org.monora.uprotocol.client.android.service.backgroundservice.BaseAttachableAsyncTask +import org.monora.uprotocol.client.android.service.backgroundservice.TaskMessage +import org.monora.uprotocol.client.android.task.FileTransferTask +import org.monora.uprotocol.client.android.util.Files +import org.monora.uprotocol.client.android.util.Resources.attrToRes +import org.monora.uprotocol.client.android.util.Resources.resToColor +import javax.inject.Inject + +/** + * Created by: veli + * Date: 5/23/17 1:43 PM + */ +@AndroidEntryPoint +class TransferDetailActivity : Activity(), SnackbarPlacementProvider, AttachedTaskListener { + @Inject + lateinit var transferRepository: TransferRepository + + private var backPressedListener: OnBackPressedListener? = null + + private var transfer: Transfer? = null + + private lateinit var openWebShareButton: Button + + private lateinit var noDevicesNoticeText: View + + private lateinit var retryMenu: MenuItem + + private lateinit var showFilesMenu: MenuItem + + private lateinit var addDeviceMenu: MenuItem + + private lateinit var toggleBrowserShare: MenuItem + + private var colorActive = 0 + + private var colorNormal = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_view_transfer) + + var transferItem: UTransferItem? = null + val toolbar = findViewById(R.id.toolbar) + val intentData = intent.data + + setSupportActionBar(toolbar) + + transfer = savedInstanceState?.getParcelable(EXTRA_TRANSFER) + openWebShareButton = findViewById(R.id.activity_transfer_detail_open_web_share_button) + noDevicesNoticeText = findViewById(R.id.activity_transfer_detail_no_devices_warning) + + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + colorActive = R.attr.colorError.attrToRes(this).resToColor(this) + colorNormal = R.attr.colorAccent.attrToRes(this).resToColor(this) + + openWebShareButton.setOnClickListener { + startActivity(Intent(this, WebShareActivity::class.java)) + } + + if (Intent.ACTION_VIEW == intent.action && intentData != null) { + try { + val streamInfo = StreamInfo.from(this, intentData) + + // TODO: 3/12/21 Suspending transfer + /** + transferItem = transferRepository.getTransferItem( + streamInfo.friendlyName, TransferItem.Type.Incoming + ) ?: throw java.lang.Exception("File is not found in the database") + + transfer = transferRepository.getTransfer( + transferItem.itemGroupId + ) ?: throw TransferNotFoundException(transferItem.itemGroupId) + **/ + } catch (e: Exception) { + e.printStackTrace() + Toast.makeText(this, R.string.mesg_notValidTransfer, Toast.LENGTH_SHORT).show() + } + } else if (ACTION_LIST_TRANSFERS == intent.action && intent.hasExtra(EXTRA_TRANSFER)) { + transfer = intent.getParcelableExtra(EXTRA_TRANSFER) + } + + val transfer = transfer + + if (transfer == null) + finish() + else { + if (transferItem != null) { + // TODO: 2/25/21 Display the transfer item info + } + + val bundle = Bundle() + bundle.putLong(TransferItemListFragment.ARG_TRANSFER_ID, transfer.id) + bundle.putString( + TransferItemListFragment.ARG_PATH, + transferItem?.location + ) + var fragment: TransferItemExplorerFragment? = getExplorerFragment() + if (fragment == null) { + fragment = supportFragmentManager.fragmentFactory.instantiate( + classLoader, + TransferItemExplorerFragment::class.java.name + ) as TransferItemExplorerFragment + fragment.arguments = bundle + val transaction = supportFragmentManager.beginTransaction() + transaction.add(R.id.activity_transaction_content_frame, fragment) + transaction.commit() + } + attachListeners(fragment) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable(EXTRA_TRANSFER, transfer) + } + + override fun onAttachTasks(taskList: List) { + super.onAttachTasks(taskList) + for (attachableAsyncTask in taskList) { + if (attachableAsyncTask is FileTransferTask) attachableAsyncTask.anchor = this + } + if (!hasTaskOf(FileTransferTask::class.java)) { + // TODO: 2/25/21 No tasks update UI + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + super.onCreateOptionsMenu(menu) + menuInflater.inflate(R.menu.actions_transfer, menu) + retryMenu = menu.findItem(R.id.actions_transfer_receiver_retry_receiving) + showFilesMenu = menu.findItem(R.id.actions_transfer_receiver_show_files) + addDeviceMenu = menu.findItem(R.id.actions_transfer_sender_add_device) + toggleBrowserShare = menu.findItem(R.id.actions_transfer_toggle_browser_share) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + val transfer = transfer + + if (id == android.R.id.home) { + finish() + } else if (id == R.id.actions_transfer_remove) { + if (transfer != null) { + Dialogs.showRemoveDialog(this, transfer) + } + } else if (id == R.id.actions_transfer_receiver_retry_receiving) lifecycleScope.launch(Dispatchers.IO) { + transfer?.let { + transferRepository.updateTemporaryFailuresAsPending(transfer.id) + createSnackbar(R.string.mesg_retryReceivingNotice)?.show() + } + } else if (id == R.id.actions_transfer_receiver_show_files) { + if (transfer != null) { + startActivity( + Intent(this, FileExplorerActivity::class.java) + .putExtra( + FileExplorerActivity.EXTRA_FILE_PATH, + Files.getSavePath(this, transfer).getUri() + ) + ) + } + } else if (id == R.id.actions_transfer_sender_add_device) { + // TODO: 2/25/21 add device + //startDeviceAddingActivity() + } else if (item.itemId == R.id.actions_transfer_toggle_browser_share) { + lifecycleScope.launch { + transfer?.let { + it.web = !it.web + transferRepository.update(it) + } + } + } else { + return super.onOptionsItemSelected(item) + } + return true + } + + override fun onBackPressed() { + if (backPressedListener == null || !backPressedListener!!.onBackPressed()) super.onBackPressed() + } + + override fun onTaskStateChange(task: BaseAttachableAsyncTask, state: AsyncTask.State) { + if (task is FileTransferTask) { + when (state) { + AsyncTask.State.Finished, AsyncTask.State.Starting -> { + // TODO: 2/25/21 Update the menus + } + } + } + } + + override fun onTaskMessage(taskMessage: TaskMessage): Boolean { + runOnUiThread { taskMessage.toDialogBuilder(this).show() } + return true + } + + private fun attachListeners(initiatedItem: Fragment?) { + backPressedListener = if (initiatedItem is OnBackPressedListener) initiatedItem else null + } + + override fun createSnackbar(resId: Int, vararg objects: Any?): Snackbar? { + val explorerFragment = supportFragmentManager.findFragmentById( + R.id.activity_transaction_content_frame + ) as TransferItemExplorerFragment? + + return if (explorerFragment != null && explorerFragment.isAdded) { + explorerFragment.createSnackbar(resId, *objects) + } else Snackbar.make( + findViewById(R.id.activity_transaction_content_frame), getString(resId, *objects), + Snackbar.LENGTH_LONG + ) + } + + fun getExplorerFragment(): TransferItemExplorerFragment? { + return supportFragmentManager.findFragmentById( + R.id.activity_transaction_content_frame + ) as TransferItemExplorerFragment? + } + + companion object { + private val TAG = TransferDetailActivity::class.simpleName + + const val ACTION_LIST_TRANSFERS = "org.monora.uprotocol.client.android.action.LIST_TRANSFERS" + + const val EXTRA_TRANSFER = "extraTransfer" + + const val EXTRA_DEVICE = "extraDevice" + + const val REQUEST_ADD_DEVICES = 5045 + + fun startInstance(context: Context, transfer: Transfer?) { + context.startActivity( + Intent(context, TransferDetailActivity::class.java) + .setAction(ACTION_LIST_TRANSFERS) + .putExtra(EXTRA_TRANSFER, transfer) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/TransferHistoryActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/TransferHistoryActivity.kt new file mode 100644 index 000000000..0b2d26881 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/TransferHistoryActivity.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.os.Bundle +import android.view.MenuItem +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity + +class TransferHistoryActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_transfer_history) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + if (id == android.R.id.home) { + finish() + } else { + return super.onOptionsItemSelected(item) + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/TransferMemberActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/TransferMemberActivity.kt new file mode 100644 index 000000000..7cbbeecca --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/TransferMemberActivity.kt @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.content.Context +import android.content.Intent +import android.content.res.ColorStateList +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.ProgressBar +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.Toolbar +import com.genonbeta.android.framework.ui.callback.SnackbarPlacementProvider +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton +import com.google.android.material.snackbar.Snackbar +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.activity.result.contract.PickClient +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.database.model.Transfer +import org.monora.uprotocol.client.android.fragment.TransferMemberListFragment +import org.monora.uprotocol.client.android.service.backgroundservice.AsyncTask +import org.monora.uprotocol.client.android.service.backgroundservice.AttachedTaskListener +import org.monora.uprotocol.client.android.service.backgroundservice.BaseAttachableAsyncTask +import org.monora.uprotocol.client.android.service.backgroundservice.TaskMessage +import org.monora.uprotocol.client.android.task.AddDeviceTask +import org.monora.uprotocol.client.android.task.OrganizeLocalSharingTask +import org.monora.uprotocol.client.android.util.Resources.attrToRes +import org.monora.uprotocol.client.android.util.Resources.resToColor + +class TransferMemberActivity : Activity(), SnackbarPlacementProvider, AttachedTaskListener { + private val transfer: Transfer? by lazy { + intent?.getParcelableExtra(EXTRA_TRANSFER) + } + + private val addingInitialDevice by lazy { + intent?.getBooleanExtra(EXTRA_ADDING_FIRST_DEVICE, false) ?: false + } + + private val pickClient = registerForActivityResult(PickClient()) { clientRoute -> + if (clientRoute != null) transfer?.let { + runUiTask(AddDeviceTask(it, clientRoute.client, clientRoute.address)) + } + } + + private lateinit var actionButton: ExtendedFloatingActionButton + + private lateinit var progressBar: ProgressBar + + private var colorActive = 0 + + private var colorNormal = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_add_devices_to_transfer) + val toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + if (addingInitialDevice) { + pickClient.launch(PickClient.ConnectionMode.Return) + } + + colorActive = R.attr.colorError.attrToRes(this).resToColor(this) + colorNormal = R.attr.colorAccent.attrToRes(this).resToColor(this) + progressBar = findViewById(R.id.progressBar) + actionButton = findViewById(R.id.content_fab) + + actionButton.setOnClickListener { + if (hasTaskOf(OrganizeLocalSharingTask::class.java)) { + interruptAllTasks(true) + } else { + pickClient.launch(PickClient.ConnectionMode.Return) + } + } + + supportFragmentManager.findFragmentById(R.id.membersListFragment) ?: run { + val memberListFragment = supportFragmentManager.fragmentFactory.instantiate( + this.classLoader, TransferMemberListFragment::class.java.name + ) as TransferMemberListFragment + + val transaction = supportFragmentManager.beginTransaction() + transaction.add(R.id.membersListFragment, memberListFragment) + transaction.commit() + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + if (id == android.R.id.home || id == R.id.actions_add_devices_done) { + interruptAllTasks(true) + finish() + } else if (id == R.id.actions_add_devices_help) { + AlertDialog.Builder(this) + .setTitle(R.string.text_help) + .setMessage(R.string.text_addDeviceHelp) + .setPositiveButton(R.string.butn_close, null) + .show() + } else + return super.onOptionsItemSelected(item) + return true + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + super.onCreateOptionsMenu(menu) + menuInflater.inflate(R.menu.actions_add_devices, menu) + return true + } + + override fun onAttachTasks(taskList: List) { + super.onAttachTasks(taskList) + for (task in taskList) if (task is AddDeviceTask) task.anchor = this + } + + override fun onTaskStateChange(task: BaseAttachableAsyncTask, state: AsyncTask.State) { + if (task is AddDeviceTask) { + when (state) { + AsyncTask.State.Starting -> setLoaderShowing(true) + AsyncTask.State.Running -> { + } + AsyncTask.State.Finished -> setLoaderShowing(false) + } + } + } + + override fun onTaskMessage(taskMessage: TaskMessage): Boolean { + if (taskMessage.sizeOfActions() > 1) runOnUiThread { + taskMessage.toDialogBuilder(this).show() + } else if (taskMessage.sizeOfActions() <= 1) runOnUiThread { + taskMessage.toSnackbar(findViewById(R.id.content_fab)).show() + } else + return false + return true + } + + override fun createSnackbar(resId: Int, vararg objects: Any?): Snackbar { + return Snackbar.make(findViewById(R.id.container), getString(resId, *objects), Snackbar.LENGTH_LONG) + } + + private fun setLoaderShowing(showing: Boolean) { + progressBar.visibility = if (showing) View.VISIBLE else View.GONE + actionButton.setIconResource(if (showing) R.drawable.ic_close_white_24dp else R.drawable.ic_add_white_24dp) + actionButton.setText(if (showing) R.string.butn_cancel else R.string.butn_addMore) + actionButton.backgroundTintList = ColorStateList.valueOf(if (showing) colorActive else colorNormal) + } + + companion object { + private val TAG = TransferMemberActivity::class.simpleName + + const val EXTRA_DEVICE = "extraDevice" + + const val EXTRA_TRANSFER = "extraTransfer" + + const val EXTRA_ADDING_FIRST_DEVICE = "extraAddingFirstDevice" + + fun startInstance(context: Context, transfer: Transfer, addingFirstDevice: Boolean) { + context.startActivity( + Intent(context, TransferMemberActivity::class.java) + .putExtra(EXTRA_TRANSFER, transfer) + .putExtra(EXTRA_ADDING_FIRST_DEVICE, addingFirstDevice) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/WebShareActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/WebShareActivity.kt new file mode 100644 index 000000000..f7b7fe862 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/WebShareActivity.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.os.Bundle +import android.view.MenuItem +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity + +class WebShareActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_web_share) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) + finish() + else + return super.onOptionsItemSelected(item) + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/WelcomeActivity.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/WelcomeActivity.kt new file mode 100644 index 000000000..8a6b68b89 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/WelcomeActivity.kt @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.activity + +import android.content.Intent +import android.content.res.ColorStateList +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.* +import android.view.View +import android.view.ViewGroup +import android.view.animation.AnimationUtils.* +import android.widget.ImageView +import android.widget.ProgressBar +import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.content.edit +import androidx.core.graphics.drawable.DrawableCompat +import androidx.transition.TransitionManager +import androidx.viewpager.widget.ViewPager +import androidx.viewpager.widget.ViewPager.OnPageChangeListener +import com.google.android.material.floatingactionbutton.FloatingActionButton +import dagger.hilt.android.AndroidEntryPoint +import org.monora.uprotocol.client.android.GlideApp +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.data.UserDataRepository +import org.monora.uprotocol.client.android.databinding.LayoutProfileEditorBinding +import org.monora.uprotocol.client.android.util.Graphics +import org.monora.uprotocol.client.android.util.Permissions +import org.monora.uprotocol.client.android.util.Resources.attrToRes +import org.monora.uprotocol.client.android.util.Resources.resToColor +import org.monora.uprotocol.client.android.viewmodel.UserProfileViewModel +import org.monora.uprotocol.client.android.widget.ViewPagerAdapter +import javax.inject.Inject + +@AndroidEntryPoint +class WelcomeActivity : Activity() { + @Inject + lateinit var userDataRepository: UserDataRepository + + private val userProfileViewModel: UserProfileViewModel by viewModels() + + private val splashView: ViewGroup by lazy { + layoutInflater.inflate(R.layout.layout_splash, null, false) as ViewGroup + } + + private val pickPhoto = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> + if (uri != null) { + Graphics.saveClientPictureLocal(applicationContext, uri) + } + } + + private val profileViewBinding: LayoutProfileEditorBinding by lazy { + LayoutProfileEditorBinding.inflate(layoutInflater, null, false).also { + it.viewModel = userProfileViewModel + it.lifecycleOwner = this + it.pickPhotoClickListener = View.OnClickListener { + pickPhoto.launch("image/*") + } + } + } + + private val permissionsView by lazy { + layoutInflater.inflate(R.layout.layout_permissions, null, false).also { + it.findViewById(R.id.requestButton).setOnClickListener { + requestRequiredPermissions(false) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (hasIntroductionShown()) { + finish() + } + + setContentView(R.layout.activity_welcome) + + skipPermissionRequest = true + welcomePageDisallowed = true + + val confettiImageView: ImageView = findViewById(R.id.confetti) + val titleTextView: TextView = findViewById(R.id.textView) + val nextButton: FloatingActionButton = findViewById(R.id.nextButton) + val previousButton: AppCompatImageView = findViewById(R.id.previousButton) + val progressBar = findViewById(R.id.progressBar) + val viewPager: ViewPager = findViewById(R.id.activity_welcome_view_pager) + val pagerAdapter = ViewPagerAdapter() + val appliedColor: Int = R.attr.colorSecondary.attrToRes(this).resToColor(this) + val titleList = mutableListOf().apply { + add(R.string.text_welcome) + if (Build.VERSION.SDK_INT >= 23) add(R.string.text_introSetUpPermissions) + add(R.string.text_introSetUpProfile) + add(R.string.text_introMakeItYours) + add(R.string.text_introAllSet) + }.map { getString(it) } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + val wrapDrawable: Drawable = DrawableCompat.wrap(progressBar.progressDrawable) + DrawableCompat.setTint(wrapDrawable, appliedColor) + progressBar.progressDrawable = DrawableCompat.unwrap(wrapDrawable) + } else { + progressBar.progressTintList = ColorStateList.valueOf(appliedColor) + } + + pagerAdapter.addView(splashView) + + if (Build.VERSION.SDK_INT >= 23) { + pagerAdapter.addView(permissionsView) + checkPermissionsState() + } + + pagerAdapter.addView(profileViewBinding.root) + profileViewBinding.executePendingBindings() + + pagerAdapter.addView(layoutInflater.inflate(R.layout.layout_look_preferences, null, false)) + + layoutInflater.inflate(R.layout.layout_nyancat, null, false).also { + pagerAdapter.addView(it) + GlideApp.with(it.context) + .load(R.drawable.nyancat) + .into(it.findViewById(R.id.nyancat)) + } + + progressBar.max = (pagerAdapter.count - 1) * 100 + + previousButton.setOnClickListener { + if (viewPager.currentItem - 1 >= 0) viewPager.setCurrentItem( + viewPager.currentItem - 1, true + ) + } + + nextButton.setOnClickListener { + if (viewPager.currentItem + 1 < pagerAdapter.count) viewPager.currentItem = + viewPager.currentItem + 1 else { + // end presentation + defaultPreferences.edit { + putBoolean("introduction_shown", true) + } + startActivity(Intent(this@WelcomeActivity, HomeActivity::class.java)) + finish() + } + } + + viewPager.addOnPageChangeListener(object : OnPageChangeListener { + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { + progressBar.progress = position * 100 + (positionOffset * 100).toInt() + if (position == 0) { + progressBar.alpha = positionOffset + previousButton.alpha = positionOffset + titleTextView.alpha = positionOffset + } else { + progressBar.alpha = 1.0f + previousButton.alpha = 1.0f + titleTextView.alpha = 1.0f + } + } + + override fun onPageSelected(position: Int) { + val lastPage = position == pagerAdapter.count - 1 + TransitionManager.beginDelayedTransition(titleTextView.parent as ViewGroup) + + titleTextView.text = titleList[position] + progressBar.visibility = if (lastPage) View.GONE else View.VISIBLE + + nextButton.setImageResource( + if (lastPage) { + R.drawable.ic_check_white_24dp + } else { + R.drawable.ic_navigate_next_white_24dp + } + ) + + if (lastPage) { + GlideApp.with(applicationContext) + .load(R.drawable.confetti) + .into(confettiImageView) + } else { + confettiImageView.setImageDrawable(null) + } + } + + override fun onPageScrollStateChanged(state: Int) {} + }) + + viewPager.adapter = pagerAdapter + } + + override fun onStart() { + super.onStart() + slideSplashView() + } + + override fun onResume() { + super.onResume() + checkPermissionsState() + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + checkPermissionsState() + } + + private fun checkPermissionsState() { + if (Build.VERSION.SDK_INT < 23) return + + val ok = Permissions.checkRunningConditions(this) + permissionsView.findViewById(R.id.okImage).visibility = if (ok) View.VISIBLE else View.GONE + permissionsView.findViewById(R.id.requestButton).visibility = if (ok) View.GONE else View.VISIBLE + } + + private fun slideSplashView() { + splashView.findViewById(R.id.logo).animation = loadAnimation( + this, R.anim.enter_from_bottom_centered + ) + splashView.findViewById(R.id.appBrand).animation = loadAnimation(this, R.anim.enter_from_bottom) + splashView.findViewById(R.id.welcomeText).animation = loadAnimation( + this, android.R.anim.fade_in + ).also { it.startOffset = 1000 } + } + + companion object { + private val TAG = WelcomeActivity::class.simpleName + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/activity/result/contract/PickClient.kt b/app/src/main/java/org/monora/uprotocol/client/android/activity/result/contract/PickClient.kt new file mode 100644 index 000000000..40c8c6441 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/activity/result/contract/PickClient.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.activity.result.contract + +import android.app.Activity +import android.content.Context +import android.content.Intent +import androidx.activity.result.contract.ActivityResultContract +import androidx.appcompat.app.AppCompatActivity +import org.monora.uprotocol.client.android.activity.PickClientActivity +import org.monora.uprotocol.client.android.database.model.UClient +import org.monora.uprotocol.client.android.database.model.UClientAddress +import org.monora.uprotocol.client.android.model.ClientRoute + +class PickClient : ActivityResultContract() { + override fun createIntent(context: Context, input: ConnectionMode): Intent = when (input) { + else -> Intent(context, PickClientActivity::class.java).putExtra(EXTRA_CONNECTION_MODE, input) + } + + override fun parseResult(resultCode: Int, intent: Intent?): ClientRoute? { + if (resultCode != Activity.RESULT_OK) return null + + val client: UClient? = intent?.getParcelableExtra(EXTRA_CLIENT) + val address: UClientAddress? = intent?.getParcelableExtra(EXTRA_CLIENT_ADDRESS) + + return if (client != null && address != null) ClientRoute(client, address) else null + } + + enum class ConnectionMode { + WaitForRequests, Return + } + + companion object { + private const val EXTRA_CLIENT = "extraClient" + + private const val EXTRA_CLIENT_ADDRESS = "extraClientAddress" + + const val EXTRA_CONNECTION_MODE = "extraConnectionMode" + + fun returnResult(activity: Activity, clientRoute: ClientRoute) { + returnResult(activity, clientRoute.client, clientRoute.address) + } + + fun returnResult(activity: Activity, client: UClient?, address: UClientAddress?) { + activity.setResult( + AppCompatActivity.RESULT_OK, Intent() + .putExtra(EXTRA_CLIENT, client) + .putExtra(EXTRA_CLIENT_ADDRESS, address) + ) + activity.finish() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/adapter/ActiveConnectionListAdapter.kt b/app/src/main/java/org/monora/uprotocol/client/android/adapter/ActiveConnectionListAdapter.kt new file mode 100644 index 000000000..14649ab24 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/adapter/ActiveConnectionListAdapter.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.adapter + +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import com.genonbeta.android.framework.widget.RecyclerViewAdapter.ViewHolder +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.IListingFragment +import org.monora.uprotocol.client.android.config.AppConfig +import org.monora.uprotocol.client.android.model.NetworkInterfaceModel +import org.monora.uprotocol.client.android.util.Networks +import org.monora.uprotocol.client.android.util.TextManipulators +import org.monora.uprotocol.client.android.util.TextManipulators.toNetworkTitle +import org.monora.uprotocol.client.android.widget.ListingAdapter +import java.net.NetworkInterface +import java.util.* + +/** + * created by: veli + * date: 4/7/19 10:35 PM + */ +class ActiveConnectionListAdapter( + fragment: IListingFragment, +) : ListingAdapter(fragment) { + override fun onLoad(): MutableList { + val resultList: MutableList = ArrayList() + val interfaceList: List = Networks.getInterfaces( + true, + AppConfig.DEFAULT_DISABLED_INTERFACES + ) + val filteringDelegate = fragment.filteringDelegate + for (networkInterface in interfaceList) { + val editableInterface = NetworkInterfaceModel( + networkInterface, networkInterface.toNetworkTitle(context) + ) + if (filteringDelegate.disabled() || filteringDelegate.filter(fragment, editableInterface)) { + resultList.add(editableInterface) + } + } + return resultList + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val holder = ViewHolder(layoutInflater.inflate(R.layout.list_active_connection, parent, false)) + fragment.registerLayoutViewClicks(holder) + holder.itemView.findViewById(R.id.visitView) + .setOnClickListener { v: View? -> fragment.performLayoutClickOpen(holder) } + holder.itemView.findViewById(R.id.selector) + .setOnClickListener { v: View? -> fragment.setItemSelected(holder, true) } + return holder + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = getItem(position) + val text1: TextView = holder.itemView.findViewById(R.id.text) + val text2: TextView = holder.itemView.findViewById(R.id.text2) + val firstAddress = Networks.getFirstInet4Address(item) + text1.text = item.name() + text2.text = firstAddress?.let { TextManipulators.makeWebShareLink(context, it.hostAddress) } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/adapter/ApplicationListAdapter.kt b/app/src/main/java/org/monora/uprotocol/client/android/adapter/ApplicationListAdapter.kt new file mode 100755 index 000000000..b841506e4 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/adapter/ApplicationListAdapter.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.adapter + +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.os.Build +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.preference.PreferenceManager +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import org.monora.uprotocol.client.android.GlideApp +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.IListingFragment +import org.monora.uprotocol.client.android.model.AppPackageModel +import org.monora.uprotocol.client.android.widget.ListingAdapter +import com.genonbeta.android.framework.widget.RecyclerViewAdapter + +class ApplicationListAdapter( + fragment: IListingFragment, +) : ListingAdapter(fragment) { + private val preferences = PreferenceManager.getDefaultSharedPreferences(context) + + private val manager = context.packageManager + + override fun onLoad(): MutableList { + val list = ArrayList() + val showSystemApps = preferences.getBoolean("show_system_apps", false) + + for (packageInfo in manager.getInstalledPackages(PackageManager.GET_META_DATA)) { + try { + val info = packageInfo.applicationInfo + if (info.flags and ApplicationInfo.FLAG_SYSTEM == 1 && !showSystemApps) continue + + list.add(AppPackageModel(packageInfo, info, info.loadLabel(manager).toString())) + } catch (e: Throwable) { + e.printStackTrace() + } + } + return list + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(layoutInflater.inflate(R.layout.list_application, parent, false)).also { + fragment.registerLayoutViewClicks(it) + it.itemView.findViewById(R.id.visitView).setOnClickListener { v: View? -> + fragment.performLayoutClickOpen(it) + } + it.itemView.findViewById(R.id.selector).setOnClickListener { v: View? -> + fragment.setItemSelected(it, true) + } + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val parentView: View = holder.itemView + val item = getItem(position) + val image: ImageView = parentView.findViewById(R.id.image) + val text1: TextView = parentView.findViewById(R.id.text) + val text2: TextView = parentView.findViewById(R.id.text2) + val layoutSplitApk = parentView.findViewById(R.id.layout_split_apk) + val isSplitApk = Build.VERSION.SDK_INT >= 21 && item.applicationInfo.splitSourceDirs != null + text1.text = item.name() + text2.text = item.packageInfo.versionName + layoutSplitApk.visibility = if (isSplitApk) View.VISIBLE else View.GONE + parentView.isSelected = item.selected() + GlideApp.with(context) + .load(item.applicationInfo) + .transition(DrawableTransitionOptions.withCrossFade()) + .override(160) + .into(image) + } +} + diff --git a/app/src/main/java/org/monora/uprotocol/client/android/adapter/AudioListAdapter.kt b/app/src/main/java/org/monora/uprotocol/client/android/adapter/AudioListAdapter.kt new file mode 100755 index 000000000..a4e023174 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/adapter/AudioListAdapter.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.adapter + +import android.content.ContentResolver +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.app.IListingFragment +import org.monora.uprotocol.client.android.model.AudioMediaModel +import org.monora.uprotocol.client.android.widget.ListingAdapter +import com.genonbeta.android.framework.widget.RecyclerViewAdapter.ViewHolder +import java.io.File + +class AudioListAdapter( + fragment: IListingFragment, +) : ListingAdapter(fragment) { + private val resolver: ContentResolver = context.contentResolver + + override fun onLoad(): MutableList { + TODO("Not yet implemented") + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(layoutInflater.inflate(R.layout.list_music, parent, false)).also { holder -> + fragment.registerLayoutViewClicks(holder) + holder.itemView.findViewById(R.id.visitView).setOnClickListener { + fragment.performLayoutClickOpen(holder) + } + holder.itemView.findViewById(R.id.selector).setOnClickListener { + fragment.setItemSelected(holder, true) + } + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val itemHolder = getItem(position) + val parentView = holder.itemView + val image = parentView.findViewById(R.id.image) + val text1: TextView = parentView.findViewById(R.id.text) + val text2: TextView = parentView.findViewById(R.id.text2) + val text3: TextView = parentView.findViewById(R.id.text3) + val textSeparator1: TextView = parentView.findViewById(R.id.textSeparator1) + text1.text = itemHolder.song + text2.text = itemHolder.artist + text3.text = itemHolder.album + text3.visibility = View.VISIBLE + textSeparator1.visibility = View.VISIBLE + parentView.isSelected = itemHolder.selected() + // FIXME: 2/21/21 Album art loading + /**GlideApp.with(context) + .load(itemHolder.albumHolder.art) + .placeholder(R.drawable.ic_music_note_white_24dp) + .override(160) + .centerCrop() + .into(image)**/ + } + + fun extractFolderName(folder: String): String { + if (folder.contains(File.separator)) { + val split = folder.split(File.separator.toRegex()).toTypedArray() + if (split.size >= 2) return split[split.size - 2] + } + return folder + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/adapter/FileListAdapter.kt b/app/src/main/java/org/monora/uprotocol/client/android/adapter/FileListAdapter.kt new file mode 100755 index 000000000..0025256c3 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/adapter/FileListAdapter.kt @@ -0,0 +1,515 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.adapter + +import android.view.ViewGroup +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.adapter.FileListAdapter.FileHolder +import org.monora.uprotocol.client.android.app.IListingFragment +import org.monora.uprotocol.client.android.widget.ListingAdapter +import com.genonbeta.android.framework.io.DocumentFile +import com.genonbeta.android.framework.widget.RecyclerViewAdapter +import org.monora.uprotocol.client.android.backend.Destination +import org.monora.uprotocol.client.android.backend.OperationBackend +import org.monora.uprotocol.client.android.backend.SharingBackend +import org.monora.uprotocol.client.android.model.ContentModel + +class FileListAdapter( + fragment: IListingFragment, +) : ListingAdapter(fragment) { + var path: DocumentFile? = null + + var showDirectories = true + + var showFiles = true + + private var showThumbnails = true + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(layoutInflater.inflate(R.layout.list_selection, parent, false)) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + TODO("Not yet implemented") + } + + override fun onLoad(): MutableList = ArrayList() + + class FileHolder : ContentModel { + override fun canCopy(): Boolean = false + + override fun canMove(): Boolean = false + + override fun canSelect(): Boolean = false + + override fun canShare(): Boolean = false + + override fun canRemove(): Boolean = false + + override fun canRename(): Boolean = false + + override fun copy(operationBackend: OperationBackend, destination: Destination): Boolean { + TODO("Not yet implemented") + } + + override fun dateCreated(): Long = 0 + + override fun dateModified(): Long = 0 + + override fun dateSupported(): Boolean = false + + override fun filter(charSequence: CharSequence): Boolean = false + + override fun id(): Long = 0 + + override fun length(): Long = 0 + + override fun lengthSupported(): Boolean = false + + override fun move(operationBackend: OperationBackend, destination: Destination): Boolean { + TODO("Not yet implemented") + } + + override fun name(): String = "FileHolder" + + override fun remove(operationBackend: OperationBackend): Boolean { + TODO("Not yet implemented") + } + + override fun share(operationBackend: OperationBackend, sharingBackend: SharingBackend): Boolean { + TODO("Not yet implemented") + } + + override fun selected(): Boolean = false + + override fun select(selected: Boolean) { + + } + } + /* + protected override fun onLoad(lister: GroupLister) { + showThumbnails = AppUtils.getDefaultPreferences(context).getBoolean("load_thumbnails", true) + val path = path + if (path != null) { + val fileIndex = path.listFiles() + if (fileIndex.isNotEmpty()) { + for (file in fileIndex) { + val searchWord = searchWord + + if (searchWord != null && !file.getName().matches(Regex(searchWord))) continue + lister.offerObliged(this, FileHolder(context, file)) + } + } + } else { + val saveDir = FileHolder(context, Files.getApplicationDirectory(context)) + saveDir.type = FileHolder.Type.SaveLocation + lister.offerObliged(this, saveDir) + + val rootDir = File(".") + if (rootDir.canRead()) { + val rootHolder = FileHolder(context, DocumentFile.fromFile(rootDir)) + rootHolder.friendlyName = context.getString(R.string.text_fileRoot) + lister.offerObliged(this, rootHolder) + } + val referencedDirectoryList: MutableList = ArrayList() + when { + Build.VERSION.SDK_INT >= 21 -> { + referencedDirectoryList.addAll(listOf(*context.externalMediaDirs)) + } + Build.VERSION.SDK_INT >= 19 -> { + referencedDirectoryList.addAll(listOf(*context.getExternalFilesDirs(null))) + } + else -> { + referencedDirectoryList.add(Environment.getExternalStorageDirectory()) + } + } + + for (mediaDir in referencedDirectoryList) { + if (!mediaDir.canWrite()) continue + val fileHolder = FileHolder(context, DocumentFile.fromFile(mediaDir)) + fileHolder.type = FileHolder.Type.Storage + val splitPath = mediaDir.absolutePath.split(File.separator.toRegex()).toTypedArray() + if (splitPath.size >= 2 && splitPath[1] == "storage") { + if (splitPath.size >= 4 && splitPath[2] == "emulated") { + val file = File(buildPath(splitPath, 4)) + if (file.canWrite()) { + fileHolder.file = DocumentFile.fromFile(file) + fileHolder.friendlyName = + if ("0" == splitPath[3]) context.getString(R.string.text_internalStorage) else context.getString( + R.string.text_emulatedMediaDirectory, + splitPath[3] + ) + } + } else if (splitPath.size >= 3) { + val file = File(buildPath(splitPath, 3)) + if (!file.canWrite()) continue + fileHolder.friendlyName = splitPath[2] + fileHolder.file = DocumentFile.fromFile(file) + } + } + lister.offerObliged(this, fileHolder) + } + run { + val savedDirList = AppUtils.getKuick(context).castQuery( + SQLQuery.Select(Kuick.TABLE_FILEBOOKMARK), + FileHolder::class.java + ) + for (dir in savedDirList) if (dir.file != null) lister.offerObliged(this, dir) + } + if (Build.VERSION.SDK_INT >= 21) { + val mountButtonRep = FileHolder( + VIEW_TYPE_ACTION_BUTTON, + context.getString(R.string.butn_mountDirectory) + ) + mountButtonRep.requestCode = REQUEST_CODE_MOUNT_FOLDER + mountButtonRep.type = FileHolder.Type.Storage + lister.offerObliged(this, mountButtonRep) + } + run { + val objects = AppUtils.getKuick(context) + .castQuery( + SQLQuery.Select(Kuick.TABLE_TRANSFERITEM).setWhere( + String.format("%s = ?", Kuick.FIELD_TRANSFERITEM_FLAG), + TransferItem.Flag.DONE.toString() + ).setOrderBy(String.format("%s DESC", Kuick.FIELD_TRANSFERITEM_LASTCHANGETIME)), + TransferItem::class.java + ) + val pickedRecentFiles: MutableList = ArrayList() + val transferMap: MutableMap = ArrayMap() + for (transfer in AppUtils.getKuick(context).castQuery( + SQLQuery.Select(Kuick.TABLE_TRANSFER), Transfer::class.java + )) transferMap[transfer.id] = transfer + var errorLimit = 3 + for (item in objects) { + val transfer = transferMap[item.transferId] + if (pickedRecentFiles.size >= 20 || errorLimit == 0 || transfer == null) break + try { + val documentFile = Files.getIncomingPseudoFile( + context, item, transfer, + false + ) + if (documentFile.exists() && !pickedRecentFiles.contains(documentFile)) pickedRecentFiles.add( + documentFile + ) else errorLimit-- + } catch (e: IOException) { + errorLimit-- + } + } + for (documentFile in pickedRecentFiles) { + val holder = FileHolder(context, documentFile) + holder.type = FileHolder.Type.Recent + lister.offerObliged(this, holder) + } + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val holder = ViewHolder(layoutInflater.inflate(R.layout.list_file, parent, false)) + + fragment.registerLayoutViewClicks(holder) + holder.itemView.findViewById(R.id.layout_image).setOnClickListener { v: View? -> + fragment.setItemSelected(holder, true) + } + holder.itemView.findViewById(R.id.menu).setOnClickListener { v: View? -> + val fileHolder: FileHolder = getList()[holder.adapterPosition] + val file = fileHolder.file + val isFile = + FileHolder.Type.File == fileHolder.type || FileHolder.Type.Recent == fileHolder.type + || FileHolder.Type.Pending == fileHolder.type + var isMounted = FileHolder.Type.Mounted == fileHolder.type + var isBookmarked = FileHolder.Type.Bookmarked == fileHolder.type + val canWrite = file != null && file.canWrite() + val canRead = file != null && file.canRead() + if (!isMounted && !isBookmarked && file != null) try { + val dbTestObject = FileHolder(context, file) + AppUtils.getKuick(context).reconstruct(dbTestObject) + isMounted = FileHolder.Type.Mounted == dbTestObject.type + isBookmarked = FileHolder.Type.Bookmarked == dbTestObject.type + } catch (ignored: ReconstructionFailedException) { + } + val popupMenu = PopupMenu(context, v) + val menuItself = popupMenu.menu + popupMenu.menuInflater.inflate(R.menu.action_mode_file, menuItself) + menuItself.findItem(R.id.action_mode_file_open).isVisible = canRead && isFile + menuItself.findItem(R.id.action_mode_file_rename).isEnabled = ((canWrite || isMounted || isBookmarked) + && FileHolder.Type.Pending != fileHolder.type) + menuItself.findItem(R.id.action_mode_file_delete).isEnabled = canWrite && !isMounted + menuItself.findItem(R.id.action_mode_file_show).isVisible = FileHolder.Type.Recent == + fileHolder.type + menuItself.findItem(R.id.action_mode_file_change_save_path).isVisible = + FileHolder.Type.SaveLocation == fileHolder.type || fileHolder.file != null + && Files.getApplicationDirectory(context) == fileHolder.file + menuItself.findItem(R.id.action_mode_file_eject_directory).isVisible = isMounted + menuItself.findItem(R.id.action_mode_file_toggle_shortcut).setVisible(!isFile && !isMounted) + .setTitle(if (isBookmarked) R.string.butn_removeShortcut else R.string.butn_addShortcut) + popupMenu.setOnMenuItemClickListener { item: MenuItem -> + val id = item.itemId + val generateSelectionList = ArrayList() + val parentFile = fileHolder.file?.getParentFile() + val fragment = fragment + + generateSelectionList.add(fileHolder) + + if (id == R.id.action_mode_file_open) { + fragment.performLayoutClickOpen(holder, fileHolder) + } else if (id == R.id.action_mode_file_show && parentFile != null) { + goPath(parentFile) + fragment.refreshList() + } else if (id == R.id.action_mode_file_eject_directory) { + AppUtils.getKuick(context).remove(fileHolder) + AppUtils.getKuick(context).broadcast() + } else if (id == R.id.action_mode_file_toggle_shortcut) { + FileListFragment.shortcutItem(fragment, fileHolder) + } else if (id == R.id.action_mode_file_change_save_path) { + context.startActivity(Intent(context, ChangeStoragePathActivity::class.java)) + } else if (fragment is FileListFragment) + return@setOnMenuItemClickListener !FileListFragment.handleEditingAction( + item, fragment, + generateSelectionList + ) + true + } + popupMenu.show() + } + return holder + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val itemHolder: FileHolder = getItem(position) + val parentView: View = holder.itemView + val lookAltered = !showFiles || !showDirectories + val thumbnail = parentView.findViewById(R.id.thumbnail) + val image = parentView.findViewById(R.id.image) + val text1: TextView = parentView.findViewById(R.id.text) + val text2: TextView = parentView.findViewById(R.id.text2) + holder.setSelected(itemHolder.selected()) + text1.setText(itemHolder.name()) + text2.text = itemHolder.getInfo(context) + if (lookAltered) { + val file = itemHolder.file + val enabled = (file == null || showFiles && file.isFile() || showDirectories && file.isDirectory()) + text1.isEnabled = enabled + text2.isEnabled = enabled + image.alpha = if (enabled) 1f else 0.5f + thumbnail.alpha = if (enabled) 1f else 0.5f + } + if (!showThumbnails || !itemHolder.loadThumbnail(context, thumbnail)) { + image.setImageResource(itemHolder.getIconRes()) + thumbnail.setImageDrawable(null) + } else image.setImageDrawable(null) + } + + fun buildPath(splitPath: Array, count: Int): String { + val stringBuilder = StringBuilder() + var i = 0 + while (i < count && i < splitPath.size) { + stringBuilder.append(File.separator) + stringBuilder.append(splitPath[i]) + i++ + } + return stringBuilder.toString() + } + + /* + override fun getRepresentativeText(merger: Merger): String { + return if (merger is FileHolderMerger) { + when (merger.type) { + FileHolderMerger.Type.Storage -> context.getString(R.string.text_storage) + FileHolderMerger.Type.PublicFolder -> context.getString(R.string.text_shortcuts) + FileHolderMerger.Type.Folder -> context.getString(R.string.text_folder) + FileHolderMerger.Type.PartFile -> context.getString(R.string.text_pendingTransfers) + FileHolderMerger.Type.RecentFile -> context.getString(R.string.text_recentFiles) + FileHolderMerger.Type.File -> context.getString(R.string.text_file) + FileHolderMerger.Type.Dummy -> context.getString(R.string.text_unknown) + } + } else super.getRepresentativeText(merger) + }*/ + class FileHolder : ContentModel, DatabaseObject { + lateinit var file: DocumentFile + + var transferItem: TransferItem? = null + + var type: Type = Type.Dummy + get() { + val file = file + return if (field == Type.Dummy && file != null) { + if (file.isDirectory()) Type.Folder else Type.File + } else field + } + constructor(context: Context, file: DocumentFile) { + initialize(file) + calculate(context) + } + + protected fun calculate(context: Context) { + val fileLocal = file + + if (fileLocal != null && AppConfig.EXT_FILE_PART == Files.getFileFormat(fileLocal.getName())) { + type = Type.Pending + try { + val kuick = AppUtils.getKuick(context) + val data: ContentValues? = kuick.getFirstFromTable( + SQLQuery.Select(Kuick.TABLE_TRANSFERITEM) + .setWhere(Kuick.FIELD_TRANSFERITEM_FILE + "=?", fileLocal.getName()) + ) + data?.let { contentValues: ContentValues -> + transferItem = TransferItem().also { + it.reconstruct(kuick.writableDatabase, kuick, contentValues) + mimeType = it.mimeType + friendlyName = it.name + } + } + } catch (ignored: Exception) { + } + } + } + + @DrawableRes + fun getIconRes(): Int { + val fileLocal = file + + return if (fileLocal == null) 0 else if (fileLocal.isDirectory()) { + when (type) { + Type.Storage -> R.drawable.ic_save_white_24dp + Type.SaveLocation -> R.drawable.ic_uprotocol + Type.Bookmarked, Type.Mounted -> R.drawable.ic_bookmark_white_24dp + else -> R.drawable.ic_folder_white_24dp + } + } else { + if (Type.Pending == type && transferItem == null) + R.drawable.ic_block_white_24dp + else + MimeIcons.loadMimeIcon(mimeType) + } + } + + fun getInfo(context: Context): String { + val fileLocal = file + return when (type) { + Type.Storage -> context.getString(R.string.text_storage) + Type.Mounted -> context.getString(R.string.text_mountedDirectory) + Type.Bookmarked, Type.Folder, Type.Public -> if (fileLocal != null && fileLocal.isDirectory()) { + val itemSize = fileLocal.listFiles().size + context.resources.getQuantityString(R.plurals.text_items, itemSize, itemSize) + } else context.getString(R.string.text_unknown) + Type.SaveLocation -> context.getString(R.string.text_defaultFolder) + Type.Pending -> if (transferItem == null) context.getString(R.string.mesg_notValidTransfer) else String.format( + "%s / %s", com.genonbeta.android.framework.util.Files.formatLength(getComparableSize(), false), + com.genonbeta.android.framework.util.Files.formatLength(transferItem!!.getComparableSize(), false) + ) + Type.Recent, Type.File -> com.genonbeta.android.framework.util.Files.formatLength( + getComparableSize(), + false + ) + else -> context.getString(R.string.text_unknown) + } + } + + override fun getValues(): ContentValues { + val contentValues = ContentValues() + contentValues.put(Kuick.FIELD_FILEBOOKMARK_TITLE, name()) + contentValues.put(Kuick.FIELD_FILEBOOKMARK_PATH, uri.toString()) + return contentValues + } + + override fun getWhere(): SQLQuery.Select { + return SQLQuery.Select(Kuick.TABLE_FILEBOOKMARK) + .setWhere(String.format("%s = ?", Kuick.FIELD_FILEBOOKMARK_PATH), uri.toString()) + } + + fun loadThumbnail(context: Context, imageView: ImageView): Boolean { + val file = file*/ + //val mimeType = mimeType ?: "*/*" +/* + if (file == null || file.isDirectory() || Type.Pending == type || !mimeType.startsWith("image/") + && !mimeType.startsWith("video/") + ) return false + GlideApp.with(context) + .load(file.getUri()) + .error(MimeIcons.loadMimeIcon(mimeType)) + .override(160) + .circleCrop() + .into(imageView) + return true + } + + override fun reconstruct(db: SQLiteDatabase, kuick: KuickDb, values: ContentValues) { + uri = Uri.parse(values.getAsString(Kuick.FIELD_FILEBOOKMARK_PATH)) + type = if (uri.toString().startsWith("file")) Type.Bookmarked else Type.Mounted + try { + initialize(com.genonbeta.android.framework.util.Files.fromUri(kuick.context, uri)) + calculate(kuick.context) + } catch (e: FileNotFoundException) { + e.printStackTrace() + } + friendlyName = values.getAsString(Kuick.FIELD_FILEBOOKMARK_TITLE) + } + + override fun onCreateObject(db: SQLiteDatabase, kuick: KuickDb, parent: Any?, progress: Progress.Context?) {} + + override fun onUpdateObject(db: SQLiteDatabase, kuick: KuickDb, parent: Any?, progress: Progress.Context?) {} + + override fun onRemoveObject(db: SQLiteDatabase, kuick: KuickDb, parent: Any?, progress: Progress.Context?) {} + + override fun setSelectableSelected(selected: Boolean): Boolean { + return when (type) { + Type.Dummy, Type.Public, Type.Storage, Type.Mounted, Type.Bookmarked -> false + else -> super.setSelectableSelected(selected) + } + } + + enum class Type { + Storage, Bookmarked, Mounted, Public, SaveLocation, Recent, Pending, Folder, File, Dummy + } + } + + private class FileHolderMerger(holder: FileHolder) : ComparableMerger() { + var type: Type = when (holder.type) { + FileHolder.Type.Mounted, FileHolder.Type.Storage -> Type.Storage + FileHolder.Type.Public, FileHolder.Type.Bookmarked -> Type.PublicFolder + FileHolder.Type.Pending -> Type.PartFile + FileHolder.Type.Recent -> Type.RecentFile + FileHolder.Type.Folder, FileHolder.Type.SaveLocation -> Type.Folder + FileHolder.Type.File -> Type.File + FileHolder.Type.Dummy -> Type.Dummy + } + + override fun equals(other: Any?): Boolean { + return other is FileHolderMerger && other.type == type + } + + override fun hashCode(): Int { + return type.hashCode() + } + + override operator fun compareTo(other: ComparableMerger): Int { + return if (other is FileHolderMerger) + MathUtils.compare(other.type.ordinal.toLong(), type.ordinal.toLong()) + else 0 + } + + enum class Type { + Storage, Folder, PublicFolder, RecentFile, PartFile, File, Dummy + } + } + + companion object { + const val REQUEST_CODE_MOUNT_FOLDER = 1 + }*/ +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/adapter/MainFragmentStateAdapter.kt b/app/src/main/java/org/monora/uprotocol/client/android/adapter/MainFragmentStateAdapter.kt new file mode 100755 index 000000000..c1f26b6b4 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/adapter/MainFragmentStateAdapter.kt @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.adapter + +import android.content.Context +import android.os.Bundle +import android.os.Parcelable +import android.util.Log +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentFactory +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.google.android.material.bottomnavigation.BottomNavigationView +import com.google.android.material.tabs.TabLayout +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize +import java.util.* + +/** + * created by: veli + * date: 11/04/18 21:53 + */ +open class MainFragmentStateAdapter( + val context: Context, fm: FragmentManager, lifecycle: Lifecycle, +) : FragmentStateAdapter(fm, lifecycle) { + private val fragments: MutableList = ArrayList() + + private val fragmentFactory: FragmentFactory = fm.fragmentFactory + + open fun onItemInstantiated(item: PageItem) {} + + fun add(fragment: PageItem) { + fragments.add(fragment) + } + + fun add(position: Int, fragment: PageItem) { + fragments.add(position, fragment) + } + + fun createTabs(tabLayout: TabLayout, withIcon: Boolean = true, withText: Boolean = true) { + if (itemCount > 0) for (iterator in 0 until itemCount) { + val item = getItem(iterator) + val tab: TabLayout.Tab = tabLayout.newTab() + if (withIcon) tab.setIcon(item.iconRes) + if (withText || !item.iconOnly) tab.text = item.title + + tabLayout.addTab(tab) + } + } + + fun createTabs(bottomNavigationView: BottomNavigationView) { + if (itemCount > 0) for (iterator in 0 until itemCount) { + val item = getItem(iterator) + bottomNavigationView.menu.add(0, iterator, iterator, item.title).setIcon(item.iconRes) + } + } + + override fun createFragment(position: Int): Fragment { + val item = getItem(position).apply { + currentPosition = position + } + val fragment = item.fragment ?: fragmentFactory.instantiate(context.classLoader, item.clazz).apply { + arguments = item.arguments + } + Log.d(MainFragmentStateAdapter::class.simpleName, "createFragment: " + fragment.javaClass.name) + onItemInstantiated(item) + + return fragment + } + + override fun getItemCount(): Int = fragments.size + + override fun getItemId(position: Int): Long { + return getItem(position).id.toLong() + } + + fun getItem(position: Int): PageItem = synchronized(fragments) { fragments[position] } + + @Parcelize + class PageItem( + var id: Int, + var iconRes: Int, + var title: String, + var clazz: String, + var arguments: Bundle? = null, + var iconOnly: Boolean = false, + ) : Parcelable { + @IgnoredOnParcel + var fragment: Fragment? = null + + @IgnoredOnParcel + var currentPosition = -1 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/adapter/PathResolverRecyclerAdapter.kt b/app/src/main/java/org/monora/uprotocol/client/android/adapter/PathResolverRecyclerAdapter.kt new file mode 100755 index 000000000..d0a3c9c67 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/adapter/PathResolverRecyclerAdapter.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageView +import androidx.recyclerview.widget.RecyclerView +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.adapter.PathResolverRecyclerAdapter.Holder +import java.util.* + +/** + * Created by: veli + * Date: 5/29/17 4:29 PM + */ +abstract class PathResolverRecyclerAdapter(val context: Context) : RecyclerView.Adapter>() { + var clickListener: OnClickListener? = null + + val list: MutableList> = ArrayList() + + /* + * To fix issues with the RecyclerView not appearing, the first item must be provided + * when dealing with wrap_content height. + */ + abstract fun onFirstItem(): Index + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { + return Holder(LayoutInflater.from(parent.context).inflate(R.layout.list_pathresolver, null)) + } + + override fun onBindViewHolder(holder: Holder, position: Int) { + holder.index = list[position] + holder.text.text = holder.index.title + holder.image.setImageResource(holder.index.imgRes) + holder.image.visibility = if (position == 0) View.GONE else View.VISIBLE + holder.text.isEnabled = itemCount - 1 != position + holder.text.setOnClickListener { clickListener?.onClick(holder) } + } + + fun initAdapter() { + synchronized(list) { + list.clear() + list.add(onFirstItem()) + } + } + + override fun getItemCount(): Int { + return list.size + } + + interface OnClickListener { + fun onClick(holder: Holder) + } + + class Holder constructor(var container: View, ) : RecyclerView.ViewHolder(container) { + var image: ImageView = container.findViewById(R.id.list_pathresolver_image) + var text: Button = container.findViewById(R.id.list_pathresolver_text) + lateinit var index: Index + } + + class Index(var title: String, var data: D, var imgRes: Int = R.drawable.ic_keyboard_arrow_right_white_24dp) + + init { + initAdapter() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/adapter/SharedTextAdapter.kt b/app/src/main/java/org/monora/uprotocol/client/android/adapter/SharedTextAdapter.kt new file mode 100755 index 000000000..d9ba26638 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/adapter/SharedTextAdapter.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import org.monora.uprotocol.client.android.database.model.SharedText +import org.monora.uprotocol.client.android.databinding.ListSectionDateBinding +import org.monora.uprotocol.client.android.databinding.ListSharedTextBinding +import org.monora.uprotocol.client.android.itemcallback.ContentModelItemCallback +import org.monora.uprotocol.client.android.model.ContentModel +import org.monora.uprotocol.client.android.model.DateSectionContentModel +import org.monora.uprotocol.client.android.viewholder.DateSectionViewHolder +import org.monora.uprotocol.client.android.viewholder.SharedTextViewHolder + +/** + * created by: Veli + * date: 30.12.2017 13:25 + */ +class SharedTextAdapter : ListAdapter(ContentModelItemCallback()) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = when (viewType) { + VIEW_TYPE_SHARED_TEXT -> SharedTextViewHolder( + ListSharedTextBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + VIEW_TYPE_SECTION -> DateSectionViewHolder( + ListSectionDateBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + else -> throw UnsupportedOperationException() + } + + override fun getItemViewType(position: Int): Int { + return when (getItem(position)) { + is SharedText -> VIEW_TYPE_SHARED_TEXT + is DateSectionContentModel -> VIEW_TYPE_SECTION + else -> throw UnsupportedOperationException() + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + when (val item = getItem(position)) { + is SharedText -> if (holder is SharedTextViewHolder) holder.bind(item) + is DateSectionContentModel -> if (holder is DateSectionViewHolder) holder.bind(item) + } + } + + override fun getItemId(position: Int): Long { + return getItem(position).id() + } + + companion object { + const val VIEW_TYPE_SECTION = 0 + const val VIEW_TYPE_SHARED_TEXT = 1 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/adapter/TransferItemListAdapter.kt b/app/src/main/java/org/monora/uprotocol/client/android/adapter/TransferItemListAdapter.kt new file mode 100755 index 000000000..833290d7f --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/adapter/TransferItemListAdapter.kt @@ -0,0 +1,790 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.adapter + +import android.view.ViewGroup +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.adapter.TransferItemListAdapter.GenericItem +import org.monora.uprotocol.client.android.app.IListingFragment +import org.monora.uprotocol.client.android.widget.ListingAdapter +import com.genonbeta.android.framework.widget.RecyclerViewAdapter.ViewHolder +import org.monora.uprotocol.client.android.backend.Destination +import org.monora.uprotocol.client.android.backend.OperationBackend +import org.monora.uprotocol.client.android.backend.SharingBackend +import org.monora.uprotocol.client.android.model.ContentModel +import java.util.* + +/** + * Created by: veli + * Date: 4/15/17 12:29 PM + */ +class TransferItemListAdapter( + fragment: IListingFragment, +) : ListingAdapter(fragment) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(layoutInflater.inflate(R.layout.list_selection, parent, false)) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + TODO("Not yet implemented") + } + + override fun onLoad(): MutableList = ArrayList() + + class GenericItem : ContentModel { + override fun canCopy(): Boolean = false + + override fun canMove(): Boolean = false + + override fun canSelect(): Boolean = false + + override fun canShare(): Boolean = false + + override fun canRemove(): Boolean = false + + override fun canRename(): Boolean = false + + override fun copy(operationBackend: OperationBackend, destination: Destination): Boolean { + TODO("Not yet implemented") + } + + override fun dateCreated(): Long = throw UnsupportedOperationException() + + override fun dateModified(): Long = throw UnsupportedOperationException() + + override fun dateSupported(): Boolean = false + + override fun filter(charSequence: CharSequence): Boolean = false + + override fun id(): Long = 0 + + override fun length(): Long = throw UnsupportedOperationException() + + override fun lengthSupported(): Boolean = false + + override fun move(operationBackend: OperationBackend, destination: Destination): Boolean { + TODO("Not yet implemented") + } + + override fun name(): String = "FileHolder" + + override fun remove(operationBackend: OperationBackend): Boolean { + TODO("Not yet implemented") + } + + override fun share(operationBackend: OperationBackend, sharingBackend: SharingBackend): Boolean { + TODO("Not yet implemented") + } + + override fun selected(): Boolean = false + + override fun select(selected: Boolean) { + + } + } + /* + var select: SQLQuery.Select = SQLQuery.Select(Kuick.TABLE_TRANSFERITEM) + + var path: String? = null + set(value) { + field = value + pathChangedListener?.onPathChange(value) + } + + var member: LoadedMember? = null + set(value) { + if (value != null) { + try { + AppUtils.getKuick(context).reconstruct(value) + field = value + } catch (ignored: ReconstructionFailedException) { + } + } else + field = null + } + + var pathChangedListener: PathChangedListener? = null + + private val percentFormat = NumberFormat.getPercentInstance() + + private val mTransfer = Transfer() + + @ColorInt + private val colorPending: Int = getColor(context, getReference(context, R.attr.colorControlNormal)) + private val colorDone: Int = getColor(context, getReference(context, R.attr.colorAccent)) + private val colorError: Int = getColor(context, getReference(context, R.attr.colorError)) + + protected override fun onLoad(lister: GroupLister) { + val loadThumbnails = AppUtils.getDefaultPreferences(context) + .getBoolean("load_thumbnails", true) + try { + AppUtils.getKuick(context).reconstruct(mTransfer) + } catch (e: ReconstructionFailedException) { + e.printStackTrace() + return + } + var hasIncoming = false + val currentPath = path?.let { if (it.isEmpty()) null else it } + val folders: MutableMap = ArrayMap() + val member = member + val members = Transfers.loadMemberList(context, getGroupId(), null) + val memberArray = members.toTypedArray() + val transferSelect = SQLQuery.Select(Kuick.TABLE_TRANSFERITEM) + val transferWhere = StringBuilder(Kuick.FIELD_TRANSFERITEM_TRANSFERID + "=?") + val transferArgs: MutableList = ArrayList() + transferArgs.add(mTransfer.id.toString()) + if (currentPath != null) { + transferWhere.append( + " AND (" + Kuick.FIELD_TRANSFERITEM_DIRECTORY + "=? OR " + + Kuick.FIELD_TRANSFERITEM_DIRECTORY + " LIKE ?)" + ) + transferArgs.add(currentPath) + transferArgs.add(currentPath + File.separator + "%") + } + if (member != null) { + transferWhere.append(" AND " + Kuick.FIELD_TRANSFERITEM_TYPE + "=?") + transferArgs.add(member.type.toString()) + } + if (sortingCriteria == MODE_GROUP_BY_DATE) { + transferSelect.setOrderBy( + Kuick.FIELD_TRANSFERITEM_LASTCHANGETIME + " " + + if (sortingOrder == MODE_SORT_ORDER_ASCENDING) "ASC" else "DESC" + ) + } + transferSelect.where = transferWhere.toString() + transferSelect.whereArgs = transferArgs.toTypedArray() + val statusItem = DetailsTransferFolder( + mTransfer.id, + if (currentPath == null) { + member?.device?.username ?: context.getString(R.string.text_home) + } else if (currentPath.contains(File.separator)) { + currentPath.substring(currentPath.lastIndexOf(File.separator) + 1) + } else currentPath, currentPath ?: "Empty" + ) + lister.offerObliged(this, statusItem) + val derivedList = AppUtils.getKuick(context).castQuery( + transferSelect, GenericTransferItem::class.java + ) + + // we first get the default files + for (item in derivedList) { + item.members = memberArray + item.directory = + if (item.directory == null || item.directory!!.length == 0) null else item.directory + if (currentPath != null && item.directory == null) continue + var transferFolder: TransferFolder? = null + val isIncoming = TransferItem.Type.INCOMING == item.type + val isOutgoing = TransferItem.Type.OUTGOING == item.type + if (currentPath == null && item.directory == null || item.directory == currentPath) { + try { + if (!loadThumbnails) item.supportThumbnail = false else { + val format = item.mimeType.split(File.separator.toRegex()).toTypedArray() + if (format.size > 0 && ("image" == format[0] || "video" == format[0])) { + var documentFile: DocumentFile? = null + if (isOutgoing) documentFile = Files.fromUri( + context, + Uri.parse(item.file) + ) else if (TransferItem.Flag.DONE == item.flag) documentFile = + org.monora.uprotocol.client.android.util.Files.getIncomingPseudoFile( + context, item, mTransfer, + false + ) + if (documentFile != null && documentFile.exists()) { + item.documentFile = documentFile + item.supportThumbnail = true + } + } else item.supportThumbnail = false + } + } catch (e: Exception) { + e.printStackTrace() + } + lister.offerObliged(this, item) + } else if (currentPath == null || item.directory!!.startsWith(currentPath)) { + val pathToErase = if (currentPath == null) 0 else currentPath.length + File.separator.length + var cleanedPath = item.directory!!.substring(pathToErase) + val slashPos = cleanedPath.indexOf(File.separator) + if (slashPos != -1) cleanedPath = cleanedPath.substring(0, slashPos) + transferFolder = folders[cleanedPath] + if (transferFolder == null) { + transferFolder = TransferFolder( + mTransfer.id, + cleanedPath, + if (currentPath != null) currentPath + File.separator + cleanedPath else cleanedPath + ) + folders[cleanedPath] = transferFolder + lister.offerObliged(this, transferFolder) + } + } + if (!hasIncoming && isIncoming) hasIncoming = true + mergeTransferInfo(statusItem, item, isIncoming, transferFolder) + } + if (currentPath == null && hasIncoming) try { + val transfer = Transfer(mTransfer.id) + AppUtils.getKuick(context).reconstruct(transfer) + val savePath = org.monora.uprotocol.client.android.util.Files.getSavePath(context, transfer) + val storageItem = StorageStatusItem() + storageItem.directory = savePath.getUri().toString() + storageItem.name = savePath.getName() + storageItem.bytesRequired = statusItem.bytesTotal - statusItem.bytesReceived + if (savePath is LocalDocumentFile) { + val saveFile: File = savePath.file + storageItem.bytesTotal = saveFile.totalSpace + storageItem.bytesFree = saveFile.freeSpace // return used space + } else if (Build.VERSION.SDK_INT >= 21 && savePath is TreeDocumentFile) { + try { + val descriptor = context.contentResolver.openFileDescriptor(savePath.originalUri, "r") + if (descriptor != null) { + val stats: StructStatVfs = Os.fstatvfs(descriptor.getFileDescriptor()) + storageItem.bytesTotal = stats.f_blocks * stats.f_bsize + storageItem.bytesFree = stats.f_bavail * stats.f_bsize + } + } catch (e: Exception) { + e.printStackTrace() + } + } else { + storageItem.bytesTotal = -1 + storageItem.bytesFree = -1 + } + lister.offerObliged(this, storageItem) + } catch (e: Exception) { + e.printStackTrace() + } + } + + override fun onCustomGroupListing(lister: GroupLister, mode: Int, holder: GenericItem): Boolean { + if (mode == MODE_GROUP_BY_DEFAULT) lister.offer( + holder, + GroupEditableTransferObjectMerger(holder, this) + ) else return false + return true + } + + override fun createLister(loadedList: MutableList, groupBy: Int): GroupLister { + return super.createLister(loadedList, groupBy).also { it.customLister = this } + } + + fun getDeviceId(): String? { + return member?.deviceId + } + + fun mergeTransferInfo( + details: DetailsTransferFolder, item: GenericTransferItem, isIncoming: Boolean, + folder: TransferFolder?, + ) { + if (isIncoming) { + mergeTransferInfo(details, item, item.flag, true, folder) + } else { + if (member != null) mergeTransferInfo( + details, + item, + item.getFlag(getDeviceId()), + false, + folder + ) else if (item.members.isEmpty()) mergeTransferInfo( + details, + item, + TransferItem.Flag.PENDING, + false, + folder + ) else for (loadedMember in item.members) { + if (TransferItem.Type.OUTGOING != loadedMember.type) continue + mergeTransferInfo( + details, item, item.getFlag(loadedMember.deviceId), + false, folder + ) + } + } + } + + fun mergeTransferInfo( + details: DetailsTransferFolder, item: TransferItem, flag: TransferItem.Flag, + isIncoming: Boolean, folder: TransferFolder?, + ) { + details.bytesTotal += item.getComparableSize() + details.numberOfTotal++ + if (folder != null) { + folder.bytesTotal += item.getComparableSize() + folder.numberOfTotal++ + } + if (TransferItem.Flag.DONE == flag) { + details.numberOfCompleted++ + details.bytesCompleted += item.getComparableSize() + if (folder != null) { + folder.numberOfCompleted++ + folder.bytesCompleted += item.getComparableSize() + } + } else if (Transfers.isError(flag)) { + details.hasIssues = true + if (folder != null) folder.hasIssues = true + } else if (TransferItem.Flag.IN_PROGRESS == flag) { + val completed = flag.bytesValue + details.bytesCompleted += completed + details.hasOngoing = true + if (folder != null) { + folder.bytesCompleted += completed + folder.hasOngoing = true + } + if (isIncoming) { + details.bytesReceived += completed + if (folder != null) folder.bytesReceived += completed + } + } + } + + fun getGroupId(): Long { + return mTransfer.id + } + + fun setTransferId(transferId: Long) { + mTransfer.id = transferId + } + + private fun getPercentFormat(): NumberFormat { + return percentFormat + } + + override fun getRepresentativeText(merger: Merger): String { + return if (merger is GroupEditableTransferObjectMerger) { + when (merger.type) { + GroupEditableTransferObjectMerger.Type.STATUS -> context.getString(R.string.text_transactionDetails) + GroupEditableTransferObjectMerger.Type.FOLDER -> context.getString(R.string.text_folder) + GroupEditableTransferObjectMerger.Type.FILE_ERROR -> context.getString(R.string.text_flagInterrupted) + GroupEditableTransferObjectMerger.Type.FOLDER_ONGOING, GroupEditableTransferObjectMerger.Type.FILE_ONGOING -> context.getString( + R.string.text_taskOngoing + ) + else -> context.getString(R.string.text_file) + } + } else super.getRepresentativeText(merger) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupViewHolder { + val holder: GroupViewHolder = if (viewType == VIEW_TYPE_DEFAULT) GroupViewHolder( + layoutInflater.inflate(R.layout.list_transfer_item, parent, false) + ) else createDefaultViews( + parent, viewType, + false + ) + if (!holder.isRepresentative()) { + fragment.registerLayoutViewClicks(holder) + holder.itemView.findViewById(R.id.layout_image).setOnClickListener { v: View? -> + fragment.setItemSelected(holder, true) + } + } + return holder + } + + override fun onBindViewHolder(holder: GroupViewHolder, position: Int) { + try { + val item: GenericItem = getItem(position) + if (!holder.tryBinding(item)) { + val parentView: View = holder.itemView + @ColorInt val appliedColor: Int + val percentage = (item.getPercentage(this) * 100).toInt() + val progressBar = parentView.findViewById(R.id.progressBar) + val thumbnail = parentView.findViewById(R.id.thumbnail) + val image = parentView.findViewById(R.id.image) + val indicator = parentView.findViewById(R.id.indicator) + val sIcon = parentView.findViewById(R.id.statusIcon) + val titleText: TextView = parentView.findViewById(R.id.text) + val firstText: TextView = parentView.findViewById(R.id.text2) + val secondText: TextView = parentView.findViewById(R.id.text3) + val thirdText: TextView = parentView.findViewById(R.id.text4) + parentView.isSelected = item.selected() + appliedColor = + if (item.hasIssues(this)) colorError else if (item.isComplete(this)) colorDone else colorPending + titleText.setText(item.name) + firstText.setText(item.getFirstText(this)) + secondText.setText(item.getSecondText(this)) + thirdText.setText(item.getThirdText(this)) + item.handleStatusIcon(sIcon, mTransfer) + item.handleStatusIndicator(indicator) + ImageViewCompat.setImageTintList(sIcon, ColorStateList.valueOf(appliedColor)) + progressBar.max = 100 + if (Build.VERSION.SDK_INT >= 24) progressBar.setProgress( + if (percentage <= 0) 1 else percentage, + true + ) else progressBar.progress = if (percentage <= 0) 1 else percentage + thirdText.setTextColor(appliedColor) + ImageViewCompat.setImageTintList(image, ColorStateList.valueOf(appliedColor)) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + val wrapDrawable: Drawable = DrawableCompat.wrap(progressBar.progressDrawable) + DrawableCompat.setTint(wrapDrawable, appliedColor) + progressBar.progressDrawable = DrawableCompat.unwrap(wrapDrawable) + } else progressBar.progressTintList = ColorStateList.valueOf(appliedColor) + val supportThumbnail = item.loadThumbnail(thumbnail) + progressBar.visibility = + if (!supportThumbnail || !item.isComplete(this)) View.VISIBLE else View.GONE + if (supportThumbnail) image.setImageDrawable(null) else { + image.setImageResource(item.getIconRes()) + thumbnail.setImageDrawable(null) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + interface PathChangedListener { + fun onPathChange(path: String?) + } + + internal interface StatusItem + + abstract class GenericItem : TransferItem { + private var viewType = 0 + + private lateinit var representativeText: String + + @DrawableRes + abstract fun getIconRes(): Int + + abstract fun loadThumbnail(imageView: ImageView): Boolean + + abstract fun getPercentage(adapter: TransferItemListAdapter): Double + + abstract fun hasIssues(adapter: TransferItemListAdapter): Boolean + + abstract fun isComplete(adapter: TransferItemListAdapter): Boolean + + abstract fun isOngoing(adapter: TransferItemListAdapter): Boolean + + abstract fun handleStatusIcon(imageView: ImageView, transfer: Transfer) + + abstract fun handleStatusIndicator(imageView: ImageView) + + abstract fun getFirstText(adapter: TransferItemListAdapter): String? + + abstract fun getSecondText(adapter: TransferItemListAdapter): String + + abstract fun getThirdText(adapter: TransferItemListAdapter): String? + } + + class GenericTransferItem : GenericItem { + var documentFile: DocumentFile? = null + + lateinit var members: Array + + var supportThumbnail = false + + override fun applyFilter(filteringKeywords: Array): Boolean { + if (super.applyFilter(filteringKeywords)) return true + for (keyword in filteringKeywords) if (mimeType.toLowerCase().contains(keyword.toLowerCase())) return true + return false + } + + override fun getIconRes(): Int { + return MimeIcons.loadMimeIcon(mimeType) + } + + override fun handleStatusIcon(imageView: ImageView, transfer: Transfer) { + imageView.visibility = View.VISIBLE + imageView.setImageResource(if (Type.INCOMING == type) R.drawable.ic_arrow_down_white_24dp else R.drawable.ic_arrow_up_white_24dp) + } + + override fun handleStatusIndicator(imageView: ImageView) { + imageView.visibility = View.GONE + } + + override fun getFirstText(adapter: TransferItemListAdapter): String { + return Files.formatLength(getComparableSize(), false) + } + + override fun getSecondText(adapter: TransferItemListAdapter): String { + val member = adapter.member + if (member != null) return member.device.username + var totalDevices = 1 + if (Type.OUTGOING == type) synchronized(senderFlagListInternal) { totalDevices = senderFlagList.size } + return adapter.context.resources.getQuantityString( + R.plurals.text_devices, + totalDevices, totalDevices + ) + } + + override fun getThirdText(adapter: TransferItemListAdapter): String { + return TextUtils.getTransactionFlagString( + adapter.context, this, + adapter.getPercentFormat(), adapter.getDeviceId() + ) + } + + override fun loadThumbnail(imageView: ImageView): Boolean { + val localDocumentFile = documentFile + + if (localDocumentFile != null && supportThumbnail && localDocumentFile.exists()) { + GlideApp.with(imageView.context) + .load(localDocumentFile.getUri()) + .error(getIconRes()) + .override(160) + .circleCrop() + .into(imageView) + return true + } + return false + } + + override fun getPercentage(adapter: TransferItemListAdapter): Double { + return getPercentage(members, adapter.getDeviceId()) + } + + override fun hasIssues(adapter: TransferItemListAdapter): Boolean { + if (members.isEmpty()) return false + if (Type.INCOMING == type) return Transfers.isError(flag) else if (adapter.getDeviceId() != null) { + return Transfers.isError(getFlag(adapter.getDeviceId())) + } else synchronized(senderFlagListInternal) { for (member in members) if (Transfers.isError(getFlag(member.deviceId))) return true } + return false + } + + override fun isComplete(adapter: TransferItemListAdapter): Boolean { + if (members.size == 0) return false + if (Type.INCOMING == type) return Flag.DONE == flag else if (adapter.getDeviceId() != null) { + return Flag.DONE == getFlag(adapter.getDeviceId()) + } else synchronized(senderFlagListInternal) { for (member in members) if (Flag.DONE != getFlag(member.deviceId)) return false } + return true + } + + override fun isOngoing(adapter: TransferItemListAdapter): Boolean { + if (members.isEmpty()) return false + if (Type.INCOMING == type) return Flag.IN_PROGRESS == flag else if (adapter.getDeviceId() != null) { + return Flag.IN_PROGRESS == getFlag(adapter.getDeviceId()) + } else synchronized(senderFlagListInternal) { for (member in members) if (Flag.IN_PROGRESS == getFlag(member.deviceId)) return true } + return false + } + } + + open class TransferFolder internal constructor( + transferId: Long, friendlyName: String, var directoryLocal: String, + ) : GenericItem() { + override var directory: String? = directoryLocal + + var hasIssues = false + + var hasOngoing = false + + var numberOfTotal = 0 + + var numberOfCompleted = 0 + + var bytesTotal: Long = 0 + + var bytesCompleted: Long = 0 + + var bytesReceived: Long = 0 + + override var id: Long + get() = directory.hashCode().toLong() + set(value) {} + + override fun equals(other: Any?): Boolean { + return other is TransferFolder && directory != null && directory == other.directory + } + + override fun getComparableSize(): Long { + return bytesTotal + } + + override fun getIconRes(): Int { + return R.drawable.ic_folder_white_24dp + } + + override fun getFirstText(adapter: TransferItemListAdapter): String? { + return Files.formatLength(bytesTotal, false) + } + + override fun getSecondText(adapter: TransferItemListAdapter): String { + return adapter.context + .getString(R.string.text_transferStatusFiles, numberOfCompleted, numberOfTotal) + } + + override fun getThirdText(adapter: TransferItemListAdapter): String? { + return adapter.getPercentFormat().format(getPercentage(adapter)) + } + + override fun getWhere(): SQLQuery.Select { + return SQLQuery.Select(Kuick.TABLE_TRANSFERITEM) + .setWhere( + Kuick.FIELD_TRANSFERITEM_TRANSFERID + "=? AND (" + + Kuick.FIELD_TRANSFERITEM_DIRECTORY + " LIKE ? OR " + + Kuick.FIELD_TRANSFERITEM_DIRECTORY + " = ?)", + transferId.toString(), + directory + File.separator + "%", + directoryLocal + ) + } + + override fun handleStatusIcon(imageView: ImageView, transfer: Transfer) { + imageView.visibility = View.GONE + } + + override fun handleStatusIndicator(imageView: ImageView) { + imageView.visibility = View.GONE + } + + override fun hasIssues(adapter: TransferItemListAdapter): Boolean { + return hasIssues + } + + override fun loadThumbnail(imageView: ImageView): Boolean { + return false + } + + override fun getPercentage(adapter: TransferItemListAdapter): Double { + return if (bytesTotal == 0L || bytesCompleted == 0L) 0.0 else (bytesCompleted.toFloat() / bytesTotal).toDouble() + } + + override fun isComplete(adapter: TransferItemListAdapter): Boolean { + return numberOfTotal == numberOfCompleted && numberOfTotal != 0 + } + + override fun isOngoing(adapter: TransferItemListAdapter): Boolean { + return hasOngoing + } + + init { + this.transferId = transferId + name = friendlyName + } + } + + class DetailsTransferFolder internal constructor(transferId: Long, friendlyName: String, directory: String) : + TransferFolder(transferId, friendlyName, directory), StatusItem { + override var id: Long = directoryLocal.hashCode().toLong() + + override fun handleStatusIcon(imageView: ImageView, transfer: Transfer) { + if (transfer.isServedOnWeb) { + imageView.visibility = View.VISIBLE + imageView.setImageResource(R.drawable.ic_web_white_24dp) + } else super.handleStatusIcon(imageView, transfer) + } + + override fun handleStatusIndicator(imageView: ImageView) { + imageView.visibility = View.VISIBLE + imageView.setImageResource(R.drawable.ic_arrow_right_white_24dp) + } + + override fun getIconRes(): Int { + return R.drawable.ic_device_hub_white_24dp + } + + override fun selected(): Boolean { + return false + } + + override fun select(selected: Boolean): Boolean { + return false + } + } + + class StorageStatusItem : GenericItem(), StatusItem { + override var id: Long = (if (directory != null) directory else name).hashCode().toLong() + + var bytesTotal: Long = 0 + + var bytesFree: Long = 0 + + var bytesRequired: Long = 0 + + override fun hasIssues(adapter: TransferItemListAdapter): Boolean { + return bytesFree < bytesRequired && bytesFree != -1L + } + + override fun isComplete(adapter: TransferItemListAdapter): Boolean { + return bytesFree == -1L || !hasIssues(adapter) + } + + override fun isOngoing(adapter: TransferItemListAdapter): Boolean { + return false + } + + override fun selected(): Boolean { + return false + } + + override fun getIconRes(): Int { + return R.drawable.ic_save_white_24dp + } + + override fun getPercentage(adapter: TransferItemListAdapter): Double { + return if (bytesTotal <= 0 || bytesFree <= 0) 0.0 else java.lang.Long.valueOf(bytesTotal - bytesFree) + .toDouble() / java.lang.Long.valueOf(bytesTotal).toDouble() + } + + override fun handleStatusIcon(imageView: ImageView, transfer: Transfer) { + imageView.visibility = View.GONE + } + + override fun handleStatusIndicator(imageView: ImageView) { + imageView.visibility = View.VISIBLE + imageView.setImageResource(R.drawable.ic_arrow_right_white_24dp) + } + + override fun getFirstText(adapter: TransferItemListAdapter): String { + return if (bytesFree == -1L) adapter.context + .getString(R.string.text_unknown) else Files.formatLength(bytesFree, false) + } + + override fun getSecondText(adapter: TransferItemListAdapter): String { + return adapter.context.getString(R.string.text_savePath) + } + + override fun getThirdText(adapter: TransferItemListAdapter): String? { + return adapter.getPercentFormat().format(getPercentage(adapter)) + } + + override fun loadThumbnail(imageView: ImageView): Boolean { + return false + } + + override fun select(selected: Boolean): Boolean { + return false + } + } + + class GroupEditableTransferObjectMerger internal constructor( + holder: GenericItem, + adapter: TransferItemListAdapter, + ) : ComparableMerger() { + val type = when { + holder is StatusItem -> Type.STATUS + holder is TransferFolder -> Type.FOLDER + holder.hasIssues(adapter) -> Type.FILE_ERROR + holder.isOngoing(adapter) -> Type.FILE_ONGOING + else -> Type.FILE + } + + override fun equals(other: Any?): Boolean { + return (other is GroupEditableTransferObjectMerger && other.type == type) + } + + override fun hashCode(): Int = type.hashCode() + + override operator fun compareTo(other: ComparableMerger): Int { + return if (other is GroupEditableTransferObjectMerger) { + MathUtils.compare(other.type.ordinal.toLong(), type.ordinal.toLong()) + } else 1 + } + + enum class Type { + STATUS, FOLDER_ONGOING, FOLDER, FILE_ONGOING, FILE_ERROR, FILE + } + } + + companion object { + //public static final int MODE_SORT_BY_DEFAULT = MODE_SORT_BY_NAME - 1; + val MODE_GROUP_BY_DEFAULT: Int = GroupEditableListAdapter.MODE_GROUP_BY_NOTHING + 1 + }*/ +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/app/Activity.kt b/app/src/main/java/org/monora/uprotocol/client/android/app/Activity.kt new file mode 100755 index 000000000..36483fc02 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/app/Activity.kt @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.app + +import android.content.* +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.os.Build +import android.os.Bundle +import android.os.PowerManager +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.annotation.StyleRes +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.preference.PreferenceManager +import com.google.android.material.bottomsheet.BottomSheetDialog +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import org.monora.uprotocol.client.android.App +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.activity.ChangelogActivity +import org.monora.uprotocol.client.android.activity.HomeActivity +import org.monora.uprotocol.client.android.activity.WelcomeActivity +import org.monora.uprotocol.client.android.backend.BackgroundBackend +import org.monora.uprotocol.client.android.data.SharedTextRepository +import org.monora.uprotocol.client.android.database.model.SharedText +import org.monora.uprotocol.client.android.databinding.LayoutProfileEditorBinding +import org.monora.uprotocol.client.android.dialog.PermissionRequests +import org.monora.uprotocol.client.android.model.Identifier +import org.monora.uprotocol.client.android.model.Identity +import org.monora.uprotocol.client.android.model.Identity.Companion.withORs +import org.monora.uprotocol.client.android.service.backgroundservice.AsyncTask +import org.monora.uprotocol.client.android.service.backgroundservice.AttachableAsyncTask +import org.monora.uprotocol.client.android.service.backgroundservice.AttachedTaskListener +import org.monora.uprotocol.client.android.service.backgroundservice.BaseAttachableAsyncTask +import org.monora.uprotocol.client.android.util.Permissions +import org.monora.uprotocol.client.android.util.Updater +import org.monora.uprotocol.client.android.viewmodel.UserProfileViewModel +import java.io.FileReader +import java.io.IOException +import java.util.* +import javax.inject.Inject + +@AndroidEntryPoint +abstract class Activity : AppCompatActivity(), OnSharedPreferenceChangeListener { + @Inject + lateinit var backgroundBackend: BackgroundBackend + + @Inject + lateinit var sharedTextRepository: SharedTextRepository + + @Inject + lateinit var updater: Updater + + private var amoledThemeState = false + + private val attachedTaskList: MutableList = ArrayList() + + private var customFontsState = false + + private var darkThemeState = false + + protected val defaultPreferences: SharedPreferences + get() = PreferenceManager.getDefaultSharedPreferences(this) + + open val identity: Identity + get() = withORs(Identifier.from(AsyncTask.Id.HashCode, AsyncTask.hashIntent(intent))) + + private val intentFilter = IntentFilter() + + private val amoledThemeEnabled: Boolean + get() = defaultPreferences.getBoolean("amoled_theme", false) + + private val customFontsEnabled: Boolean + get() = defaultPreferences.getBoolean("custom_fonts", false) && Build.VERSION.SDK_INT >= 16 + + private val darkThemeEnabled: Boolean + get() { + val value = defaultPreferences.getString("theme", "light") + val systemWideTheme = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + return ("dark" == value || "system" == value && systemWideTheme == Configuration.UI_MODE_NIGHT_YES + || "battery" == value && powerSaveEnabled) + } + + private val powerSaveEnabled: Boolean + get() { + if (Build.VERSION.SDK_INT < 23) return false + val powerManager = getSystemService(POWER_SERVICE) as PowerManager + return powerManager.isPowerSaveMode + } + + private var ongoingRequest: AlertDialog? = null + + private val receiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (ACTION_SYSTEM_POWER_SAVE_MODE_CHANGED == intent.action) + checkForThemeChange() + else if (BackgroundBackend.ACTION_TASK_CHANGE == intent.action) + attachTasks() + } + } + + var skipPermissionRequest = false + protected set + + private var themeLoadingFailed = false + + private val uiTaskList: MutableList = ArrayList() + + var welcomePageDisallowed = false + protected set + + override fun onCreate(savedInstanceState: Bundle?) { + darkThemeState = darkThemeEnabled + amoledThemeState = amoledThemeEnabled + customFontsState = customFontsEnabled + + intentFilter.addAction(ACTION_SYSTEM_POWER_SAVE_MODE_CHANGED) + intentFilter.addAction(BackgroundBackend.ACTION_TASK_CHANGE) + + if (darkThemeState) try { + @StyleRes + val currentThemeRes = packageManager.getActivityInfo(componentName, 0).theme.let { + Log.d(Activity::class.simpleName, "ActivityTheme=$it AppTheme=" + applicationInfo.theme) + return@let if (it == 0) applicationInfo.theme else it + } + + @StyleRes + val appliedRes = when (currentThemeRes) { + R.style.Theme_TrebleShot -> R.style.Theme_TrebleShot_Dark + R.style.Theme_TrebleShot_NoActionBar -> R.style.Theme_TrebleShot_Dark_NoActionBar + R.style.Theme_TrebleShot_NoActionBar_StaticStatusBar -> { + R.style.Theme_TrebleShot_Dark_NoActionBar_StaticStatusBar + } + else -> { + Log.e(Activity::class.simpleName, "The requested theme ${javaClass.simpleName} is unknown.") + 0 + } + } + + themeLoadingFailed = appliedRes == 0 + + if (!themeLoadingFailed) { + setTheme(appliedRes) + + if (amoledThemeState) { + theme.applyStyle(R.style.BlackPatch, true) + } + } + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + } + + // Apply the Preferred Font Family as a patch if enabled + if (customFontsState) { + Log.d(Activity::class.simpleName, "Custom fonts have been applied") + theme.applyStyle(R.style.Roundies, true) + } + + super.onCreate(savedInstanceState) + } + + override fun onStart() { + super.onStart() + attachTasks() + registerReceiver(receiver, intentFilter) + backgroundBackend.notifyActivityInForeground(this, true) + } + + override fun onResume() { + super.onResume() + checkForThemeChange() + defaultPreferences.registerOnSharedPreferenceChangeListener(this) + + if (!hasIntroductionShown() && !welcomePageDisallowed) { + startActivity(Intent(this, WelcomeActivity::class.java)) + finish() + } else if (!Permissions.checkRunningConditions(this) && !skipPermissionRequest) { + requestRequiredPermissions(true) + } + + if (this is HomeActivity && hasIntroductionShown()) { + checkAndShowCrashReport() + checkAndShowChangelog() + } + } + + override fun onPause() { + super.onPause() + defaultPreferences.unregisterOnSharedPreferenceChangeListener(this) + } + + override fun onStop() { + super.onStop() + unregisterReceiver(receiver) + backgroundBackend.notifyActivityInForeground(this, false) + } + + override fun onDestroy() { + super.onDestroy() + stopUiTasks() + detachTasks() + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + if ("custom_fonts" == key || "theme" == key || "amoled_theme" == key) { + checkForThemeChange() + } + } + + protected open fun onAttachTasks(taskList: List) { + + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (!Permissions.checkRunningConditions(this)) { + requestRequiredPermissions(!skipPermissionRequest) + } + } + + fun attachTask(task: BaseAttachableAsyncTask) { + synchronized(attachedTaskList) { if (!attachedTaskList.add(task)) return } + if (task.activityIntent == null) task.setContentIntent(this, intent) + } + + @Synchronized + private fun attachTasks() { + val concurrentTaskList = backgroundBackend.findTasksBy(identity) + val attachableBgTaskList: MutableList = ArrayList(attachedTaskList) + val checkIfExists = attachableBgTaskList.size > 0 + + // If this call is in between of onStart and onStop, it means there could be some tasks held in the + // attached task list. To avoid duplicates, we check them using 'checkIfExists'. + if (concurrentTaskList.isNotEmpty()) { + for (task in concurrentTaskList) { + if (task is BaseAttachableAsyncTask) { + if (!checkIfExists || !attachableBgTaskList.contains(task)) { + attachTask(task) + attachableBgTaskList.add(task) + } + } + } + } + + // In this phase, we remove the tasks that are no longer known to the background service. + if (checkIfExists && attachableBgTaskList.size > 0) { + if (concurrentTaskList.isEmpty()) { + detachTasks() + } else { + val unrefreshedTaskList: List = ArrayList(attachableBgTaskList) + for (task in unrefreshedTaskList) { + if (!concurrentTaskList.contains(task)) detachTask(task) + } + } + } + onAttachTasks(attachableBgTaskList) + for (bgTask in attachableBgTaskList) if (!bgTask.hasAnchor()) throw RuntimeException( + "The task " + bgTask.javaClass.simpleName + " owner " + + javaClass.simpleName + " did not provide the anchor." + ) + } + + fun attachUiTask(task: AsyncTask) { + synchronized(uiTaskList) { uiTaskList.add(task) } + } + + private fun checkAndShowCrashReport() { + try { + val log = getFileStreamPath(App.FILENAME_UNHANDLED_CRASH_LOG) + val report = FileReader(log).use { it.readText() } + val streamObject = SharedText(0, report, log.lastModified()) + + Log.d(TAG, "checkAndShowCrashReport: $report") + + log.delete() + + AlertDialog.Builder(this) + .setTitle(R.string.text_crashReport) + .setMessage(R.string.text_crashInfo) + .setNegativeButton(R.string.butn_dismiss, null) + .setNeutralButton(android.R.string.copy) { _: DialogInterface?, _: Int -> + val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager + clipboardManager.setPrimaryClip( + ClipData.newPlainText(getString(R.string.text_crashReport), report) + ) + Toast.makeText(this, R.string.mesg_textCopiedToClipboard, Toast.LENGTH_SHORT).show() + }.setPositiveButton(R.string.butn_save) { _: DialogInterface?, _: Int -> + lifecycleScope.launch { + sharedTextRepository.insert(streamObject) + } + + Toast.makeText(this, R.string.mesg_textStreamSaved, Toast.LENGTH_SHORT).show() + }.show() + } catch (ignored: IOException) { + } + } + + private fun checkAndShowChangelog() { + if (!updater.isLatestChangelogShown()) { + AlertDialog.Builder(this) + .setMessage(R.string.mesg_versionUpdatedChangelog) + .setPositiveButton(R.string.butn_yes) { _: DialogInterface?, _: Int -> + updater.declareLatestChangelogAsShown() + startActivity(Intent(this@Activity, ChangelogActivity::class.java)) + } + .setNeutralButton(R.string.butn_never) { _: DialogInterface?, _: Int -> + defaultPreferences.edit() + .putBoolean("show_changelog_dialog", false) + .apply() + } + .setNegativeButton(R.string.butn_no) { _: DialogInterface?, _: Int -> + updater.declareLatestChangelogAsShown() + Toast.makeText( + this@Activity, R.string.mesg_versionUpdatedChangelogRejected, + Toast.LENGTH_SHORT + ).show() + } + .show() + } + } + + fun checkForThemeChange() { + if ((darkThemeState != darkThemeEnabled || (darkThemeEnabled && amoledThemeState != amoledThemeEnabled)) + && !themeLoadingFailed || customFontsState != customFontsEnabled + ) recreate() + } + + fun checkUiTasks() { + if (uiTaskList.size > 0) synchronized(uiTaskList) { + val uiTaskList: MutableList = ArrayList() + for (task in this.uiTaskList) if (!task.finished) uiTaskList.add(task) + this.uiTaskList.clear() + this.uiTaskList.addAll(uiTaskList) + } + } + + fun detachTask(task: BaseAttachableAsyncTask) { + synchronized(attachedTaskList) { + task.removeAnchor() + attachedTaskList.remove(task) + } + } + + private fun detachTasks() { + val taskList: List = ArrayList(attachedTaskList) + for (task in taskList) detachTask(task) + } + + protected fun editProfile( + userProfileViewModel: UserProfileViewModel, + pickPhoto: ActivityResultLauncher, + lifecycleOwner: LifecycleOwner, + ) { + val binding = LayoutProfileEditorBinding.inflate( + LayoutInflater.from(this), null, false + ) + + val dialog = BottomSheetDialog(this) + + binding.viewModel = userProfileViewModel + binding.lifecycleOwner = lifecycleOwner + binding.pickPhotoClickListener = View.OnClickListener { + pickPhoto.launch("image/*") + } + + binding.executePendingBindings() + + dialog.setContentView(binding.root) + dialog.show() + } + + fun findTasksWith(identity: Identity): List { + synchronized(attachedTaskList) { + return BackgroundBackend.findTasksBy( + attachedTaskList, + identity + ) + } + } + + fun getTaskListOf(clazz: Class): List { + synchronized(attachedTaskList) { return BackgroundBackend.getTaskListOf(attachedTaskList, clazz) } + } + + fun hasIntroductionShown(): Boolean { + return defaultPreferences.getBoolean("introduction_shown", false) + } + + fun hasTaskOf(clazz: Class): Boolean { + synchronized(attachedTaskList) { return BackgroundBackend.hasTaskOf(attachedTaskList, clazz) } + } + + fun hasTaskWith(identity: Identity): Boolean { + synchronized(attachedTaskList) { return BackgroundBackend.hasTaskWith(attachedTaskList, identity) } + } + + fun interruptAllTasks(userAction: Boolean) { + if (attachedTaskList.size <= 0) return + synchronized(attachedTaskList) { for (task in attachedTaskList) task.interrupt(userAction) } + } + + fun requestRequiredPermissions(finishIfOtherwise: Boolean) { + if (ongoingRequest?.isShowing == true) return + for (request in Permissions.getRequiredPermissions(this)) { + if (PermissionRequests.requestIfNecessary(this, request, finishIfOtherwise).also { + ongoingRequest = it + } != null + ) break + } + } + + fun run(task: AsyncTask) { + task.setContentIntent(this, intent) + backgroundBackend.run(task) + } + + /** + * Run a task that will be stopped if the user leaves. + * + * @param task to be stopped when user leaves + */ + fun runUiTask(task: AsyncTask) { + attachUiTask(task) + run(task) + } + + fun > runUiTask(task: V, anchor: T) { + task.anchor = anchor + runUiTask(task) + } + + protected fun stopUiTasks() { + if (uiTaskList.size <= 0) return + synchronized(uiTaskList) { + for (task in uiTaskList) task.interrupt(true) + uiTaskList.clear() + } + } + + @Deprecated("Do not use this! Thanks!") + interface OnBackPressedListener { + fun onBackPressed(): Boolean + } + + companion object { + private val TAG = Activity::class.simpleName + + const val ACTION_SYSTEM_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/app/IListingFragment.kt b/app/src/main/java/org/monora/uprotocol/client/android/app/IListingFragment.kt new file mode 100644 index 000000000..d77c08ddb --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/app/IListingFragment.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.app + +import com.genonbeta.android.framework.widget.RecyclerViewAdapter.ViewHolder +import org.monora.uprotocol.client.android.model.ContentModel + +interface IListingFragment : ListingFragmentBase { + fun performLayoutClickOpen(holder: V): Boolean + + fun performLayoutClickOpen(holder: V, target: T): Boolean + + fun performDefaultLayoutClick(holder: V, target: T): Boolean + + fun performDefaultLayoutLongClick(holder: V, target: T): Boolean + + fun performLayoutClick(holder: V): Boolean + + fun performLayoutLongClick(holder: V): Boolean + + fun registerLayoutViewClicks(holder: V) + + fun setItemSelected(holder: V): Boolean + + fun setItemSelected(holder: V, force: Boolean): Boolean + + fun setLayoutClickListener(clickListener: ListingFragment.LayoutClickListener?) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/app/ListingFragment.kt b/app/src/main/java/org/monora/uprotocol/client/android/app/ListingFragment.kt new file mode 100755 index 000000000..da158a742 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/app/ListingFragment.kt @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.app + +import android.app.Activity +import android.content.Context +import android.net.Uri +import android.os.Bundle +import android.util.Log +import android.view.* +import androidx.appcompat.widget.SearchView +import androidx.recyclerview.widget.RecyclerView +import com.genonbeta.android.framework.app.RecyclerViewFragment +import com.genonbeta.android.framework.ui.PerformerMenu +import com.genonbeta.android.framework.util.Files +import com.genonbeta.android.framework.util.actionperformer.* +import com.genonbeta.android.framework.widget.RecyclerViewAdapter.ViewHolder +import com.genonbeta.android.framework.widget.recyclerview.FastScroller +import org.monora.uprotocol.client.android.BuildConfig +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.dialog.SelectionEditorDialog +import org.monora.uprotocol.client.android.model.ContentModel +import org.monora.uprotocol.client.android.util.Selections +import org.monora.uprotocol.client.android.view.LongTextBubbleFastScrollViewProvider +import org.monora.uprotocol.client.android.widget.ListingAdapter +import org.monora.uprotocol.client.android.widget.ListingAdapterBase +import org.monora.uprotocol.client.android.widget.recyclerview.SwipeSelectionListener +import java.util.* + +/** + * created by: Veli + * date: 21.11.2017 10:12 + */ +abstract class ListingFragment> + : RecyclerViewFragment(), IListingFragment { + override var adapter: E + get() = super.adapter + set(value) { + super.adapter = value + engineConnection.setSelectionModelProvider(adapter) + fastScroller?.recyclerView = listView + adapter.fragment = this + } + + override val adapterImpl: ListingAdapterBase + get() = adapter + + var bottomSpaceShown = false + + var dividerResId = R.id.abstract_layout_fast_scroll_recyclerview_bottom_divider + + override val engineConnection: IEngineConnection by lazy { + EngineConnection(this, this) + } + + var fastScroller: FastScroller? = null + private set + + override var filteringDelegate: FilteringDelegate = object : FilteringDelegate { + override var keyword: String? = null + set(value) { + field = value?.trim() + keywordMap = field?.split("".toRegex()) + } + + private var keywordMap: List? = null + + override fun enabled(): Boolean = keywordMap != null + + override fun filter(listFragment: ListingFragmentBase, item: T): Boolean { + val keywordMap = keywordMap ?: return false + keywordMap.forEach { keyword -> if (item.filter(keyword)) return true } + return false + } + } + + override var filteringSupported: Boolean = false + + override var localSelectionActivated: Boolean = false + set(value) { + field = value + if (!value) engineConnection.getSelectionList()?.let { + if (it.isNotEmpty()) engineConnection.setSelected(it, IntArray(it.size), false) + } + activity?.invalidateOptionsMenu() + } + + override val localSelectionMode: Boolean + get() = activity !is PerformerEngineProvider && performerMenu != null + + var layoutResId = R.layout.abstract_layout_editable_list_fragment + + private var layoutClickListener: LayoutClickListener? = null + + override var listView: RecyclerView + get() = super.listView + set(value) { + super.listView = value + + snackbarContainer = value + value.addOnItemTouchListener(SwipeSelectionListener(this)) + + if (bottomSpaceShown) { + value.clipToPadding = false + value.setPadding(0, 0, 0, (resources.getDimension(R.dimen.fab_margin) * 12).toInt()) + } + } + + private val performerEngine: IPerformerEngine = PerformerEngine() + + private var performerMenu: PerformerMenu? = null + + override var refreshRequested: Boolean = false + + private val selectionList: MutableList = ArrayList() + + override var selectByClickEnabled: Boolean = false + get() = field || localSelectionMode + + open fun onCreatePerformerMenu(context: Context): PerformerMenu? { + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + arguments?.let { + selectByClickEnabled = it.getBoolean(ARG_SELECT_BY_CLICK, selectByClickEnabled) + bottomSpaceShown = it.getBoolean(ARG_HAS_BOTTOM_SPACE, bottomSpaceShown) + } + performerMenu?.setUp(performerEngine) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(layoutResId, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + fastScroller = view.findViewById(R.id.abstract_layout_fast_scroll_recyclerview_fastscroll_view) + fastScroller?.viewProvider = LongTextBubbleFastScrollViewProvider() + + setDividerVisible(true) + } + + override fun onResume() { + super.onResume() + refreshList() + } + + override fun onAttach(context: Context) { + super.onAttach(context) + performerMenu = onCreatePerformerMenu(context) + engineConnection.addSelectionListener(this) + getPerformerEngine()?.ensureSlot(this, engineConnection) + } + + override fun onDetach() { + super.onDetach() + getPerformerEngine()?.removeSlot(engineConnection) + } + + override fun onSelected( + engine: IPerformerEngine, owner: IEngineConnection, model: T, isSelected: Boolean, position: Int, + ) { + if (position >= 0) adapter.syncAndNotify(position) else adapter.syncAllAndNotify() + ensureLocalSelection() + } + + override fun onSelected( + engine: IPerformerEngine, owner: IEngineConnection, modelList: MutableList, + isSelected: Boolean, positions: IntArray, + ) { + adapter.syncAllAndNotify() + ensureLocalSelection() + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + if (localSelectionMode && localSelectionActivated) { + performerMenu?.populateMenu(menu) + } else { + inflater.inflate(R.menu.actions_abs_editable_list, menu) + val filterItem = menu.findItem(R.id.actions_abs_editable_filter) + if (filterItem != null) { + filterItem.isVisible = filteringSupported + if (filteringSupported) { + val view = filterItem.actionView + if (view is SearchView) { + view.setOnQueryTextListener( + object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + refreshList() + return true + } + + override fun onQueryTextChange(newText: String): Boolean { + filteringDelegate.keyword = newText + refreshList() + return true + } + } + ) + } + } + } + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + + if (id == R.id.actions_abs_editable_multi_select && performerMenu != null && activity != null) + localSelectionActivated = !localSelectionActivated + else if (performerMenu?.onMenuItemClick(item) == true) + localSelectionActivated = false + else + return super.onOptionsItemSelected(item) + + return true + } + + private fun ensureLocalSelection() { + val shouldActivate = engineConnection.getSelectionList()?.isNotEmpty() ?: false + + if (localSelectionActivated != shouldActivate) { + Log.d(TAG, "ensureLocalSelection: Altering local selection state to '$shouldActivate'") + localSelectionActivated = shouldActivate + } + } + + override fun getPerformerEngine(): IPerformerEngine? { + activity?.let { + if (it is PerformerEngineProvider) return it.getPerformerEngine() + } + + return performerEngine + } + + override fun getAvailableList(): MutableList { + return adapter.getList() + } + + override fun getSelectionList(): MutableList { + return selectionList + } + + override fun getUniqueSettingKey(setting: String): String { + return javaClass.simpleName + "_" + setting + } + + fun invokeClickListener(holder: V, longClick: Boolean): Boolean { + return layoutClickListener?.onLayoutClick(this, holder, longClick) ?: false + } + + override fun loadIfRequested(): Boolean { + val refreshed = refreshRequested + refreshRequested = false + + if (refreshed) { + refreshList() + } + + return refreshed + } + + override fun openUri(uri: Uri): Boolean { + return Files.openUri(requireContext(), uri) + } + + override fun performLayoutClickOpen(holder: V): Boolean { + val pos = holder.adapterPosition + return pos != RecyclerView.NO_POSITION && performLayoutClickOpen(holder, adapterImpl.getItem(pos)) + } + + // FIXME: 2/20/21 Reimplement content opening if possible. Should we be doing ourselves? + override fun performLayoutClickOpen(holder: V, target: T): Boolean = false + + override fun performDefaultLayoutLongClick(holder: V, target: T): Boolean { + return false + } + + override fun performLayoutClick(holder: V): Boolean { + val position = holder.adapterPosition + return if (position == RecyclerView.NO_POSITION) false else setItemSelected(holder) + || invokeClickListener(holder, false) + || performDefaultLayoutClick(holder, adapterImpl.getItem(position)) + } + + override fun performLayoutLongClick(holder: V): Boolean { + val position = holder.adapterPosition + return if (position == RecyclerView.NO_POSITION) false else invokeClickListener(holder, true) + || performDefaultLayoutLongClick(holder, adapterImpl.getItem(position)) + || setItemSelected(holder, true) + } + + override fun registerLayoutViewClicks(holder: V) { + holder.itemView.setOnClickListener { performLayoutClick(holder) } + holder.itemView.setOnLongClickListener { performLayoutLongClick(holder) } + } + + fun setDividerVisible(visible: Boolean) { + view?.let { + val divider = it.findViewById(dividerResId) + divider.visibility = if (visible) View.VISIBLE else View.GONE + } + } + + override fun setItemSelected(holder: V): Boolean { + return setItemSelected(holder, selectByClickEnabled) + } + + override fun setItemSelected(holder: V, force: Boolean): Boolean { + Log.d(TAG, "setItemSelected: $selectByClickEnabled $force") + + if (engineConnection.getSelectionList().let { it != null && it.size <= 0 } && !force) { + return false + } + + try { + engineConnection.setSelected(holder) + return true + } catch (e: SelectionModelNotFoundException) { + e.printStackTrace() + } catch (e: CouldNotAlterException) { + if (BuildConfig.DEBUG) e.printStackTrace() + } + return false + } + + override fun setLayoutClickListener(clickListener: LayoutClickListener?) { + layoutClickListener = clickListener + } + + interface LayoutClickListener { + fun onLayoutClick(listFragment: ListingFragmentBase<*>, holder: V, longClick: Boolean): Boolean + } + + interface FilteringDelegate { + var keyword: String? + + fun disabled(): Boolean = !enabled() + + fun enabled(): Boolean + + fun filter(listFragment: ListingFragmentBase, item: T): Boolean + } + + open class SelectionCallback( + val activity: Activity, private val provider: PerformerEngineProvider, + ) : PerformerMenu.Callback, PerformerEngineProvider { + private lateinit var previewSelections: MenuItem + + var foregroundConnection: IEngineConnection<*>? = null + + var cancellable = true + + override fun onPerformerMenuList( + performerMenu: PerformerMenu, + inflater: MenuInflater, + targetMenu: Menu, + ): Boolean { + inflater.inflate(R.menu.action_mode_abs_editable, targetMenu) + + if (!cancellable) + targetMenu.findItem(R.id.action_mode_abs_editable_cancel_selection).isVisible = false + + previewSelections = targetMenu.findItem(R.id.action_mode_abs_editable_preview_selections) + + getPerformerEngine()?.let { updateTitle(Selections.getTotalSize(it)) } + return true + } + + override fun onPerformerMenuSelected(performerMenu: PerformerMenu, item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_mode_abs_editable_cancel_selection -> setSelectionState(false, false) + R.id.action_mode_abs_editable_select_all -> setSelectionState(true, true) + R.id.action_mode_abs_editable_select_none -> setSelectionState(false, true) + R.id.action_mode_abs_editable_preview_selections -> SelectionEditorDialog(activity, provider).show() + } + + // Returning false means that the target item did not execute anything other than small changes. + // It can be said that descendants can interfere with this by making changes and returning true; + return false + } + + override fun onPerformerMenuItemSelection( + performerMenu: PerformerMenu, engine: IPerformerEngine, + owner: IBaseEngineConnection, selectionModel: SelectionModel, + isSelected: Boolean, position: Int, + ): Boolean { + return true + } + + override fun onPerformerMenuItemSelection( + performerMenu: PerformerMenu, engine: IPerformerEngine, owner: IBaseEngineConnection, + selectionModelList: MutableList, isSelected: Boolean, positions: IntArray, + ): Boolean { + return true + } + + override fun onPerformerMenuItemSelected( + performerMenu: PerformerMenu, engine: IPerformerEngine, + owner: IBaseEngineConnection, selectionModel: SelectionModel, isSelected: Boolean, + position: Int, + ) { + updateTitle(Selections.getTotalSize(engine)) + } + + override fun onPerformerMenuItemSelected( + performerMenu: PerformerMenu, engine: IPerformerEngine, owner: IBaseEngineConnection, + selectionModelList: MutableList, isSelected: Boolean, positions: IntArray, + ) { + updateTitle(Selections.getTotalSize(engine)) + } + + fun setSelectionState(newState: Boolean, tryForeground: Boolean) { + val engine = provider.getPerformerEngine() + val foreground = foregroundConnection + + if (foreground != null && tryForeground) { + setSelectionState(foreground, newState) + } else if (engine != null) for (baseEngineConnection in engine.getConnectionList()) { + if (baseEngineConnection is IEngineConnection<*>) setSelectionState( + baseEngineConnection, newState + ) + } + } + + private fun setSelectionState(connection: IEngineConnection, newState: Boolean) { + connection.getAvailableList()?.let { + if (it.size > 0) { + val positions = IntArray(it.size) + for (i in positions.indices) + positions[i] = i + connection.setSelected(it, positions, newState) + } + } + } + + override fun getPerformerEngine(): IPerformerEngine? { + return provider.getPerformerEngine() + } + + private fun updateTitle(totalSelections: Int) { + // For local selections, the menu may be invalidated or may be just created meaning the menu item may not be + // available yet. + previewSelections.title = totalSelections.toString() + previewSelections.isEnabled = totalSelections > 0 + } + } + + companion object { + private val TAG = ListingFragment::class.simpleName + + const val ARG_SELECT_BY_CLICK = "argSelectByClick" + + const val ARG_HAS_BOTTOM_SPACE = "argHasBottomSpace" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/app/ListingFragmentBase.kt b/app/src/main/java/org/monora/uprotocol/client/android/app/ListingFragmentBase.kt new file mode 100755 index 000000000..7c10ded5d --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/app/ListingFragmentBase.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.app + +import android.content.Context +import android.net.Uri +import androidx.fragment.app.FragmentActivity +import androidx.recyclerview.widget.RecyclerView +import org.monora.uprotocol.client.android.widget.ListingAdapterBase +import com.genonbeta.android.framework.app.ListingFragmentBase +import com.genonbeta.android.framework.util.actionperformer.IEngineConnection +import com.genonbeta.android.framework.util.actionperformer.PerformerEngineProvider +import com.genonbeta.android.framework.util.actionperformer.SelectionHost +import com.genonbeta.android.framework.util.actionperformer.SelectionModelProvider +import org.monora.uprotocol.client.android.model.ContentModel + +/** + * created by: veli + * date: 14/04/18 10:35 + */ +interface ListingFragmentBase : ListingFragmentBase, PerformerEngineProvider, + IEngineConnection.SelectionListener, SelectionModelProvider, SelectionHost { + val adapterImpl: ListingAdapterBase + + val engineConnection: IEngineConnection + + var filteringDelegate: ListingFragment.FilteringDelegate + + var filteringSupported: Boolean + + val listView: RecyclerView + + var localSelectionActivated: Boolean + + val localSelectionMode: Boolean + + var selectByClickEnabled: Boolean + + var refreshRequested: Boolean + + fun getUniqueSettingKey(setting: String): String + + fun openUri(uri: Uri): Boolean + + fun loadIfRequested(): Boolean + + fun requireActivity(): FragmentActivity + + fun requireContext(): Context +} diff --git a/app/src/main/java/org/monora/uprotocol/client/android/backend/BackgroundBackend.kt b/app/src/main/java/org/monora/uprotocol/client/android/backend/BackgroundBackend.kt new file mode 100644 index 000000000..c65b180e8 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/backend/BackgroundBackend.kt @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.backend + +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.media.MediaScannerConnection +import android.net.wifi.WifiConfiguration +import android.net.wifi.WifiManager +import android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF +import android.os.Build +import android.provider.Settings +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import fi.iki.elonen.NanoHTTPD +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import org.monora.uprotocol.client.android.App +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.activity.PickClientActivity +import org.monora.uprotocol.client.android.activity.TransferDetailActivity +import org.monora.uprotocol.client.android.app.Activity +import org.monora.uprotocol.client.android.config.AppConfig +import org.monora.uprotocol.client.android.data.TransferRepository +import org.monora.uprotocol.client.android.database.model.Transfer +import org.monora.uprotocol.client.android.database.model.UClient +import org.monora.uprotocol.client.android.database.model.UTransferItem +import org.monora.uprotocol.client.android.model.Identity +import org.monora.uprotocol.client.android.receiver.BgBroadcastReceiver +import org.monora.uprotocol.client.android.service.BackgroundService +import org.monora.uprotocol.client.android.service.WebShareServer +import org.monora.uprotocol.client.android.service.backgroundservice.AsyncTask +import org.monora.uprotocol.client.android.util.* +import org.monora.uprotocol.core.TransportSession +import java.util.* +import java.util.concurrent.Executors +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class BackgroundBackend @Inject constructor( + @ApplicationContext val context: Context, + private val transferRepository: TransferRepository, + private val transportSession: TransportSession, + private val nsdDaemon: NsdDaemon, + private val webShareServer: WebShareServer, +) { + private val bgIntent = Intent(context, BackgroundService::class.java) + + private val bgStopIntent = Intent(context, BackgroundService::class.java).also { + it.action = BackgroundService.ACTION_STOP_BG_SERVICE + } + + val bgNotification + get() = taskNotification?.takeIf { hasTasks() } ?: notificationHelper.foregroundNotification + + private val executor = Executors.newFixedThreadPool(10) + + private var foregroundActivitiesCount = 0 + + private var foregroundActivity: Activity? = null + + private var hotspotManager = HotspotManager.newInstance(context) + + var mediaScanner: MediaScannerConnection = MediaScannerConnection(context, null) + + var notificationHelper = Notifications(NotificationBackend(context)) + + private val taskList: MutableList = ArrayList() + + private var taskNotification: DynamicNotification? = null + + private var taskNotificationTime: Long = 0 + + private var wifiLock: WifiManager.WifiLock? = null + + private val wifiManager: WifiManager + get() = hotspotManager.wifiManager + + fun attach(task: AsyncTask) { + runInternal(task) + } + + fun findTaskBy(identity: Identity): AsyncTask? { + val taskList = findTasksBy(identity) + return if (taskList.isNotEmpty()) taskList[0] else null + } + + @Synchronized + fun findTasksBy(identity: Identity): List { + synchronized(taskList) { return findTasksBy(taskList, identity) } + } + + fun getHotspotConfig(): WifiConfiguration? { + return hotspotManager.configuration + } + + fun getTaskListOf(clazz: Class): List { + synchronized(taskList) { return getTaskListOf(taskList, clazz) } + } + + fun hasTaskOf(clazz: Class): Boolean { + synchronized(taskList) { return hasTaskOf(taskList, clazz) } + } + + fun hasTasks(): Boolean { + return taskList.size > 0 + } + + fun interruptTasksBy(identity: Identity, userAction: Boolean) { + synchronized(taskList) { for (task in findTasksBy(identity)) task.interrupt(userAction) } + } + + fun interruptAllTasks() { + synchronized(taskList) { + for (task in taskList) { + task.interrupt(false) + Log.d(TAG, "interruptAllTasks(): Ongoing task stopped: " + task.getName(context)) + } + } + } + + @Synchronized + fun notifyActivityInForeground(activity: Activity, inForeground: Boolean) { + if (!inForeground && foregroundActivitiesCount == 0) return + val wasInForeground = foregroundActivitiesCount > 0 + foregroundActivitiesCount += if (inForeground) 1 else -1 + val inBg = wasInForeground && foregroundActivitiesCount == 0 + val newlyInFg = !wasInForeground && foregroundActivitiesCount == 1 + + if (Permissions.checkRunningConditions(context)) { + takeBgServiceFgIfNeeded(newlyInFg, inBg) + } + + foregroundActivity = if (inBg) null else if (inForeground) activity else foregroundActivity + Log.d(TAG, "notifyActivityInForeground: Count: $foregroundActivitiesCount") + } + + fun notifyFileRequest(device: UClient, transfer: Transfer, itemList: List) { + // Don't show when in the Add Device activity + if (foregroundActivity is PickClientActivity) return + + val activity = foregroundActivity + val numberOfFiles = itemList.size + val acceptIntent: Intent = Intent(context, BackgroundService::class.java) + .setAction(BgBroadcastReceiver.ACTION_FILE_TRANSFER) + .putExtra(BgBroadcastReceiver.EXTRA_DEVICE, device) + .putExtra(BgBroadcastReceiver.EXTRA_TRANSFER, transfer) + .putExtra(BgBroadcastReceiver.EXTRA_ACCEPTED, true) + val rejectIntent = (acceptIntent.clone() as Intent) + .putExtra(BgBroadcastReceiver.EXTRA_ACCEPTED, false) + val transferDetail: Intent = Intent(context, TransferDetailActivity::class.java) + .setAction(TransferDetailActivity.ACTION_LIST_TRANSFERS) + .putExtra(TransferDetailActivity.EXTRA_TRANSFER, transfer) + val message = if (numberOfFiles > 1) context.resources.getQuantityString( + R.plurals.ques_receiveMultipleFiles, + numberOfFiles, numberOfFiles + ) else itemList[0].name + + if (activity == null) { + notificationHelper.notifyTransferRequest( + device, transfer, acceptIntent, rejectIntent, transferDetail, message + ) + } else { + val builder: AlertDialog.Builder = AlertDialog.Builder(activity) + .setTitle(context.getString(R.string.text_deviceFileTransferRequest, device.clientNickname)) + .setMessage(message) + .setCancelable(false) + .setNeutralButton(R.string.butn_show) { _: DialogInterface?, _: Int -> + activity.startActivity(transferDetail) + } + .setNegativeButton(R.string.butn_reject) { _: DialogInterface?, _: Int -> + ContextCompat.startForegroundService(activity, rejectIntent) + } + .setPositiveButton(R.string.butn_accept) { _: DialogInterface?, _: Int -> + ContextCompat.startForegroundService(activity, acceptIntent) + } + activity.runOnUiThread(Runnable { builder.show() }) + } + } + + fun publishTaskNotifications(force: Boolean): Boolean { + val notified = System.nanoTime() + if (notified <= taskNotificationTime && !force) return false + if (!hasTasks()) { + takeBgServiceFgIfNeeded(newlyInFg = false, newlyInBg = false, byOthers = true) + return false + } + + var taskList: List + synchronized(this.taskList) { taskList = ArrayList(this.taskList) } + taskNotificationTime = System.nanoTime() + AppConfig.DELAY_DEFAULT_NOTIFICATION * 1e6.toLong() + taskNotification = notificationHelper.notifyTasksNotification(taskList, taskNotification) + return true + } + + @Synchronized + private fun registerWork(task: T) { + synchronized(taskList) { taskList.add(task) } + Log.d(TAG, "registerWork: " + task.javaClass.simpleName) + context.sendBroadcast(Intent(ACTION_TASK_CHANGE)) + } + + fun run(runningTask: AsyncTask) { + executor.submit { attach(runningTask) } + } + + private fun runInternal(runningTask: AsyncTask) { + registerWork(runningTask) + try { + runningTask.run(this) + } catch (e: Exception) { + e.printStackTrace() + } + unregisterWork(runningTask) + publishTaskNotifications(true) + } + + private fun start() { + val webServerRunning = webShareServer.isAlive + val commServerRunning = transportSession.isListening + + if (webServerRunning && commServerRunning) { + Log.d(TAG, "start: Services are already up") + return + } + + try { + if (!Permissions.checkRunningConditions(context)) throw Exception( + "The app doesn't have the satisfactory permissions to start the services." + ) + + wifiLock = wifiManager.createWifiLock(WIFI_MODE_FULL_HIGH_PERF, TAG).also { + it.acquire() + } + + if (!commServerRunning) { + transportSession.start() + } + + if (!webServerRunning) { + // TODO: 2/26/21 Fix bound runner + /*backend.webShareServer.setAsyncRunner( + BoundRunner(Executors.newFixedThreadPool(AppConfig.WEB_SHARE_CONNECTION_MAX)) + )*/ + webShareServer.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false) + } + + nsdDaemon.registerService() + nsdDaemon.startDiscovering() + } catch (e: Exception) { + e.printStackTrace() + } + } + + fun stop() { + try { + transportSession.stop() + } catch (ignored: Exception) { + } + + wifiLock?.takeIf { it.isHeld }?.let { + it.release() + wifiLock = null + + Log.d(TAG, "onDestroy: Releasing Wi-Fi lock") + } + + nsdDaemon.unregisterService() + nsdDaemon.stopDiscovering() + webShareServer.stop() + + GlobalScope.launch(Dispatchers.IO) { + transferRepository.hideTransfersFromWeb() + } + + if (hotspotManager.unloadPreviousConfig()) { + Log.d(TAG, "onDestroy: Stopping hotspot (previously started)=" + hotspotManager.disable()) + } + + interruptAllTasks() + } + + fun toggleHotspot() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O && !Settings.System.canWrite(context)) return + + if (hotspotManager.enabled) { + hotspotManager.disable() + } else { + val result = hotspotManager.enableConfigured(context.getString(R.string.text_appName), null) + Log.d(TAG, "toggleHotspot: Enabling=$result") + } + } + + fun takeBgServiceFgIfNeeded( + newlyInFg: Boolean, + newlyInBg: Boolean, + byOthers: Boolean = false, + forceStop: Boolean = false, + ) { + // Do not try to tweak this!!! + val hasTasks = hasTasks() && !forceStop + val hasServices = (hotspotManager.started || webShareServer.hadClients) && !forceStop + val inFgNow = foregroundActivitiesCount > 0 + val inBgNow = !inFgNow + + if (newlyInFg) { + start() + } else if (inBgNow && !hasServices && !hasTasks) { + stop() + } else if (newlyInBg && (hasServices || hasTasks)) { + ContextCompat.startForegroundService(context, bgIntent) + } + + // Fg checking is for avoiding unnecessary invocation by activities in fg + if (newlyInFg || (inFgNow && byOthers) || (inBgNow && !hasServices && !hasTasks)) { + ContextCompat.startForegroundService(context, bgStopIntent) + } + + if (!hasTasks) { + if (hasServices && inBgNow) { + notificationHelper.foregroundNotification.show() + } else { + notificationHelper.foregroundNotification.cancel() + } + } + } + + @Synchronized + private fun unregisterWork(task: AsyncTask) { + synchronized(taskList) { taskList.remove(task) } + Log.d(TAG, "unregisterWork: " + task.javaClass.simpleName) + context.sendBroadcast(Intent(ACTION_TASK_CHANGE)) + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private inner class SecondaryHotspotCallback : WifiManager.LocalOnlyHotspotCallback() { + override fun onStarted(reservation: WifiManager.LocalOnlyHotspotReservation) { + super.onStarted(reservation) + context.sendBroadcast( + Intent(ACTION_OREO_HOTSPOT_STARTED).putExtra(EXTRA_HOTSPOT_CONFIG, reservation.wifiConfiguration) + ) + } + } + + companion object { + private val TAG = App::class.simpleName + + const val ACTION_OREO_HOTSPOT_STARTED = "org.monora.trebleshot.intent.action.HOTSPOT_STARTED" + + const val ACTION_TASK_CHANGE = "org.monora.uprotocol.client.android.transaction.action.TASK_STATUS_CHANGE" + + const val EXTRA_HOTSPOT_CONFIG = "hotspotConfig" + + fun findTasksBy(taskList: List, identity: Identity): List { + val foundList: MutableList = ArrayList() + for (task in taskList) if (identity == task.identity) foundList.add(task) + return foundList + } + + fun getTaskListOf(taskList: List, clazz: Class): List { + val foundList: MutableList = ArrayList() + for (task in taskList) if (clazz.isInstance(task)) foundList.add(task as T) + return foundList + } + + fun hasTaskOf(taskList: List, clazz: Class): Boolean { + for (task in taskList) if (clazz.isInstance(task)) return true + return false + } + + fun hasTaskWith(taskList: List, identity: Identity): Boolean { + for (task in taskList) if (identity == task.identity) return true + return false + } + } + + init { + mediaScanner.connect() + if (Build.VERSION.SDK_INT >= 26) hotspotManager.secondaryCallback = SecondaryHotspotCallback() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/backend/Destination.kt b/app/src/main/java/org/monora/uprotocol/client/android/backend/Destination.kt new file mode 100644 index 000000000..12cae7c86 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/backend/Destination.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.backend + +class Destination { +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/backend/OperationBackend.kt b/app/src/main/java/org/monora/uprotocol/client/android/backend/OperationBackend.kt new file mode 100644 index 000000000..68d1d23a5 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/backend/OperationBackend.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.backend + +import android.content.Context +import org.monora.uprotocol.client.android.App + +interface OperationBackend { + val app: App + + val context: Context + + +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/backend/SharingBackend.kt b/app/src/main/java/org/monora/uprotocol/client/android/backend/SharingBackend.kt new file mode 100644 index 000000000..c5dceff1e --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/backend/SharingBackend.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.backend; + +public class SharingBackend +{ +} diff --git a/app/src/main/java/org/monora/uprotocol/client/android/backend/TaskBackend.kt b/app/src/main/java/org/monora/uprotocol/client/android/backend/TaskBackend.kt new file mode 100644 index 000000000..c6bc4a11e --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/backend/TaskBackend.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.backend + +class TaskBackend { + +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/binding/ContentBindings.kt b/app/src/main/java/org/monora/uprotocol/client/android/binding/ContentBindings.kt new file mode 100644 index 000000000..96dab706d --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/binding/ContentBindings.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.binding + +import android.widget.ImageView +import androidx.databinding.BindingAdapter +import org.monora.uprotocol.client.android.GlideApp +import org.monora.uprotocol.client.android.database.model.UTransferItem +import org.monora.uprotocol.client.android.util.MimeIcons +import org.monora.uprotocol.core.transfer.TransferItem + +@BindingAdapter("thumbnailOf") +fun loadThumbnailOf(imageView: ImageView, transferItem: UTransferItem) { + if (transferItem.type == TransferItem.Type.Outgoing) { + GlideApp.with(imageView) + .load(transferItem.location) + .circleCrop() + .into(imageView) + } +} + +@BindingAdapter("iconOf") +fun loadIconOf(imageView: ImageView, transferItem: UTransferItem) { + imageView.setImageResource(MimeIcons.loadMimeIcon(transferItem.mimeType)) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/binding/DateBindings.kt b/app/src/main/java/org/monora/uprotocol/client/android/binding/DateBindings.kt new file mode 100644 index 000000000..eee8a4125 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/binding/DateBindings.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.binding + +import android.text.format.DateUtils +import android.widget.TextView +import androidx.databinding.BindingAdapter + +@BindingAdapter("clock") +fun toClock(textView: TextView, time: Long) { + textView.text = DateUtils.formatDateTime(textView.context, time, DateUtils.FORMAT_SHOW_TIME) +} diff --git a/app/src/main/java/org/monora/uprotocol/client/android/binding/UserProfileBindings.kt b/app/src/main/java/org/monora/uprotocol/client/android/binding/UserProfileBindings.kt new file mode 100644 index 000000000..70db225f2 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/binding/UserProfileBindings.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.binding + +import android.widget.EditText +import android.widget.ImageView +import androidx.core.widget.addTextChangedListener +import androidx.databinding.BindingAdapter +import androidx.preference.PreferenceManager +import org.monora.uprotocol.client.android.GlideApp +import org.monora.uprotocol.client.android.data.UserDataRepository +import org.monora.uprotocol.client.android.util.Graphics +import org.monora.uprotocol.client.android.viewmodel.UserProfileViewModel +import org.monora.uprotocol.core.protocol.Client + +@BindingAdapter("listenNicknameChanges") +fun listenNicknameChanges(editText: EditText, viewModel: UserProfileViewModel) { + editText.addTextChangedListener { editable -> + val nickname = editable.toString().also { if (it.isEmpty()) return@addTextChangedListener } + + PreferenceManager.getDefaultSharedPreferences(editText.context).edit() + .putString(UserDataRepository.KEY_NICKNAME, nickname) + .apply() + } +} + +@BindingAdapter("pictureOf") +fun loadPictureOfClient(imageView: ImageView, client: Client?) { + if (client == null) return + + try { + val default = Graphics.createIconBuilder(imageView.context).buildRound(client.clientNickname) + + if (client.hasPicture()) { + GlideApp.with(imageView) + .load(client.clientPictureData) + .circleCrop() + .error(default) + .into(imageView) + } else { + imageView.setImageDrawable(default) + } + } catch (ignored: Exception) { + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/binding/ViewBindings.kt b/app/src/main/java/org/monora/uprotocol/client/android/binding/ViewBindings.kt new file mode 100644 index 000000000..1579b33c4 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/binding/ViewBindings.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.binding + +import android.view.View +import androidx.databinding.BindingAdapter + +@BindingAdapter("onLongClick") +fun assignLongClickListener(view: View, clickListener: View.OnClickListener) { + view.setOnLongClickListener { + clickListener.onClick(it) + true + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/concurrent/SingleLiveEvent.kt b/app/src/main/java/org/monora/uprotocol/client/android/concurrent/SingleLiveEvent.kt new file mode 100644 index 000000000..976eea49b --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/concurrent/SingleLiveEvent.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.monora.uprotocol.client.android.concurrent + +import android.util.Log +import androidx.annotation.MainThread +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import java.util.concurrent.atomic.AtomicBoolean + +/** + * A lifecycle-aware observable that sends only new updates after subscription, used for events like + * navigation and Snackbar messages. + * + * This avoids a common problem with events: on configuration change (like rotation) an update + * can be emitted if the observer is active. This LiveData only calls the observable if there's an + * explicit call to setValue() or call(). + * + * Note that only one observer is going to be notified of changes. + */ +class SingleLiveEvent : MutableLiveData() { + private val pending = AtomicBoolean(false) + + @MainThread + override fun observe(owner: LifecycleOwner, observer: Observer) { + if (hasActiveObservers()) { + Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") + } + + // Observe the internal MutableLiveData + super.observe(owner) { + if (pending.compareAndSet(true, false)) { + observer.onChanged(it) + } + } + } + + @MainThread + override fun setValue(t: T?) { + pending.set(true) + super.setValue(t) + } + + /** + * Used for cases where T is Void, to make calls cleaner. + */ + @MainThread + fun call() { + value = null + } + + companion object { + private const val TAG = "SingleLiveEvent" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/config/AppConfig.kt b/app/src/main/java/org/monora/uprotocol/client/android/config/AppConfig.kt new file mode 100755 index 000000000..3686aba89 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/config/AppConfig.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.config + +object AppConfig { + const val SERVER_PORT_WEBSHARE = 58732 + + const val DEFAULT_TIMEOUT_SOCKET = 5000 + + const val DEFAULT_TIMEOUT_HOTSPOT = 40000 + + const val DELAY_DEFAULT_NOTIFICATION = 1000 + + const val BUFFER_LENGTH_DEFAULT = 8096 + + const val DELAY_UPDATE_CHECK = 21600 + + const val WEB_SHARE_CONNECTION_MAX = 20 + + const val ID_GROUP_WEB_SHARE = 10L + + const val EMAIL_DEVELOPER = "trebleshot@monora.org" + + const val URI_API_GITHUB = "https://api.github.com/" + + const val URI_REPO = "repos/trebleshot/android" + + const val URI_REPO_APP_UPDATE = "https://api.github.com/repos/trebleshot/android/releases" + + const val URI_REPO_APP_CONTRIBUTORS = "https://api.github.com/repos/trebleshot/android/contributors" + + const val URI_GOOGLE_PLAY = "https://play.google.com/store/apps/details?id=org.monora.uprotocol.client.android" + + const val URI_ORG_HOME = "https://monora.org" + + const val URI_REPO_APP = "http://github.com/trebleshot" + + const val URI_REPO_ORG = "http://github.com/monoraorg" + + const val URI_GITHUB_PROFILE = "https://github.com/%s" + + const val URI_TRANSLATE = "https://github.com/genonbeta/TrebleShot/wiki/Language-contribution" + + const val URI_TELEGRAM_CHANNEL = "https://t.me/trebleshot" + + const val PREFIX_ACCESS_POINT = "TS_" + + const val EXT_FILE_PART = "tshare" + + const val KEY_GOOGLE_PUBLIC = ("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk1peq7MhNms9ynhnoRtwxnb" + + "izdEr3TKifUGlUPB3r33WkvPWjwowRvYeuY36+CkBmtjc46Xg/6/jrhPY+L0a+wd58lsNxLUMpo7" + + "tN2std0TGrsMmmlihb4Bsxcu/6ThsY4CIQx0bdze2v8Zle3e4EoHuXcqQnpwkb+3wMx2rR2E9ih+" + + "6utqrYAop9NdAbsRZ6BDXDUgJEuiHnRKwDZGDjU5PD4TCiR1jz2YJPYiRuI1QytJM6LirJu6YwE/" + + "o6pfzSQ3xXlK4yGpGUhzLdTmSNQNIJTWRqZoM7qNgp+0ocmfQRJ32/6E+BxbJaVbHdTINhbVAvLR" + + "+UFyQ2FldecfuQQIDAQAB") + + val DEFAULT_DISABLED_INTERFACES = arrayOf("rmnet") +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/config/Keyword.kt b/app/src/main/java/org/monora/uprotocol/client/android/config/Keyword.kt new file mode 100755 index 000000000..7bf98e9fd --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/config/Keyword.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.config + +/** + * Created by: veli + * Date: 4/28/17 8:29 PM + */ +object Keyword { + const val QR_CODE_TYPE_HOTSPOT = "hs" + + const val QR_CODE_TYPE_WIFI = "wf" +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/data/ClientRepository.kt b/app/src/main/java/org/monora/uprotocol/client/android/data/ClientRepository.kt new file mode 100644 index 000000000..c9beda7e9 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/data/ClientRepository.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.data + +import androidx.lifecycle.LiveData +import org.monora.uprotocol.client.android.database.ClientAddressDao +import org.monora.uprotocol.client.android.database.ClientDao +import org.monora.uprotocol.client.android.database.model.UClient +import org.monora.uprotocol.client.android.database.model.UClientAddress +import org.monora.uprotocol.client.android.util.NsdDaemon +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ClientRepository @Inject constructor( + private val clientDao: ClientDao, + private val clientAddressDao: ClientAddressDao, +) { + suspend fun delete(client: UClient) = clientDao.delete(client) + + suspend fun get(uid: String): UClient? = clientDao.get(uid) + + fun getAll(): LiveData> = clientDao.getAll() + + suspend fun getAddresses(clientUid: String): List = clientAddressDao.getAll(clientUid) + + suspend fun insert(client: UClient) = clientDao.insert(client) + + suspend fun insert(address: UClientAddress) = clientAddressDao.insert(address) + + suspend fun update(client: UClient) = clientDao.update(client) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/data/GitHubDataRepository.kt b/app/src/main/java/org/monora/uprotocol/client/android/data/GitHubDataRepository.kt new file mode 100644 index 000000000..937490313 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/data/GitHubDataRepository.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.data + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.monora.uprotocol.client.android.remote.GitHubService +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GitHubDataRepository @Inject constructor( + private val gitHubService: GitHubService, +) { + suspend fun getContributors() = withContext(Dispatchers.IO) { gitHubService.contributors() } + + suspend fun getReleases() = withContext(Dispatchers.IO) { gitHubService.releases() } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/data/LicensesRepository.kt b/app/src/main/java/org/monora/uprotocol/client/android/data/LicensesRepository.kt new file mode 100644 index 000000000..7e602e5e6 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/data/LicensesRepository.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.data + +import android.content.Context +import android.util.Log +import com.google.gson.Gson +import com.google.gson.stream.JsonReader +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.monora.uprotocol.client.android.fragment.external.LicensesFragment +import org.monora.uprotocol.client.android.model.LibraryLicense +import java.io.InputStreamReader +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LicensesRepository @Inject constructor( + @ApplicationContext private val context: Context, +) { + suspend fun getLicenses(): List = withContext(Dispatchers.IO) { + val list = mutableListOf() + + context.assets.open("licenses.json").use { inputStream -> + val jsonReader = JsonReader(InputStreamReader(inputStream)) + val gson = Gson() + + try { + // skip to the "libraries" index + jsonReader.beginObject() + + if (!jsonReader.hasNext()) { + return@use + } else if (jsonReader.nextName() != "libraries") { + Log.d(LicensesFragment::class.simpleName, "onViewCreated: 'libraries' does not exist") + return@use + } + + jsonReader.beginArray() + + while (jsonReader.hasNext()) { + list.add(gson.fromJson(jsonReader, LibraryLicense::class.java)) + } + + list.sortBy { it.artifactId.group } + + jsonReader.endArray() + jsonReader.endObject() + } catch (e: Exception) { + e.printStackTrace() + } + } + + list + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/data/OnlineClientRepository.kt b/app/src/main/java/org/monora/uprotocol/client/android/data/OnlineClientRepository.kt new file mode 100644 index 000000000..2f1e20fc7 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/data/OnlineClientRepository.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.data + +import org.monora.uprotocol.client.android.util.NsdDaemon +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class OnlineClientRepository @Inject constructor( + private val nsdDaemon: NsdDaemon +){ + fun getOnlineClients() = nsdDaemon.onlineClients +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/data/SharedTextRepository.kt b/app/src/main/java/org/monora/uprotocol/client/android/data/SharedTextRepository.kt new file mode 100644 index 000000000..433e994a2 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/data/SharedTextRepository.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.data + +import androidx.lifecycle.LiveData +import org.monora.uprotocol.client.android.database.SharedTextDao +import org.monora.uprotocol.client.android.database.model.SharedText +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SharedTextRepository @Inject constructor( + private val sharedTextDao: SharedTextDao, +) { + suspend fun delete(sharedText: SharedText) = sharedTextDao.delete(sharedText) + + fun getSharedTexts(): LiveData> = sharedTextDao.getAll() + + suspend fun insert(sharedText: SharedText) = sharedTextDao.insert(sharedText) + + suspend fun update(sharedText: SharedText) = sharedTextDao.update(sharedText) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/data/TransferRepository.kt b/app/src/main/java/org/monora/uprotocol/client/android/data/TransferRepository.kt new file mode 100644 index 000000000..d05ba3ceb --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/data/TransferRepository.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.data + +import org.monora.uprotocol.client.android.database.TransferDao +import org.monora.uprotocol.client.android.database.TransferItemDao +import org.monora.uprotocol.client.android.database.model.Transfer +import org.monora.uprotocol.client.android.database.model.UTransferItem +import org.monora.uprotocol.core.transfer.TransferItem +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class TransferRepository @Inject constructor( + private val transferDao: TransferDao, + private val transferItemDao: TransferItemDao, +) { + suspend fun containsTransfer(groupId: Long) = transferDao.contains(groupId) + + suspend fun delete(transferItem: UTransferItem) = transferItemDao.delete(transferItem) + + suspend fun getReceivable(groupId: Long) = transferItemDao.getReceivable(groupId) + + suspend fun getTransfer(transferId: Long): Transfer? = transferDao.get(transferId) + + suspend fun getTransfer( + transferId: Long, + clientUid: String, + type: TransferItem.Type, + ): Transfer? = transferDao.get(transferId, clientUid, type) + + suspend fun getTransferItem(groupId: Long, id: Long) = transferItemDao.get(groupId, id) + + suspend fun getTransferItem( + groupId: Long, + id: Long, + type: TransferItem.Type, + ) = transferItemDao.get(groupId, id, type) + + suspend fun getTransferItem(location: String, type: TransferItem.Type) = transferItemDao.get(location, type) + + fun getTransfers() = transferDao.getAll() + + suspend fun hideTransfersFromWeb() = transferDao.hideTransfersFromWeb() + + suspend fun insert(transfer: Transfer) = transferDao.insert(transfer) + + suspend fun insert(list: List) = transferItemDao.insert(list) + + suspend fun update(transfer: Transfer) = transferDao.update(transfer) + + suspend fun update(transferItem: UTransferItem) = transferItemDao.update(transferItem) + + suspend fun updateTemporaryFailuresAsPending( + groupId: Long, + ) = transferItemDao.updateTemporaryFailuresAsPending(groupId) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/data/UserDataRepository.kt b/app/src/main/java/org/monora/uprotocol/client/android/data/UserDataRepository.kt new file mode 100644 index 000000000..161a1b4f5 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/data/UserDataRepository.kt @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.data + +import android.content.Context +import android.content.SharedPreferences +import android.os.Build +import android.util.Base64 +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.preference.PreferenceManager +import dagger.hilt.android.qualifiers.ApplicationContext +import org.monora.uprotocol.client.android.BuildConfig +import org.monora.uprotocol.client.android.database.model.UClient +import org.monora.uprotocol.core.protocol.ClientType +import org.monora.uprotocol.core.spec.v1.Config +import org.spongycastle.asn1.x500.X500Name +import org.spongycastle.asn1.x500.X500NameBuilder +import org.spongycastle.asn1.x500.style.BCStyle +import org.spongycastle.asn1.x500.style.IETFUtils +import org.spongycastle.cert.X509CertificateHolder +import org.spongycastle.cert.X509v3CertificateBuilder +import org.spongycastle.cert.jcajce.JcaX509CertificateConverter +import org.spongycastle.cert.jcajce.JcaX509v3CertificateBuilder +import org.spongycastle.jce.provider.BouncyCastleProvider +import org.spongycastle.operator.jcajce.JcaContentSignerBuilder +import java.math.BigInteger +import java.security.KeyFactory +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.cert.X509Certificate +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.X509EncodedKeySpec +import java.time.LocalDate +import java.time.ZoneId +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class UserDataRepository @Inject constructor( + @ApplicationContext private val context: Context, +) { + private val bouncyCastleProvider: BouncyCastleProvider = BouncyCastleProvider() + + private val client by lazy { + MutableLiveData(clientStatic()) + } + + private val clientNickname by lazy { + MutableLiveData(clientNicknameStatic()) + } + + private val preferencesListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + if (key == KEY_NICKNAME || key == KEY_PICTURE_CHECKSUM) { + client.postValue(clientStatic()) + clientNickname.postValue(clientNicknameStatic()) + } + } + + private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context).also { + it.registerOnSharedPreferenceChangeListener(preferencesListener) + } + + val keyFactory: KeyFactory = KeyFactory.getInstance("RSA") + + val keyPair: KeyPair by lazy { + val publicKey = preferences.getString(KEY_PUBLIC_KEY, null) + val privateKey = preferences.getString(KEY_PRIVATE_KEY, null) + + if (publicKey == null || privateKey == null) { + val keyPairGenerator = KeyPairGenerator.getInstance("RSA") + keyPairGenerator.initialize(2048) + val keyPair = keyPairGenerator.genKeyPair() + + preferences.edit() + .putString(KEY_PUBLIC_KEY, Base64.encodeToString(keyPair.public.encoded, Base64.DEFAULT)) + .putString(KEY_PRIVATE_KEY, Base64.encodeToString(keyPair.private.encoded, Base64.DEFAULT)) + .apply() + + return@lazy keyPair + } + + return@lazy KeyPair( + keyFactory.generatePublic(X509EncodedKeySpec(Base64.decode(publicKey, Base64.DEFAULT))), + keyFactory.generatePrivate(PKCS8EncodedKeySpec(Base64.decode(privateKey, Base64.DEFAULT))), + ) + } + + val certificate: X509Certificate by lazy { + val certificateIndex = preferences.getString(KEY_CERTIFICATE, null) + + if (certificateIndex != null) { + val certificateHolder = X509CertificateHolder(Base64.decode(certificateIndex, Base64.DEFAULT)) + val cert: X509Certificate = JcaX509CertificateConverter().setProvider(bouncyCastleProvider) + .getCertificate(certificateHolder) + + try { + val principal = cert.subjectX500Principal + val x500name = X500Name(principal.name) + val rdn = x500name.getRDNs(BCStyle.CN)[0] + val commonName = IETFUtils.valueToString(rdn.first.value) + + if (clientUid() != commonName) { + throw IllegalArgumentException("The client uid changed. The certificate should be regenerated.") + } + + return@lazy cert + } catch (e: Exception) { + + } + } + + // TODO: 1/22/21 Don't forget to change the locale to English in production environments when it is set to + // Persian to fix the issue: https://issuetracker.google.com/issues/37095309 + val nameBuilder = X500NameBuilder(BCStyle.INSTANCE) + + nameBuilder.addRDN(BCStyle.CN, clientUid()) + nameBuilder.addRDN(BCStyle.OU, "uprotocol") + nameBuilder.addRDN(BCStyle.O, "monora") + val localDate = LocalDate.now().minusYears(1) + val notBefore = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant() + val notAfter = localDate.plusYears(10).atStartOfDay(ZoneId.systemDefault()).toInstant() + val certificateBuilder: X509v3CertificateBuilder = JcaX509v3CertificateBuilder( + nameBuilder.build(), BigInteger.ONE, Date.from(notBefore), Date.from(notAfter), + nameBuilder.build(), keyPair.public + ) + val contentSigner = JcaContentSignerBuilder("SHA256WithRSAEncryption") + .setProvider(bouncyCastleProvider).build(keyPair.private) + val cert = JcaX509CertificateConverter().setProvider(bouncyCastleProvider) + .getCertificate(certificateBuilder.build(contentSigner)) + + preferences.edit() + .putString(KEY_CERTIFICATE, Base64.encodeToString(cert.encoded, Base64.DEFAULT)) + .apply() + + return@lazy cert + } + + fun client(): LiveData = client + + fun clientStatic() = UClient( + clientUid(), + clientNicknameStatic(), + Build.MANUFACTURER, + Build.PRODUCT, + ClientType.Portable, + BuildConfig.VERSION_NAME, + BuildConfig.VERSION_CODE, + Config.VERSION_UPROTOCOL, + Config.VERSION_UPROTOCOL_MIN, + System.currentTimeMillis(), + blocked = false, + local = true, + trusted = true, + certificate, + clientPictureFile(), + clientPictureChecksum() + ) + + fun clientNickname(): LiveData = clientNickname + + fun clientNicknameStatic() = preferences.getString( + KEY_NICKNAME, null + ) ?: Build.MODEL.toUpperCase(Locale.getDefault()) + + fun clientUid() = preferences.getString(KEY_UUID, null) ?: UUID.randomUUID().toString().also { + preferences.edit() + .putString(KEY_UUID, it) + .apply() + } + + private fun clientPictureFile() = with(context.getFileStreamPath(FILE_CLIENT_PICTURE)) { + if (isFile) this else null + } + + private fun clientPictureChecksum() = preferences.getInt(KEY_PICTURE_CHECKSUM, 0) + + companion object { + private const val KEY_CERTIFICATE = "certificate" + + const val KEY_NICKNAME = "client_nickname" + + const val KEY_PICTURE_CHECKSUM = "picture_checksum" + + private const val KEY_PRIVATE_KEY = "private_key" + + private const val KEY_PUBLIC_KEY = "public_key" + + private const val KEY_UUID = "uuid" + + const val FILE_CLIENT_PICTURE = "localPicture" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/AppDatabase.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/AppDatabase.kt new file mode 100644 index 000000000..329a456d4 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/AppDatabase.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import org.monora.uprotocol.client.android.database.model.* + +@Database( + entities = [ + UClient::class, + UClientAddress::class, + UTransferItem::class, + SharedText::class, + Transfer::class, + WebClient::class + ], + version = 1 +) +@TypeConverters(ClientTypeConverter::class, IOTypeConverter::class, TransferItemTypeConverter::class) +abstract class AppDatabase : RoomDatabase() { + abstract fun clientDao(): ClientDao + + abstract fun clientAddressDao(): ClientAddressDao + + abstract fun sharedTextDao(): SharedTextDao + + abstract fun transferDao(): TransferDao + + abstract fun transferItemDao(): TransferItemDao + + abstract fun webClientDao(): WebClientDao +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/ClientAddressDao.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/ClientAddressDao.kt new file mode 100644 index 000000000..f8c94ef5e --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/ClientAddressDao.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database + +import androidx.room.* +import org.monora.uprotocol.client.android.database.model.UClientAddress + +@Dao +interface ClientAddressDao { + @Query("SELECT * FROM clientAddress WHERE clientUid = :clientUid") + suspend fun getAll(clientUid: String): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(address: UClientAddress) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/ClientDao.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/ClientDao.kt new file mode 100644 index 000000000..e1e995616 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/ClientDao.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database + +import androidx.lifecycle.LiveData +import androidx.room.* +import org.monora.uprotocol.client.android.database.model.UClient + +@Dao +interface ClientDao { + @Delete + suspend fun delete(client: UClient) + + @Query("SELECT * FROM client WHERE uid == :uid LIMIT 1") + suspend fun get(uid: String): UClient? + + @Query("SELECT * FROM client") + fun getAll(): LiveData> + + @Insert + suspend fun insert(client: UClient) + + @Update + suspend fun update(client: UClient) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/ClientTypeConverter.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/ClientTypeConverter.kt new file mode 100644 index 000000000..35f017f76 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/ClientTypeConverter.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database + +import android.util.Base64 +import androidx.room.TypeConverter +import org.monora.uprotocol.core.protocol.ClientType +import org.spongycastle.cert.X509CertificateHolder +import org.spongycastle.cert.jcajce.JcaX509CertificateConverter +import org.spongycastle.jce.provider.BouncyCastleProvider +import java.security.cert.X509Certificate + +class ClientTypeConverter { + private val bouncyCastleProvider = BouncyCastleProvider() + + @TypeConverter + fun fromCertificate(value: X509Certificate?): String? { + if (value == null) + return null + return Base64.encodeToString(value.encoded, Base64.DEFAULT) + } + + @TypeConverter + fun toCertificate(value: String?): X509Certificate? { + if (value == null) + return null + + return JcaX509CertificateConverter().setProvider(bouncyCastleProvider) + .getCertificate(X509CertificateHolder(Base64.decode(value, Base64.DEFAULT))) + } + + @TypeConverter + fun fromType(value: ClientType): String = value.protocolValue + + @TypeConverter + fun toType(value: String): ClientType = ClientType.from(value) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/IOTypeConverter.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/IOTypeConverter.kt new file mode 100644 index 000000000..f94214233 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/IOTypeConverter.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database + +import androidx.room.TypeConverter +import java.io.File +import java.net.InetAddress + +class IOTypeConverter { + @TypeConverter + fun fromFile(value: File?) = value?.absolutePath + + @TypeConverter + fun toFile(value: String?): File? = value?.let { File(value) } + + @TypeConverter + fun fromInetAddress(value: InetAddress): String = value.hostAddress + + @TypeConverter + fun toInetAddress(value: String): InetAddress = InetAddress.getByName(value) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/SharedTextDao.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/SharedTextDao.kt new file mode 100644 index 000000000..494b4f931 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/SharedTextDao.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database + +import androidx.lifecycle.LiveData +import androidx.room.* +import kotlinx.coroutines.flow.Flow +import org.monora.uprotocol.client.android.database.model.SharedText + +@Dao +interface SharedTextDao { + @Delete + suspend fun delete(sharedText: SharedText) + + @Query("SELECT * FROM sharedText ORDER BY created DESC") + fun getAll(): LiveData> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(sharedText: SharedText) + + @Update + suspend fun update(sharedText: SharedText) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/TransferDao.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/TransferDao.kt new file mode 100644 index 000000000..3c3c4e376 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/TransferDao.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database; + +import androidx.lifecycle.LiveData +import androidx.room.* +import org.monora.uprotocol.client.android.database.model.Transfer; +import org.monora.uprotocol.core.transfer.TransferItem + +@Dao +interface TransferDao { + @Query("SELECT EXISTS(SELECT * FROM transfer WHERE id == :groupId)") + suspend fun contains(groupId: Long): Boolean + + @Query("SELECT * FROM transfer WHERE id = :transferId") + suspend fun get(transferId: Long): Transfer? + + @Query("SELECT * FROM transfer WHERE id = :transferId AND clientUid = :clientUid AND type = :type") + suspend fun get(transferId: Long, clientUid: String, type: TransferItem.Type): Transfer? + + @Query("SELECT * FROM transfer") + fun getAll(): LiveData> + + @Query("UPDATE transfer SET web = 0") + suspend fun hideTransfersFromWeb() + + @Insert + suspend fun insert(transfer: Transfer) + + @Update + suspend fun update(transfer: Transfer) +} diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/TransferItemDao.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/TransferItemDao.kt new file mode 100644 index 000000000..dedd85bd2 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/TransferItemDao.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database + +import androidx.room.* +import org.monora.uprotocol.client.android.database.model.UTransferItem +import org.monora.uprotocol.core.persistence.PersistenceProvider.STATE_PENDING +import org.monora.uprotocol.core.transfer.TransferItem + +@Dao +interface TransferItemDao { + @Delete + suspend fun delete(transferItem: UTransferItem) + + @Query("SELECT * FROM transferItem WHERE groupId == :groupId AND id == :id LIMIT 1") + suspend fun get(groupId: Long, id: Long): UTransferItem? + + @Query("SELECT * FROM transferItem WHERE groupId == :groupId AND state == $STATE_PENDING ORDER BY name LIMIT 1") + suspend fun getReceivable(groupId: Long): UTransferItem? + + @Query("SELECT * FROM transferItem WHERE groupId == :groupId AND id == :id AND type == :type LIMIT 1") + suspend fun get(groupId: Long, id: Long, type: TransferItem.Type): UTransferItem? + + @Query("SELECT * FROM transferItem WHERE location = :location AND type = :type") + suspend fun get(location: String, type: TransferItem.Type): UTransferItem? + + @Insert + suspend fun insert(list: List) + + @Update + suspend fun update(transferItem: UTransferItem) + + @Query("UPDATE transferItem SET state = $STATE_PENDING WHERE groupId = :groupId") + suspend fun updateTemporaryFailuresAsPending(groupId: Long) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/TransferItemTypeConverter.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/TransferItemTypeConverter.kt new file mode 100644 index 000000000..1f6223f2e --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/TransferItemTypeConverter.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database + +import androidx.room.TypeConverter +import org.monora.uprotocol.core.transfer.TransferItem + +class TransferItemTypeConverter { + @TypeConverter + fun fromType(value: TransferItem.Type): String = value.protocolValue + + @TypeConverter + fun toType(value: String): TransferItem.Type = TransferItem.Type.from(value) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/WebClientDao.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/WebClientDao.kt new file mode 100644 index 000000000..a1f59b981 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/WebClientDao.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import org.monora.uprotocol.client.android.database.model.WebClient + +@Dao +interface WebClientDao { + @Query("SELECT * FROM webClient WHERE address = :address") + suspend fun get(address: String): WebClient? + + @Insert + suspend fun insert(webClient: WebClient) +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/model/SharedText.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/model/SharedText.kt new file mode 100644 index 000000000..ddd1d3917 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/model/SharedText.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database.model + +import android.os.Parcelable +import androidx.core.util.ObjectsCompat +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.PrimaryKey +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize +import org.monora.uprotocol.client.android.backend.Destination +import org.monora.uprotocol.client.android.backend.OperationBackend +import org.monora.uprotocol.client.android.backend.SharingBackend +import org.monora.uprotocol.client.android.model.ContentModel +import retrofit2.http.Field + +@Parcelize +@Entity(tableName = "sharedText") +data class SharedText( + @PrimaryKey(autoGenerate = true) + val id: Int, + var text: String, + val created: Long = System.currentTimeMillis(), + var modified: Long = created, +) : ContentModel, Parcelable { + @IgnoredOnParcel + @Ignore + var selected: Boolean = false + + override fun canCopy(): Boolean = false + + override fun canMove(): Boolean = false + + override fun canShare(): Boolean = false + + override fun canSelect(): Boolean = true + + override fun canRemove(): Boolean = true + + override fun canRename(): Boolean = false + + override fun copy(operationBackend: OperationBackend, destination: Destination): Boolean { + throw UnsupportedOperationException() + } + + override fun dateCreated(): Long = created + + override fun dateModified(): Long = modified + + override fun dateSupported(): Boolean = true + + override fun filter(charSequence: CharSequence): Boolean = text.contains(charSequence) + + override fun id(): Long = ObjectsCompat.hash(id, created).toLong() + + override fun length(): Long = throw UnsupportedOperationException() + + override fun lengthSupported(): Boolean = false + + override fun move(operationBackend: OperationBackend, destination: Destination): Boolean { + throw UnsupportedOperationException() + } + + override fun name(): String = this.text + + override fun remove(operationBackend: OperationBackend): Boolean { + TODO("Not yet implemented") + } + + override fun share(operationBackend: OperationBackend, sharingBackend: SharingBackend): Boolean { + TODO("Not yet implemented") + } + + override fun selected(): Boolean = selected + + override fun select(selected: Boolean) { + this.selected = selected + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/model/Transfer.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/model/Transfer.kt new file mode 100644 index 000000000..627019c5d --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/model/Transfer.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database.model + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.ForeignKey.CASCADE +import androidx.room.PrimaryKey +import kotlinx.parcelize.Parcelize +import org.monora.uprotocol.core.transfer.TransferItem + +@Parcelize +@Entity( + tableName = "transfer", + foreignKeys = [ + ForeignKey( + entity = UClient::class, parentColumns = ["uid"], childColumns = ["clientUid"], onDelete = CASCADE + ), + ] +) +data class Transfer( + @PrimaryKey + val id: Long, + @ColumnInfo(index = true) + val clientUid: String, + val type: TransferItem.Type, + var location: String, + var web: Boolean = false, + val dateCreated: Long = System.currentTimeMillis(), +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/model/UClient.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/model/UClient.kt new file mode 100644 index 000000000..860f10151 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/model/UClient.kt @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database.model + +import android.os.Parcelable +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.parcelize.Parcelize +import org.monora.uprotocol.core.protocol.Client +import org.monora.uprotocol.core.protocol.ClientType +import java.io.File +import java.io.FileInputStream +import java.security.cert.X509Certificate + +@Parcelize +@Entity(tableName = "client") +data class UClient( + @PrimaryKey + var uid: String, + var nickname: String, + var manufacturer: String, + var product: String, + var type: ClientType, + var versionName: String, + var versionCode: Int, + var protocolVersion: Int, + var protocolVersionMin: Int, + var lastUsageTime: Long = System.currentTimeMillis(), + var blocked: Boolean = false, + var local: Boolean = false, + var trusted: Boolean = false, + var certificate: X509Certificate? = null, + var pictureFile: File? = null, + var checksum: Int = 0, +) : Client, Parcelable { + override fun getClientCertificate(): X509Certificate? = certificate + + override fun getClientLastUsageTime(): Long = lastUsageTime + + override fun getClientManufacturer(): String = manufacturer + + override fun getClientNickname(): String = nickname + + override fun getClientProduct(): String = product + + override fun getClientProtocolVersion(): Int = protocolVersion + + override fun getClientProtocolVersionMin(): Int = protocolVersionMin + + override fun getClientType(): ClientType = type + + override fun getClientUid(): String = uid + + override fun getClientVersionCode(): Int = versionCode + + override fun getClientVersionName(): String = versionName + + override fun getClientPictureData(): ByteArray = FileInputStream(pictureFile).use { + it.readBytes() + } + + override fun getClientPictureChecksum(): Int = checksum + + override fun hasPicture(): Boolean = pictureFile?.isFile == true + + override fun isClientBlocked(): Boolean = blocked + + override fun isClientLocal(): Boolean = local + + override fun isClientTrusted(): Boolean = trusted + + override fun setClientBlocked(blocked: Boolean) { + this.blocked = blocked + } + + override fun setClientCertificate(certificate: X509Certificate?) { + this.certificate = certificate + } + + override fun setClientLastUsageTime(lastUsageTime: Long) { + this.lastUsageTime = lastUsageTime + } + + override fun setClientLocal(local: Boolean) { + this.local = local + } + + override fun setClientManufacturer(manufacturer: String) { + this.manufacturer = manufacturer + } + + override fun setClientNickname(nickname: String) { + this.nickname = nickname + } + + override fun setClientProduct(product: String) { + this.product = product + } + + override fun setClientProtocolVersion(protocolVersion: Int) { + this.protocolVersion = protocolVersion + } + + override fun setClientProtocolVersionMin(protocolVersionMin: Int) { + this.protocolVersionMin = protocolVersionMin + } + + override fun setClientTrusted(trusted: Boolean) { + this.trusted = trusted + } + + override fun setClientType(type: ClientType) { + this.type = type + } + + override fun setClientUid(uid: String) { + this.uid = uid + } + + override fun setClientVersionCode(versionCode: Int) { + this.versionCode = versionCode + } + + override fun setClientVersionName(versionName: String) { + this.versionName = versionName + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/model/UClientAddress.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/model/UClientAddress.kt new file mode 100644 index 000000000..65e7f7188 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/model/UClientAddress.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database.model + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.ForeignKey.CASCADE +import androidx.room.PrimaryKey +import kotlinx.parcelize.Parcelize +import org.monora.uprotocol.core.protocol.ClientAddress +import java.net.InetAddress + +@Parcelize +@Entity( + tableName = "clientAddress", + foreignKeys = [ + ForeignKey( + entity = UClient::class, parentColumns = ["uid"], childColumns = ["clientUid"], onDelete = CASCADE + ), + ] +) +data class UClientAddress( + @PrimaryKey + var inetAddress: InetAddress, + @ColumnInfo(index = true) + var clientUid: String, + var lastUsageTime: Long = System.currentTimeMillis(), +) : ClientAddress, Parcelable { + override fun getClientAddress(): InetAddress = inetAddress + + override fun getClientAddressLastUsageTime(): Long = lastUsageTime + + override fun getClientAddressOwnerUid(): String = clientUid + + override fun setClientAddress(inetAddress: InetAddress) { + this.inetAddress = inetAddress + } + + override fun setClientAddressLastUsageTime(lastUsageTime: Long) { + this.lastUsageTime = lastUsageTime + } + + override fun setClientAddressOwnerUid(clientUid: String) { + this.clientUid = clientUid + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/model/UTransferItem.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/model/UTransferItem.kt new file mode 100644 index 000000000..0af85437a --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/model/UTransferItem.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database.model + +import android.os.Parcelable +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.ForeignKey.CASCADE +import kotlinx.parcelize.Parcelize +import org.monora.uprotocol.core.persistence.PersistenceProvider +import org.monora.uprotocol.core.transfer.TransferItem + +@Parcelize +@Entity( + tableName = "transferItem", + primaryKeys = ["groupId", "id"], + foreignKeys = [ + ForeignKey( + entity = Transfer::class, parentColumns = ["id"], childColumns = ["groupId"], onDelete = CASCADE + ), + ] +) +data class UTransferItem( + var id: Long, + var groupId: Long, + var name: String, + var mimeType: String, + var size: Long, + var directory: String?, + var location: String, + var type: TransferItem.Type, + var state: Int = PersistenceProvider.STATE_PENDING, + var dateCreated: Long = System.currentTimeMillis(), + var dateModified: Long = dateCreated, +) : TransferItem, Parcelable { + override fun getItemDirectory(): String? = directory + + override fun getItemGroupId(): Long = groupId + + override fun getItemId(): Long = id + + override fun getItemLastChangeTime(): Long = dateModified + + override fun getItemMimeType(): String = mimeType + + override fun getItemName(): String = name + + override fun getItemSize(): Long = size + + override fun getItemType(): TransferItem.Type = type + + override fun setItemId(id: Long) { + this.id = id + } + + override fun setItemGroupId(groupId: Long) { + this.groupId = groupId + } + + override fun setItemName(name: String) { + this.name = name + } + + override fun setItemDirectory(directory: String?) { + this.directory = directory + } + + override fun setItemMimeType(mimeType: String) { + this.mimeType = mimeType + } + + override fun setItemSize(size: Long) { + this.size = size + } + + override fun setItemLastChangeTime(lastChangeTime: Long) { + this.dateModified = lastChangeTime + } + + override fun setItemType(type: TransferItem.Type) { + this.type = type + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/database/model/WebClient.kt b/app/src/main/java/org/monora/uprotocol/client/android/database/model/WebClient.kt new file mode 100644 index 000000000..f79ebb10b --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/database/model/WebClient.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.database.model + +import android.os.Parcelable +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.parcelize.Parcelize + +@Parcelize +@Entity(tableName = "webClient") +class WebClient( + @PrimaryKey val address: String, + val title: String, + val blocked: Boolean = false, + val created: Long = System.currentTimeMillis(), +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/di/GitHubModule.kt b/app/src/main/java/org/monora/uprotocol/client/android/di/GitHubModule.kt new file mode 100644 index 000000000..fc8faeaba --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/di/GitHubModule.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.components.SingletonComponent +import org.monora.uprotocol.client.android.config.AppConfig +import org.monora.uprotocol.client.android.remote.GitHubService +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Qualifier +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object GitHubModule { + @GitHubApi + @Provides + @Singleton + fun provideGitHubApiRetrofit(): Retrofit { + return Retrofit.Builder() + .baseUrl(AppConfig.URI_API_GITHUB) + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + + @Provides + @Singleton + fun provideGitHubService(@GitHubApi retrofit: Retrofit): GitHubService { + return retrofit.create(GitHubService::class.java) + } +} + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class GitHubApi \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/di/RoomModule.kt b/app/src/main/java/org/monora/uprotocol/client/android/di/RoomModule.kt new file mode 100644 index 000000000..d8109be39 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/di/RoomModule.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.di + +import android.content.Context +import androidx.room.Room +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import org.monora.uprotocol.client.android.database.* +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object RoomModule { + @Singleton + @Provides + fun provideRoomDatabase(@ApplicationContext context: Context): AppDatabase { + return Room.databaseBuilder(context, AppDatabase::class.java, "Main.db") + .build() + } + + @Provides + fun provideClientDao(appDatabase: AppDatabase): ClientDao { + return appDatabase.clientDao() + } + + @Provides + fun provideClientAddressDao(appDatabase: AppDatabase): ClientAddressDao { + return appDatabase.clientAddressDao() + } + + @Provides + fun provideSharedTextDao(appDatabase: AppDatabase): SharedTextDao { + return appDatabase.sharedTextDao() + } + + @Provides + fun provideTransferDao(appDatabase: AppDatabase): TransferDao { + return appDatabase.transferDao() + } + + @Provides + fun provideTransferItemDao(appDatabase: AppDatabase): TransferItemDao { + return appDatabase.transferItemDao() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/di/SingletonUprotocolModule.kt b/app/src/main/java/org/monora/uprotocol/client/android/di/SingletonUprotocolModule.kt new file mode 100644 index 000000000..5cfe7cabf --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/di/SingletonUprotocolModule.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.di + +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.monora.uprotocol.client.android.protocol.MainConnectionFactory +import org.monora.uprotocol.client.android.protocol.MainPersistenceProvider +import org.monora.uprotocol.client.android.protocol.MainTransportSeat +import org.monora.uprotocol.core.TransportSeat +import org.monora.uprotocol.core.TransportSession +import org.monora.uprotocol.core.persistence.PersistenceProvider +import org.monora.uprotocol.core.protocol.ConnectionFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class SingletonUprotocolModule { + @Singleton + @Binds + abstract fun provideConnectionFactory( + mainConnectionFactory: MainConnectionFactory, + ): ConnectionFactory + + @Singleton + @Binds + abstract fun providePersistenceProvider( + mainPersistenceProvider: MainPersistenceProvider, + ): PersistenceProvider + + @Singleton + @Binds + abstract fun provideTransportSeat( + mainTransportSeat: MainTransportSeat, + ): TransportSeat +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/di/TransportSessionModule.kt b/app/src/main/java/org/monora/uprotocol/client/android/di/TransportSessionModule.kt new file mode 100644 index 000000000..b540b0dcb --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/di/TransportSessionModule.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.monora.uprotocol.client.android.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.monora.uprotocol.core.TransportSeat +import org.monora.uprotocol.core.TransportSession +import org.monora.uprotocol.core.persistence.PersistenceProvider +import org.monora.uprotocol.core.protocol.ConnectionFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class TransportSessionModule { + @Singleton + @Provides + fun provideTransportSession( + connectionFactory: ConnectionFactory, + persistenceProvider: PersistenceProvider, + transportSeat: TransportSeat, + ): TransportSession { + return TransportSession(connectionFactory, persistenceProvider, transportSeat) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/AbstractFailureAwareDialog.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/AbstractFailureAwareDialog.kt new file mode 100755 index 000000000..6568d82c0 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/AbstractFailureAwareDialog.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialogimport + +import android.content.Context +import android.view.View +import androidx.appcompat.app.AlertDialog + +/** + * created by: Veli + * date: 26.02.2018 08:19 + */ +abstract class AbstractFailureAwareDialog(context: Context) : AlertDialog.Builder(context) { + private var clickListener: OnProceedClickListener? = null + + fun setOnProceedClickListener(buttonText: String, listener: OnProceedClickListener) { + setPositiveButton(buttonText, null) + clickListener = listener + } + + fun setOnProceedClickListener(buttonRes: Int, listener: OnProceedClickListener) { + setOnProceedClickListener(context.getString(buttonRes), listener) + } + + override fun show(): AlertDialog { + val dialog = super.show() + clickListener?.let { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { v: View? -> + if (it.onProceedClick(dialog)) dialog.dismiss() + } + } + + return dialog + } + + interface OnProceedClickListener { + fun onProceedClick(dialog: AlertDialog): Boolean + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/AbstractSingleTextInputDialog.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/AbstractSingleTextInputDialog.kt new file mode 100755 index 000000000..70245f164 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/AbstractSingleTextInputDialog.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialog + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.EditText +import androidx.appcompat.app.AlertDialog +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.dialogimport.AbstractFailureAwareDialog + +/** + * created by: Veli + * date: 26.02.2018 07:55 + */ +abstract class AbstractSingleTextInputDialog(context: Context) : AbstractFailureAwareDialog(context) { + private val containerView: ViewGroup = LayoutInflater.from(context).inflate( + R.layout.layout_dialog_single_text_input, null + ) as ViewGroup + + protected val editText: EditText = containerView.findViewById(R.id.layout_dialog_single_text_input_text) + + override fun show(): AlertDialog { + setView(containerView) + setNegativeButton(R.string.butn_close, null) + + val dialog = super.show() + editText.requestFocus() + return dialog + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/ChooseSharingMethodDialog.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/ChooseSharingMethodDialog.kt new file mode 100644 index 000000000..fb6ed4a7c --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/ChooseSharingMethodDialog.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialog + +import android.app.Activity +import android.content.DialogInterface +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.task.OrganizeLocalSharingTask +import org.monora.uprotocol.client.android.model.ContentModel + +class ChooseSharingMethodDialog(activity: Activity, listener: PickListener) : AlertDialog.Builder(activity) { + private val layoutInflater = LayoutInflater.from(context) + + private val sharingMethods = SharingMethod.values() + + internal inner class SharingMethodListAdapter : BaseAdapter() { + override fun getCount(): Int { + return sharingMethods.size + } + + override fun getItem(position: Int): Any { + return sharingMethods[position] + } + + override fun getItemId(position: Int): Long { + // Since the list will be a static one, item ids do not have importance. + return position.toLong() + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = convertView ?: layoutInflater.inflate(R.layout.list_sharing_method, parent, false) + val sharingMethod = getItem(position) as SharingMethod + val image = view.findViewById(R.id.image) + val text1: TextView = view.findViewById(R.id.text1) + image.setImageResource(sharingMethod.iconRes) + text1.text = context.getString(sharingMethod.titleRes) + return view + } + } + + enum class SharingMethod( + @field:DrawableRes @param:DrawableRes val iconRes: Int, + @field:StringRes @param:StringRes val titleRes: Int, + ) { + WebShare(R.drawable.ic_web_white_24dp, R.string.butn_webShare), + LocalShare(R.drawable.ic_compare_arrows_white_24dp, R.string.text_devicesWithAppInstalled); + } + + interface PickListener { + fun onShareMethod(sharingMethod: SharingMethod) + } + + companion object { + fun createLocalShareOrganizingTask( + method: SharingMethod, + list: List, + ): OrganizeLocalSharingTask { + return when (method) { + SharingMethod.WebShare -> OrganizeLocalSharingTask(list, addNewDevice = false, webShare = true) + SharingMethod.LocalShare -> OrganizeLocalSharingTask(list, addNewDevice = true, webShare = false) + } + } + } + + init { + setTitle(R.string.text_chooseSharingMethod) + setAdapter(SharingMethodListAdapter()) { dialog: DialogInterface?, which: Int -> + listener.onShareMethod(sharingMethods[which]) + } + setNegativeButton(R.string.butn_cancel, null) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/Dialogs.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/Dialogs.kt new file mode 100644 index 000000000..d97f8609a --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/Dialogs.kt @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialog + +import android.app.Activity +import android.content.DialogInterface +import android.view.LayoutInflater +import android.view.View +import android.widget.CheckBox +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.database.model.Transfer +import org.monora.uprotocol.core.transfer.TransferItem +import java.util.* + +/** + * created by: veli + * date: 7/3/19 7:53 PM + */ +object Dialogs { + fun showGenericCheckBoxDialog( + activity: Activity, + @StringRes title: Int, + content: String?, + @StringRes positiveButton: Int, + @StringRes checkBox: Int, + positiveListener: ClickListener, + vararg textArgs: String?, + ) { + val view = LayoutInflater.from(activity).inflate( + R.layout.abstract_layout_dialog_text_option, + null + ) + val text1: TextView = view.findViewById(R.id.text1) + val checkBox1: CheckBox = view.findViewById(R.id.checkbox1) + + text1.text = content + if (checkBox == 0) checkBox1.visibility = View.GONE else checkBox1.setText(checkBox) + AlertDialog.Builder(activity) + .setTitle(title) + .setView(view) + .setNegativeButton(R.string.butn_cancel, null) + .setPositiveButton(positiveButton) { dialog: DialogInterface, which: Int -> + positiveListener.onClick(dialog, which, checkBox1) + } + .show() + } + + fun showRemoveDialog(activity: Activity, transfer: Transfer) { + showGenericCheckBoxDialog( + activity, + R.string.ques_removeAll, + activity.getString(R.string.text_removeTransferGroupSummary), + R.string.butn_remove, + R.string.text_alsoDeleteReceivedFiles, + object : ClickListener { + override fun onClick(dialog: DialogInterface, which: Int, checkBox: CheckBox) { + // FIXME: 2/25/21 Transfer removal + /* + transfer.deleteFilesOnRemoval = checkBox.isChecked == true + AppUtils.getKuick(activity).removeAsynchronous(activity, transfer, null) + + */ + } + } + ) + } + + fun showRemoveDialog(activity: Activity, item: TransferItem) { + val checkBox = if (TransferItem.Type.Incoming == item.itemType) R.string.text_alsoDeleteReceivedFiles else 0 + showGenericCheckBoxDialog( + activity, + R.string.ques_removeTransfer, + activity.getString(R.string.text_removeTransferSummary, item.itemName), + R.string.butn_remove, + checkBox, + object : ClickListener { + override fun onClick(dialog: DialogInterface, which: Int, checkBox: CheckBox) { + // FIXME: 2/25/21 TransferItem removal + /* + item.setDeleteOnRemoval(checkBox.isChecked) + AppUtils.getKuick(activity).removeAsynchronous(activity, item, null) + + */ + } + } + ) + } + + fun showRemoveTransferObjectListDialog( + activity: Activity, + objects: List, + ) { + val copiedObjects: List = ArrayList(objects) + showGenericCheckBoxDialog( + activity, + R.string.ques_removeTransfer, + activity.resources.getQuantityString(R.plurals.text_removeQueueSummary, objects.size, objects.size), + R.string.butn_remove, R.string.text_alsoDeleteReceivedFiles, + object : ClickListener { + override fun onClick(dialog: DialogInterface, which: Int, checkBox: CheckBox) { + // FIXME: 2/25/21 Transfer item list removal + /** + val isChecked = checkBox.isChecked + for (item in copiedObjects) item.setDeleteOnRemoval(isChecked) + AppUtils.getKuick(activity).removeAsynchronous(activity, copiedObjects, null) + */ + } + } + ) + } + + fun showRemoveTransferGroupListDialog( + activity: Activity, + groups: List, + ) { + val copiedTransfers: List = ArrayList(groups) + showGenericCheckBoxDialog( + activity, + R.string.ques_removeAll, + activity.getString(R.string.text_removeSelected), + R.string.butn_remove, + R.string.text_alsoDeleteReceivedFiles, + object : ClickListener { + override fun onClick(dialog: DialogInterface, which: Int, checkBox: CheckBox) { + val isChecked = checkBox.isChecked + // FIXME: 2/25/21 Transfer list removal + /* + for (transfer in copiedTransfers) transfer.deleteFilesOnRemoval = isChecked + AppUtils.getKuick(activity).removeAsynchronous(activity, copiedTransfers, null) + + */ + } + } + ) + } + + interface ClickListener { + fun onClick(dialog: DialogInterface, which: Int, checkBox: CheckBox) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/FileDeletionDialog.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/FileDeletionDialog.kt new file mode 100755 index 000000000..6d045d246 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/FileDeletionDialog.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialogimport + +import android.app.Activity +import android.content.DialogInterface +import androidx.appcompat.app.AlertDialog +import org.monora.uprotocol.client.android.App +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.adapter.FileListAdapter +import org.monora.uprotocol.client.android.task.FileDeletionTask + +/** + * Created by: veli + * Date: 5/21/17 2:21 AM + */ +class FileDeletionDialog(activity: Activity, items: List) : AlertDialog.Builder(activity) { + init { + setTitle(R.string.text_deleteConfirm) + setMessage(context.resources.getQuantityString(R.plurals.ques_deleteFile, items.size, items.size)) + setNegativeButton(R.string.butn_cancel, null) + setPositiveButton(R.string.butn_delete) { dialog: DialogInterface?, p2: Int -> + // TODO: 2/26/21 Give this backend, please? + //App.from(activity).run(FileDeletionTask(items)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/FileRenameDialog.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/FileRenameDialog.kt new file mode 100755 index 000000000..95a92939c --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/FileRenameDialog.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialog + +import android.app.Activity +import androidx.appcompat.app.AlertDialog +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.adapter.FileListAdapter.FileHolder +import org.monora.uprotocol.client.android.task.RenameMultipleFilesTask +import com.genonbeta.android.framework.io.DocumentFile +import java.util.* + +/** + * created by: Veli + * date: 26.02.2018 08:53 + */ +class FileRenameDialog(val activity: Activity, val list: List) : AbstractSingleTextInputDialog(activity) { + override fun show(): AlertDialog { + val itemList: List = ArrayList(list) + val multiple = itemList.size > 1 + + setTitle(if (multiple) R.string.text_renameMultipleItems else R.string.text_rename) + editText.setText(if (multiple) "%d" else itemList[0].name()) + + setOnProceedClickListener(R.string.butn_rename, object : OnProceedClickListener { + override fun onProceedClick(dialog: AlertDialog): Boolean { + val renameTo = editText.text.toString() + if (multiple) try { + String.format(renameTo, itemList.size) + // TODO: 2/26/21 Give this backend, please + //App.from(activity).run(RenameMultipleFilesTask(itemList, renameTo)) + } catch (e: Exception) { + editText.error = activity.getString(R.string.text_errorIncludePrintfPlaceholder) + return false + } else if (itemList.size == 1) { + val fileHolder: FileHolder = itemList[0] + val scannerList: MutableList = ArrayList() + + RenameMultipleFilesTask.renameFile( + fileHolder, + renameTo, + scannerList + ) + RenameMultipleFilesTask.notifyFileChanges(context, scannerList) + } + + return true + } + }) + + return super.show() + } + + companion object { + private val TAG = FileRenameDialog::class.simpleName + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/FolderCreationDialog.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/FolderCreationDialog.kt new file mode 100755 index 000000000..dfe825db2 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/FolderCreationDialog.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialog + +import android.content.Context +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import org.monora.uprotocol.client.android.R +import com.genonbeta.android.framework.io.DocumentFile + +/** + * Created by: veli + * Date: 5/30/17 12:18 PM + */ +class FolderCreationDialog( + context: Context, currentFolder: DocumentFile, + createdListener: OnFolderCreatedListener, +) : AbstractSingleTextInputDialog(context) { + interface OnFolderCreatedListener { + fun onFolderCreated(directoryFile: DocumentFile?) + } + + init { + setTitle(R.string.text_createFolder) + setOnProceedClickListener(R.string.butn_create, object : OnProceedClickListener { + override fun onProceedClick(dialog: AlertDialog): Boolean { + val fileName = editText.text.toString() + if (fileName.isEmpty()) return false + val createdFile = currentFolder.createDirectory(fileName) + if (createdFile == null) { + Toast.makeText(getContext(), R.string.mesg_folderCreateError, Toast.LENGTH_SHORT).show() + return false + } + createdListener.onFolderCreated(createdFile) + dialog.dismiss() + return true + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/PermissionRequests.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/PermissionRequests.kt new file mode 100755 index 000000000..06be39b1a --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/PermissionRequests.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialog + +import android.app.Activity +import android.content.Context +import android.content.DialogInterface +import android.content.pm.PackageManager +import androidx.appcompat.app.AlertDialog +import androidx.core.app.ActivityCompat +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.activity.HomeActivity +import org.monora.uprotocol.client.android.util.Activities + +/** + * created by: Veli + * date: 18.11.2017 20:16 + */ +class PermissionRequests( + activity: Activity, + permissionQueue: PermissionRequest, + killActivityOtherwise: Boolean +) : AlertDialog.Builder(activity) { + data class PermissionRequest( + val permission: String, + val title: String, + val message: String, + val required: Boolean = true + ) { + constructor(context: Context, permission: String, titleRes: Int, messageRes: Int) : this( + permission, + context.getString(titleRes), + context.getString(messageRes) + ) + } + + companion object { + fun requestIfNecessary( + activity: Activity, + permissionQueue: PermissionRequest, + killActivityOtherwise: Boolean + ): AlertDialog? = if (ActivityCompat.checkSelfPermission(activity, permissionQueue.permission) + == PackageManager.PERMISSION_GRANTED + ) null + else + PermissionRequests(activity, permissionQueue, killActivityOtherwise).show() + } + + init { + setCancelable(false) + setTitle(permissionQueue.title) + setMessage(permissionQueue.message) + if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permissionQueue.permission)) { + setNeutralButton(R.string.butn_settings) { _: DialogInterface?, _: Int -> + Activities.startApplicationDetails(activity) + } + } + setPositiveButton(R.string.butn_ask) { _: DialogInterface?, _: Int -> + ActivityCompat.requestPermissions( + activity, + arrayOf(permissionQueue.permission), + HomeActivity.REQUEST_PERMISSION_ALL + ) + } + if (killActivityOtherwise) { + setNegativeButton(R.string.butn_reject) { _: DialogInterface?, _: Int -> activity.finish() } + } else { + setNegativeButton(R.string.butn_close, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/RemoveDeviceDialog.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/RemoveDeviceDialog.kt new file mode 100644 index 000000000..0da138de8 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/RemoveDeviceDialog.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialogimport + +import android.app.Activity +import android.content.DialogInterface +import androidx.appcompat.app.AlertDialog +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.database.model.UClient + +class RemoveDeviceDialog(activity: Activity, client: UClient) : AlertDialog.Builder(activity) { + init { + setTitle(R.string.ques_removeDevice) + setMessage(R.string.text_removeDeviceNotice) + setNegativeButton(R.string.butn_cancel, null) + setPositiveButton(R.string.butn_proceed) { dialog: DialogInterface?, which: Int -> + // TODO: 2/25/21 remove the device + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/SelectionEditorDialog.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/SelectionEditorDialog.kt new file mode 100755 index 000000000..b52cffdd2 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/SelectionEditorDialog.kt @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialog + +import android.app.Activity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.ListView +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.model.MappedSelectionModel +import org.monora.uprotocol.client.android.model.MappedSelectionModel.Companion.compileFrom +import com.genonbeta.android.framework.util.actionperformer.IEngineConnection +import com.genonbeta.android.framework.util.actionperformer.PerformerEngineProvider +import com.genonbeta.android.framework.util.actionperformer.SelectionModel + +/** + * created by: Veli + * date: 5.01.2018 10:38 + */ +class SelectionEditorDialog(activity: Activity, provider: PerformerEngineProvider) : AlertDialog.Builder(activity) { + private val inflater: LayoutInflater = LayoutInflater.from(activity) + + private val adapter = SelfAdapter() + + private val engine = provider.getPerformerEngine() + + private val mappedList: List> = compileFrom(engine) + + private val mappedConnectionList: MutableList> = ArrayList() + + fun checkReversed(textView: TextView, removeSign: View, selectionModel: SelectionModel) { + selectionModel.select(!selectionModel.selected()) + mark(textView, removeSign, selectionModel) + } + + fun mark(textView: TextView, removeSign: View, selectionModel: SelectionModel) { + val selected: Boolean = selectionModel.selected() + textView.setEnabled(selected) + removeSign.visibility = if (selected) View.GONE else View.VISIBLE + } + + fun massCheck(check: Boolean) { + synchronized(mappedList) { for (mappedConnection in mappedConnectionList) massCheck(check, mappedConnection) } + adapter.notifyDataSetChanged() + } + + private fun massCheck(check: Boolean, mappedConnection: MappedConnection) { + mappedConnection.connection.setSelected(mappedConnection.list, IntArray(mappedConnection.list.size), check) + } + + override fun show(): AlertDialog { + val dialog = super.show() + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { v: View? -> massCheck(true) } + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { v: View? -> massCheck(false) } + return dialog + } + + private inner class SelfAdapter : BaseAdapter() { + override fun getCount(): Int { + return mappedList.size + } + + override fun getItem(position: Int): Any { + return mappedList[position] + } + + override fun getItemId(position: Int): Long { + return 0 + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = convertView ?: inflater.inflate(R.layout.list_selection, parent, false) + val selectionModel = getItem(position) as MappedSelectionModel<*> + val textView1: TextView = view.findViewById(R.id.text) + val removalSignView = view.findViewById(R.id.removalSign) + textView1.text = selectionModel.name() + mark(textView1, removalSignView, selectionModel) + view.isClickable = true + view.setOnClickListener { v: View? -> checkReversed(textView1, removalSignView, selectionModel) } + return view + } + } + + private fun addToMappedObjectList(connection: IEngineConnection) { + mappedConnectionList.add(MappedConnection(connection, connection.getSelectionList())) + } + + private class MappedConnection(var connection: IEngineConnection, list: List?) { + var list: MutableList = if (list == null) ArrayList() else ArrayList(list) + } + + companion object { + private val TAG = SelectionEditorDialog::class.simpleName + } + + init { + if (engine != null) { + for (baseEngineConnection in engine.getConnectionList()) { + if (baseEngineConnection is IEngineConnection<*>) { + addToMappedObjectList(baseEngineConnection) + } + } + } + val view = inflater.inflate(R.layout.layout_selection_editor, null, false) + val listView = view.findViewById(R.id.listView) + listView.adapter = adapter + listView.dividerHeight = 0 + if (mappedList.isNotEmpty()) setView(view) else setMessage(R.string.text_listEmpty) + setTitle(R.string.text_previewAndEditList) + setNeutralButton(R.string.butn_check, null) + setNegativeButton(R.string.butn_uncheck, null) + setPositiveButton(R.string.butn_close, null) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/ShareAppDialog.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/ShareAppDialog.kt new file mode 100644 index 000000000..159633f30 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/ShareAppDialog.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialog + +import android.content.* +import android.content.pm.PackageInfo +import android.os.* +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.config.AppConfig +import org.monora.uprotocol.client.android.util.Files +import com.genonbeta.android.framework.io.DocumentFile +import com.genonbeta.android.framework.util.Files.getSecureUri +import com.genonbeta.android.framework.util.Stoppable +import com.genonbeta.android.framework.util.StoppableImpl +import java.io.File + +class ShareAppDialog(context: Context) : AlertDialog.Builder(context) { + private fun shareAsApk(context: Context) { + try { + val interrupter: Stoppable = StoppableImpl() + val pm = context.packageManager + val packageInfo: PackageInfo = pm.getPackageInfo(context.applicationInfo.packageName, 0) + val fileName: String = packageInfo.applicationInfo.loadLabel(pm).toString() + "_" + packageInfo.versionName + val storageDirectory = Files.getApplicationDirectory(context.applicationContext) + val codeFile = DocumentFile.fromFile(File(context.applicationInfo.sourceDir)) + val cloneFile = storageDirectory.createFile(codeFile.getType(), fileName)!! + if (cloneFile.exists()) cloneFile.delete() + Files.copy(context, codeFile, cloneFile, interrupter) + try { + val sendIntent = Intent(Intent.ACTION_SEND) + .putExtra(Intent.EXTRA_STREAM, getSecureUri(context, cloneFile)) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .setType(cloneFile.getType()) + context.startActivity( + Intent.createChooser( + sendIntent, context.getString( + R.string.text_fileShareAppChoose + ) + ) + ) + } catch (e: IllegalArgumentException) { + Toast.makeText(context, R.string.mesg_providerNotAllowedError, Toast.LENGTH_LONG).show() + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun shareAsLink(context: Context) { + Handler(Looper.getMainLooper()).post { + try { + val textToShare = context.getString(R.string.text_linkTrebleshot, AppConfig.URI_GOOGLE_PLAY) + val sendIntent = Intent(Intent.ACTION_SEND) + .putExtra(Intent.EXTRA_TEXT, textToShare) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .setType("text/plain") + context.startActivity( + Intent.createChooser( + sendIntent, context.getString( + R.string.text_fileShareAppChoose + ) + ) + ) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + init { + setMessage(R.string.ques_shareAsApkOrLink) + setNegativeButton(R.string.butn_cancel, null) + setNeutralButton(R.string.butn_asApk) { dialogInterface: DialogInterface?, i: Int -> shareAsApk(context) } + setPositiveButton(R.string.butn_asLink) { dialogInterface: DialogInterface?, i: Int -> shareAsLink(context) } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/dialog/WebShareDetailsDialog.kt b/app/src/main/java/org/monora/uprotocol/client/android/dialog/WebShareDetailsDialog.kt new file mode 100644 index 000000000..6eb136d74 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/dialog/WebShareDetailsDialog.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.dialog + +import android.content.Context +import android.graphics.Bitmap +import android.view.LayoutInflater +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import org.monora.uprotocol.client.android.GlideApp +import org.monora.uprotocol.client.android.R +import com.google.zxing.BarcodeFormat +import com.google.zxing.MultiFormatWriter +import com.google.zxing.WriterException +import com.google.zxing.common.BitMatrix +import org.monora.android.codescanner.BarcodeEncoder + +/** + * created by: veli + * date: 4/8/19 9:16 AM + */ +class WebShareDetailsDialog(context: Context, address: String?) : AlertDialog.Builder(context) { + init { + val view = LayoutInflater.from(context).inflate(R.layout.layout_web_share_details, null) + val qrImage = view.findViewById(R.id.image) + val addressText = view.findViewById(R.id.text) + + //setTitle(R.string.text_webShare); + setView(view) + setPositiveButton(R.string.butn_close, null) + addressText.text = address + + try { + val formatWriter = MultiFormatWriter() + val bitMatrix: BitMatrix = formatWriter.encode(address, BarcodeFormat.QR_CODE, 400, 400) + val encoder = BarcodeEncoder() + val bitmap: Bitmap = encoder.createBitmap(bitMatrix) + GlideApp.with(getContext()) + .load(bitmap) + .into(qrImage) + } catch (e: WriterException) { + e.printStackTrace() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/drawable/TextDrawable.kt b/app/src/main/java/org/monora/uprotocol/client/android/drawable/TextDrawable.kt new file mode 100644 index 000000000..2b57e38a3 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/drawable/TextDrawable.kt @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.drawable + +import android.graphics.* +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.OvalShape +import android.graphics.drawable.shapes.RectShape +import android.graphics.drawable.shapes.RoundRectShape +import org.monora.uprotocol.client.android.util.TextManipulators +import java.util.* +import kotlin.math.min + +class TextDrawable private constructor( + builder: Builder, + private val text: String, + private val textPaint: Paint, + private val borderPaint: Paint, +) : ShapeDrawable(builder.shape) { + private val height = builder.height + + private val width = builder.width + + private val radius = builder.radius + + private val shapeBorderThickness = builder.shapeBorderThickness + + private val textSize = builder.textSize + + override fun draw(canvas: Canvas) { + super.draw(canvas) + val r = bounds + + // draw border + if (shapeBorderThickness > 0) { + drawBorder(canvas) + } + + val count = canvas.save() + canvas.translate(r.left.toFloat(), r.top.toFloat()) + + // draw text + val width = if (width < 0) r.width() else width + val height = if (height < 0) r.height() else height + val fontSize = if (textSize < 0) min(width, height) / 2 else textSize + textPaint.textSize = fontSize.toFloat() + + canvas.drawText( + text, (width / 2).toFloat(), height / 2 - (textPaint.descent() + textPaint.ascent()) / 2, textPaint + ) + canvas.restoreToCount(count) + } + + private fun drawBorder(canvas: Canvas) { + val rect = RectF(bounds).also { + it.inset((shapeBorderThickness / 2).toFloat(), (shapeBorderThickness / 2).toFloat()) + } + + when (shape) { + is OvalShape -> canvas.drawOval(rect, borderPaint) + is RoundRectShape -> canvas.drawRoundRect(rect, radius, radius, borderPaint) + else -> canvas.drawRect(rect, borderPaint) + } + } + + override fun getIntrinsicWidth(): Int { + return width + } + + override fun getIntrinsicHeight(): Int { + return height + } + + override fun getOpacity(): Int { + return PixelFormat.TRANSLUCENT + } + + override fun setAlpha(alpha: Int) { + textPaint.alpha = alpha + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + textPaint.colorFilter = colorFilter + } + + interface Builder { + var radius: Float + + var shape: RectShape + + var shapeBorderThickness: Int + + var shapeColor: Int + + var textAllCaps: Boolean + + var textBold: Boolean + + var textColor: Int + + var textTypeface: Typeface + + var textFirstLetters: Boolean + + var textMaxLength: Int + + var textSingleCharacter: Boolean + + var textSize: Int + + var height: Int + + var width: Int + + fun build(text: String): TextDrawable + + fun buildRect(text: String): TextDrawable + + fun buildRound(text: String): TextDrawable + + fun buildRoundRect(text: String, radius: Int): TextDrawable + + fun rect() + + fun round() + + fun roundRect(radius: Int) + } + + private class BuilderImpl : Builder { + override var radius: Float = 0f + + override var shape: RectShape = RectShape() + + override var shapeBorderThickness: Int = 0 + + override var shapeColor: Int = Color.GRAY + + override var textAllCaps: Boolean = false + + override var textBold: Boolean = false + + override var textColor: Int = Color.WHITE + + override var textFirstLetters: Boolean = false + + override var textMaxLength: Int = -1 + + override var textSingleCharacter: Boolean = false + + override var textSize: Int = -1 + + override var textTypeface: Typeface = Typeface.create("sans-serif", Typeface.NORMAL) + + override var height: Int = -1 + + override var width: Int = -1 + + override fun rect() { + shape = RectShape() + } + + override fun round() { + shape = OvalShape() + } + + override fun roundRect(radius: Int) { + val radiusF = radius.toFloat() + val radii = floatArrayOf(radiusF, radiusF, radiusF, radiusF, radiusF, radiusF, radiusF, radiusF) + shape = RoundRectShape(radii, null, null) + this.radius = radiusF + } + + override fun buildRect(text: String): TextDrawable { + rect() + return build(text) + } + + override fun buildRoundRect(text: String, radius: Int): TextDrawable { + roundRect(radius) + return build(text) + } + + override fun buildRound(text: String): TextDrawable { + round() + return build(text) + } + + override fun build(text: String): TextDrawable { + var modifiedText = text + + if (textAllCaps) { + modifiedText = modifiedText.toUpperCase(Locale.getDefault()) + } + + if (textMaxLength > 0) { + modifiedText = when { + textFirstLetters -> TextManipulators.getLetters(modifiedText, textMaxLength) + modifiedText.length > textMaxLength -> modifiedText.substring(0, textMaxLength) + else -> modifiedText + } + } + + val textPaint = Paint().apply { + color = textColor + isAntiAlias = true + isFakeBoldText = textBold + style = Paint.Style.FILL + typeface = textTypeface + textAlign = Paint.Align.CENTER + strokeWidth = shapeBorderThickness.toFloat() + } + + // border paint settings + val borderPaint = Paint().apply { + color = getDarkerShade(shapeColor) + style = Paint.Style.STROKE + strokeWidth = shapeBorderThickness.toFloat() + } + + return TextDrawable(this, modifiedText, textPaint, borderPaint).also { + it.paint.color = shapeColor + } + } + } + + companion object { + private const val SHADE_FACTOR = 0.9f + + fun createBuilder(): Builder { + return BuilderImpl() + } + + private fun getDarkerShade(color: Int): Int { + return Color.rgb( + (SHADE_FACTOR * Color.red(color)).toInt(), + (SHADE_FACTOR * Color.green(color)).toInt(), + (SHADE_FACTOR * Color.blue(color)).toInt() + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/exception/ConnectionNotFoundException.kt b/app/src/main/java/org/monora/uprotocol/client/android/exception/ConnectionNotFoundException.kt new file mode 100755 index 000000000..65f805826 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/exception/ConnectionNotFoundException.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.exception + +import com.genonbeta.android.database.exception.ReconstructionFailedException + +/** + * created by: Veli + * date: 6.01.2018 22:25 + */ +class ConnectionNotFoundException(val deviceId: String) : ReconstructionFailedException( + "No known connection route for the device $deviceId" +) \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/exception/DeviceNotFoundException.kt b/app/src/main/java/org/monora/uprotocol/client/android/exception/DeviceNotFoundException.kt new file mode 100755 index 000000000..e11fbc509 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/exception/DeviceNotFoundException.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.exception + +import com.genonbeta.android.database.exception.ReconstructionFailedException + +/** + * created by: Veli + * date: 6.01.2018 22:26 + */ +class DeviceNotFoundException(var uid: String) : ReconstructionFailedException( + "$uid doesn't point to a device" +) \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/exception/TargetNotFoundException.kt b/app/src/main/java/org/monora/uprotocol/client/android/exception/TargetNotFoundException.kt new file mode 100755 index 000000000..ef58e78d5 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/exception/TargetNotFoundException.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.exception + +import com.genonbeta.android.database.exception.ReconstructionFailedException + +/** + * created by: veli + * date: 06.04.2018 11:20 + */ +class TargetNotFoundException(val clientUid: String, val groupId: Long) : ReconstructionFailedException( + "Member with deviceId=${clientUid} and transferId=${groupId} is not valid" +) \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/exception/TransferNotFoundException.kt b/app/src/main/java/org/monora/uprotocol/client/android/exception/TransferNotFoundException.kt new file mode 100755 index 000000000..433a1476d --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/exception/TransferNotFoundException.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.exception + +import com.genonbeta.android.database.exception.ReconstructionFailedException + +/** + * created by: Veli + * date: 6.01.2018 22:26 + */ +class TransferNotFoundException(val transferId: Long) : ReconstructionFailedException( + "Transfer for $transferId group not found" +) \ No newline at end of file diff --git a/app/src/main/java/org/monora/uprotocol/client/android/fragment/ActiveConnectionListFragment.kt b/app/src/main/java/org/monora/uprotocol/client/android/fragment/ActiveConnectionListFragment.kt new file mode 100644 index 000000000..2b5057d05 --- /dev/null +++ b/app/src/main/java/org/monora/uprotocol/client/android/fragment/ActiveConnectionListFragment.kt @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2019 Veli Tasalı + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.monora.uprotocol.client.android.fragment + +import android.bluetooth.BluetoothA2dp +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager.CONNECTIVITY_ACTION +import android.net.Uri +import android.net.wifi.WifiManager.WIFI_STATE_CHANGED_ACTION +import android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.cardview.widget.CardView +import androidx.preference.PreferenceManager +import androidx.transition.TransitionManager +import com.genonbeta.android.framework.widget.RecyclerViewAdapter.ViewHolder +import org.monora.uprotocol.client.android.R +import org.monora.uprotocol.client.android.adapter.ActiveConnectionListAdapter +import org.monora.uprotocol.client.android.app.ListingFragment +import org.monora.uprotocol.client.android.dialog.WebShareDetailsDialog +import org.monora.uprotocol.client.android.fragment.NetworkManagerFragment.Companion.WIFI_AP_STATE_CHANGED +import org.monora.uprotocol.client.android.model.NetworkInterfaceModel +import org.monora.uprotocol.client.android.util.Networks +import org.monora.uprotocol.client.android.util.TextManipulators + +/** + * created by: veli + * date: 4/7/19 10:59 PM + */ +class ActiveConnectionListFragment : + ListingFragment() { + private val filter = IntentFilter() + + private val preferences by lazy { + PreferenceManager.getDefaultSharedPreferences(requireContext()) + } + + private val receiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (WIFI_AP_STATE_CHANGED == intent.action || CONNECTIVITY_ACTION == intent.action + || WIFI_STATE_CHANGED_ACTION == intent.action || WIFI_P2P_CONNECTION_CHANGED_ACTION == intent.action + || BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED == intent.action + ) refreshList() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + layoutResId = R.layout.layout_active_connection + filteringSupported = true + filter.addAction(WIFI_AP_STATE_CHANGED) + filter.addAction(CONNECTIVITY_ACTION) + filter.addAction(WIFI_STATE_CHANGED_ACTION) + filter.addAction(WIFI_P2P_CONNECTION_CHANGED_ACTION) + filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + adapter = ActiveConnectionListAdapter(this) + emptyListImageView.setImageResource(R.drawable.ic_share_white_24dp) + emptyListTextView.text = getString(R.string.text_listEmptyConnection) + val webShareInfo = view.findViewById(R.id.card_web_share_info) + val webShareInfoHideButton = view.findViewById