diff --git a/Green_Arrow_Up_Darker.svg b/Green_Arrow_Up_Darker.svg new file mode 100644 index 0000000..2d5ea0d --- /dev/null +++ b/Green_Arrow_Up_Darker.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE index def3ae0..64f6a8c 100644 --- a/LICENSE +++ b/LICENSE @@ -19,3 +19,351 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Firebase Terms of Service AND Conditions + +FIREBASE ANALYTICS + +These Google Analytics for Firebase Terms of Service, the Google Analytics for Firebase Policies and the Google API Terms of Service (this "Agreement") are entered into by Google and the entity or individual using the Service ("You"). This Agreement governs Your use of Google Analytics for Firebase (the "Service"). BY CLICKING THE "I ACCEPT" BUTTON, COMPLETING THE REGISTRATION PROCESS, OR USING THE SERVICE, YOU ACKNOWLEDGE THAT YOU HAVE REVIEWED AND ACCEPT THIS AGREEMENT AND ARE AUTHORIZED TO ACT ON BEHALF OF, AND BIND TO THIS AGREEMENT, THE OWNER OF THIS ACCOUNT. In consideration of the foregoing, the parties agree as follows: +1. Definitions. + +"Account" refers to the account for the Service. + +"Affiliate(s)" means in relation to each of the parties: (a) any parent company of that party; and (b) any corporate body of which that party directly or indirectly has control or which is directly or indirectly controlled by the same person or group of persons as that party. + +"Confidential Information" includes any proprietary data and any other information disclosed by one party to the other in writing and marked "confidential" or disclosed orally and, within five business days, reduced to writing and marked "confidential". However, Confidential Information will not include any information that is or becomes known to the general public, which is already in the receiving party's possession prior to disclosure by a party or which is independently developed by the receiving party without the use of Confidential Information. + +"Customer Data" means the data You collect, process or store using the Service concerning the characteristics and activities of Users. + +"Documentation" means any accompanying documentation made available to You by Google for use with the Processing Software, including any documentation available online. + +"Google" means either (i) Google Ireland Limited, with offices at Gordon House, Barrow Street, Dublin 4, Ireland, if Your principal place of business (for entities) or place of residence (for individuals) is in any country within Europe, the Middle East, or Africa ("EMEA"), (ii) Google Asia Pacific Pte. Ltd., with offices at 70 Pasir Panjang Road, #03-01, Mapletree Business City II, Singapore 117371, if Your principal place of business (for entities) or place of residence (for individuals) is in any country within the Asia Pacific region ("APAC"), or (iii) Google LLC, with offices at 1600 Amphitheatre Parkway, Mountain View, California 94043, if Your principal place of business (for entities) or place of residence (for individuals) is in any country in the world other than those in EMEA and APAC. + +"SDK" means the Firebase Software Development Kit, which is used or incorporated in an App for the purpose of collecting Customer Data, together with any fixes, updates and upgrades provided to You. + +"Processing Software" means the Google server-side software and any upgrades, which analyzes the Customer Data and generates the Reports. + +"App" means any app or other resource that sends data to the Service. Each App must be under Your control. + +"Privacy Policy" means the privacy policy on an App. + +"Report" means the resulting analysis made available to You. + +"Servers" means the servers controlled by Google or its Affiliates on which the Processing Software and Customer Data are stored. + +"Software" means the SDK and the Processing Software. + +"Third Party" means any third party (i) to which You provide access to Your Account or (i) for which You use the Service to collect information on the third party's behalf. + +"Users" means users of Your Apps. + +The words "include" and "including" mean "including but not limited to." +2. Fees and Service. + +Google and its Affiliates may change its fees and payment policies for the Service from time to time. The changes to the fees or payment policies are effective upon Your acceptance of those changes which will be posted at firebase.google.com/terms/analytics. Unless otherwise stated, all fees are quoted in U.S. Dollars. Any outstanding balance becomes immediately due and payable upon termination of this Agreement and any collection expenses (including attorneys' fees) incurred by Google and its Affiliates will be included in the amount owed, and may be charged to the credit card or other billing mechanism associated with Your AdWords account. +3. Member Account, Password, and Security. + +To register for the Service, You must be acting in the course of business, complete the registration process by providing Google with current, complete and accurate information as prompted by the registration form, including Your e-mail address (username) and password. You will protect Your passwords and take full responsibility for Your own, and third party, use of Your accounts. You are solely responsible for any and all activities that occur under Your Account. You will notify Google immediately upon learning of any unauthorized use of Your Account or any other breach of security. Google or its Affiliates’ support staff may, from time to time, log in to the Service under Your Account in order to maintain or improve service, including to provide You assistance with technical or billing issues. By creating Your Account you agree to receive electronic statements from Google and its Affiliates. +4. Nonexclusive License. + +Subject to the terms and conditions of this Agreement, (a) Google grants You a limited, revocable, non-exclusive, non-sublicensable license to install, copy and use the SDK solely as necessary for You to use the Service on Your Apps or Third Parties Apps; and (b) You may remotely access, view and download Your Reports. You will not (and You will not allow any third party to) use data labeled as belonging to a third party in the Service for purposes other than generating, viewing, and downloading Reports. You will comply with all applicable laws and regulations and Your agreements with third parties in Your use of and access to the Documentation, Software, Service and Reports. +5. Confidentiality. + +Neither party will use or disclose the other party's Confidential Information without the other's prior written consent except for the purpose of performing its obligations under this Agreement or if required by law, regulation or court order; in which case, the party being compelled to disclose Confidential Information will give the other party as much notice as is reasonably practicable prior to disclosing the Confidential Information if permitted by law. +6. Information Rights and Publicity. + +Google and its Affiliates may retain and use, subject to the terms of its privacy policy (located at www.google.com/privacy.html), information collected in Your use of the Service. Google will not share Your Customer Data or any Third Party's Customer Data with any third parties unless Google (i) has Your consent for any Customer Data or any Third Party's consent for the Third Party's Customer Data; (ii) concludes that it is required by law or has a good faith belief that access, preservation or disclosure of Customer Data is reasonably necessary to protect the rights, property or safety of Google, its users or the public; or (iii) provides Customer Data in certain limited circumstances to third parties to carry out tasks on Google's behalf (e.g., billing or data storage) with strict restrictions that prevent the data from being used or shared except as directed by Google. When this is done, it is subject to agreements that oblige those parties to process Customer Data only on Google's instructions and in compliance with this Agreement and appropriate confidentiality and security measures. +7. Privacy. + +You will not, and will not assist or permit any third party to, pass information to Google that Google could use or recognize as personally identifiable information. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws, policies, and regulations relating to the collection, usage and sharing of information from Users. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies, identifiers for mobile devices (e.g., Android Advertising Identifier or Advertising Identifier for iOS) or similar technology that are used to collect data. You must disclose the use of the Service, and how it collects and processes data. This can be done by displaying a prominent link to the site "How Google uses data when you use our partners' sites or apps", (located at www.google.com/policies/privacy/partners/, or any other URL Google may provide from time to time). You will use commercially reasonable efforts to ensure that a User is provided with clear and comprehensive information about, and consents to, the storing and accessing of cookies or other information on the User’s device where such activity occurs in connection with the Service and where providing such information and obtaining such consent is required by law. + +You must not circumvent any privacy features that are part of the Service. + +Your access to and use of any other DoubleClick or Google service is subject to the applicable terms between You and Google regarding that service. +8. Indemnification. + +To the maximum extent permitted by applicable law, You will indemnify, hold harmless and defend Google and its Affiliates, at Your expense, from any and all third-party claims, actions, proceedings, and suits brought against Google or any of its officers, directors, employees, agents or Affiliates, and all related liabilities, damages, settlements, penalties, fines, costs or expenses (including, reasonable attorneys' fees and other litigation expenses) incurred by Google or any of its officers, directors, employees, agents or Affiliates, arising out of or relating to (i) Your breach of any term or condition of this Agreement, (ii) Your use of the Service, (iii) Your violations of applicable laws, rules or regulations in connection with the Service, (iv) any representations and warranties made by You concerning any aspect of the Service, the Software or Reports to any Third Party; (v) any claims made by or on behalf of any Third Party pertaining directly or indirectly to Your use of the Service, the Software or Reports; (vi) violations of Your obligations of privacy to any Third Party; and (vii) any claims with respect to acts or omissions of any Third Party in connection with the Service, the Software or Reports. Google will provide You with written notice of any claim, suit or action from which You must indemnify Google and its Affiliates. You will cooperate as fully as reasonably required in the defense of any claim. Google and its Affiliates reserve the right, at their own expense, to assume the exclusive defense and control of any matter subject to indemnification by You. +9. Third Parties. + +If You use the Service on behalf of a Third Party or a Third Party otherwise uses the Service through Your Account, whether or not You are authorized by Google to do so, then You represent and warrant that (a) You are authorized to act on behalf of, and bind to this Agreement, the Third Party to all obligations that You have under this Agreement, (b) Google and its Affiliates may share with the Third Party any Customer Data that is specific to the Third Party's Apps, and (c) You will not disclose Third Party's Customer Data to any other party without the Third Party's consent. +10. DISCLAIMER OF WARRANTIES. + +TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, EXCEPT AS EXPRESSLY PROVIDED FOR IN THIS AGREEMENT, GOOGLE MAKES NO OTHER WARRANTY OF ANY KIND, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR USE AND NON-INFRINGEMENT. THE SERVICE IS PROVIDED "AS IS". +11. LIMITATION OF LIABILITY. + +TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, GOOGLE WILL NOT BE LIABLE FOR YOUR LOST PROFITS (WHETHER DIRECT OR INDIRECT) OR INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE LOSSES OR DAMAGES (WHETHER OR NOT FORESEEABLE OR CONTEMPLATED BY THE PARTIES), EVEN IF GOOGLE OR ITS AFFILIATES HAVE BEEN ADVISED OF, KNEW OR SHOULD HAVE KNOWN THAT SUCH DAMAGES WERE POSSIBLE AND EVEN IF DIRECT DAMAGES DO NOT SATISFY A REMEDY. GOOGLE AND ITS AFFILIATES’ TOTAL CUMULATIVE LIABILITY TO YOU OR ANY OTHER PARTY FOR ANY LOSS OR DAMAGES RESULTING FROM CLAIMS, DEMANDS, OR ACTIONS ARISING OUT OF OR RELATING TO THIS AGREEMENT WILL NOT EXCEED $500 (USD). +12. Proprietary Rights Notice. + +The Service, which includes the Software and all intellectual property rights therein are, and will remain, the property of Google and its Affiliates. All rights in and to the Software not expressly granted to You in this Agreement are reserved and retained by Google, its Affiliates, and its licensors without restriction, including, Google's (and its Affiliates') right to sole ownership of the Software and Documentation. Without limiting the generality of the foregoing, You agree, to the maximum extent permitted by applicable law, not to (and not to allow any third party to): (a) sublicense, distribute, or use the Service or Software outside of the scope of the license granted in this Agreement; (b) copy, modify, adapt, translate, prepare derivative works from, reverse engineer, disassemble, or decompile the Software or Documentation or otherwise attempt to discover any source code or trade secrets related to the Service; (c) rent, lease, sell, assign or otherwise transfer rights in or to the Software, the Documentation or the Service; (d) use, post, transmit or introduce any device, software or routine which interferes or attempts to interfere with the operation of the Service or the Software; (e) use the trademarks, trade names, service marks, logos, domain names and other distinctive brand features or any copyright or other proprietary rights associated with the Service for any purpose without the express written consent of Google and its Affiliates ; (f) register, attempt to register, or assist anyone else to register any trademark, trade name, serve marks, logos, domain names and other distinctive brand features, copyright or other proprietary rights associated with Google or its Affiliates other than in the name of Google (or its Affiliates as the case may be); (g) remove, obscure, or alter any notice of copyright, trademark, or other proprietary right appearing in or on any item included with the Service or Software or (h) seek, in a proceeding filed during the term of this Agreement or for one year after such term, an injunction of any portion of the Service based on patent infringement. +13. U.S. Government Rights. + +If the use of the Service is being acquired by or on behalf of the U.S. Government or by a U.S. Government prime contractor or subcontractor (at any tier), in accordance with 48 C.F.R. 227.7202-4 (for Department of Defense (DOD) acquisitions) and 48 C.F.R. 2.101 and 12.212 (for non-DOD acquisitions), the Government's rights in the Software, including its rights to use, modify, reproduce, release, perform, display or disclose the Software or Documentation, will be subject in all respects to the commercial license rights and restrictions provided in this Agreement. +14. Term and Termination. + +Either party may terminate this Agreement at any time with notice. Upon any termination of this Agreement, Google will stop providing, and You will stop using the Service. In the event of any termination (a) You will not be entitled to any refunds of any usage fees or any other fees, and (b) any outstanding balance for Service rendered through the date of termination will be immediately due and payable in full and (c) all of Your historical Report data will no longer be available to You. +15. Modifications to Terms of Service and Other Policies. + +Google may modify these terms or any additional terms that apply to the Service to, for example, reflect changes to the law or changes to the Service. You should look at the terms regularly. Google will post notice of modifications to these terms at firebase.google.com/terms/analytics, the Google Analytics for Firebase Policies at firebase.google.com/policies/analytics, or other policies referenced in these terms at the applicable URL for such policies. Changes will not apply retroactively and will become effective no sooner than 14 days after they are posted. If You do not agree to the modified terms for the Service, You should discontinue Your use of the Service. No amendment to or modification of this Agreement will be binding unless (i) in writing and signed by a duly authorized representative of Google, (ii) You accept updated terms online, or (iii) You continue to use the Service after Google has posted updates to the Agreement or to any policy governing the Service. +16. Applicable Law and Venue. + +(a) Except as set forth in Sections 16(b) and (c) below, all claims arising out of or relating to this Agreement or the Services ("Disputes") will be governed by California law, excluding California’s conflict of laws rules, and all Disputes will be litigated exclusively in the federal or state courts of Santa Clara County, California, USA, and You and Google consent to personal jurisdiction in those courts. + +(b) If Your principal place of business (for entities) or place of residence (for individuals) is in any country within APAC (other than Australia, Japan, New Zealand or Singapore) or Latin America, this Section 16(b) will apply instead of Section 16(a) above. ALL DISPUTES (AS DEFINED ABOVE) WILL BE GOVERNED BY CALIFORNIA LAW, EXCLUDING CALIFORNIA’S CONFLICT OF LAWS RULES. The parties will try in good faith to settle any Dispute within 30 days after the Dispute arises. If the Dispute is not resolved within 30 days, it must be resolved by arbitration by the American Arbitration Association’s International Centre for Dispute Resolution in accordance with its Expedited Commercial Rules in force as of the date of this Agreement ("Rules"). The parties will mutually select one arbitrator. The arbitration will be conducted in English in Santa Clara County, California, USA. Either party may apply to any competent court for injunctive relief necessary to protect its rights pending resolution of the arbitration. The arbitrator may order equitable or injunctive relief consistent with the remedies and limitations in this Agreement. Subject to the confidentiality requirements in Section 5, either party may petition any competent court to issue any order necessary to protect that party's rights or property; this petition will not be considered a violation or waiver of this governing law and arbitration section and will not affect the arbitrator’s powers, including the power to review the judicial decision. The parties stipulate that the courts of Santa Clara County, California, USA, are competent to grant any order under this subsection. The arbitral award will be final and binding on the parties and its execution may be presented in any competent court, including any court with jurisdiction over either party or any of its property. Any arbitration proceeding conducted in accordance with this section will be considered Confidential Information under this Agreement's confidentiality section, including (i) the existence of, (ii) any information disclosed during, and (iii) any oral communications or documents related to the arbitration proceedings. The parties may also disclose the information described in this section to a competent court as may be necessary to file any order under this section or execute any arbitral decision, but the parties must request that those judicial proceedings be conducted in camera (in private). The parties will pay the arbitrator’s fees, the arbitrator's appointed experts' fees and expenses, and the arbitration center's administrative expenses in accordance with the Rules. In its final decision, the arbitrator will determine the non-prevailing party's obligation to reimburse the amount paid in advance by the prevailing party for these fees. Each party will bear its own lawyers’ and experts’ fees and expenses, regardless of the arbitrator’s final decision. + +(c) If Your principal place of business (for entities) or place of residence (for individuals) is in Greece, all Disputes (as defined above) will be governed by Greek law and the parties submit to the exclusive jurisdiction of the courts of Athens in relation to any Dispute. +17. Miscellaneous + +Google and its Affiliates will be excused from performance in this Agreement to the extent that performance is prevented, delayed or obstructed by causes beyond its reasonable control. This Agreement (including any amendment agreed upon by the parties in writing) represents the complete agreement between You and Google concerning its subject matter, and supersedes all prior agreements and representations between the parties. If any provision of this Agreement is held to be unenforceable for any reason, such provision will be reformed to the extent necessary to make it enforceable to the maximum extent permissible so as to effect the intent of the parties, and the remainder of this Agreement will continue in full force and effect. Certain laws of the jurisdiction in which you reside may confer rights and remedies and imply terms into this Agreement that cannot be excluded. Those rights, remedies, and implied terms are not excluded by this Agreement. To the extent that the relevant laws permit Google to limit their operation, Google’s liability under those laws will be limited at its option, to the supply of the services again, or payment of the cost of having the services supplied again. The United Nations Convention on Contracts for the International Sale of Goods and the Uniform Computer Information Transactions Act do not apply to this Agreement. The Software is controlled by U.S. Export Regulations, and it may be not be exported to or used by embargoed countries or individuals. Any notices to Google must be sent to: Google LLC, 1600 Amphitheatre Parkway, Mountain View, CA 94043, USA, with a copy to Legal Department, via first class or air mail or overnight courier, and are deemed given upon receipt. A waiver of any default is not a waiver of any subsequent default. You may not assign or otherwise transfer any of Your rights in this Agreement without Google's prior written consent, and any such attempt is void. Google may assign or otherwise transfer this Agreement to any of its Affiliates. The relationship between Google and You is not one of a legal partnership relationship, but is one of independent contractors. This Agreement will be binding upon and inure to the benefit of the respective successors and assigns of the parties hereto. The following sections of this Agreement will survive any termination thereof: 1, 4, 5, 6 , 7, 8, 9, 10, 11, 12, 14, 16 and 17. + +Last Updated: 2017/05/17 + + +FIREBASE CRASH REPORTING TERMS OF SERVICE + +Thank you for using Google's APIs, other developer services, and associated software (collectively, "APIs"). By accessing or using our APIs, you are agreeing to the terms below. If there is a conflict between these terms and additional terms applicable to a given API, the additional terms will control for that conflict. Collectively, we refer to the terms below, any additional terms, terms within the accompanying API documentation, and any applicable policies and guidelines as the "Terms." You agree to comply with the Terms and that the Terms control your relationship with us. So please read all the Terms carefully. If you use the APIs as an interface to, or in conjunction with other Google products or services, then the terms for those other products or services also apply. + +Under the Terms, "Google" means Google Inc., with offices at 1600 Amphitheatre Parkway, Mountain View, California 94043, United States, unless set forth otherwise in additional terms applicable for a given API. We may refer to "Google" as "we", "our", or "us" in the Terms. +Section 1: Account and Registration +a. Accepting the Terms + +You may not use the APIs and may not accept the Terms if (a) you are not of legal age to form a binding contract with Google, or (b) you are a person barred from using or receiving the APIs under the applicable laws of the United States or other countries including the country in which you are resident or from which you use the APIs. +b. Entity Level Acceptance + +If you are using the APIs on behalf of an entity, you represent and warrant that you have authority to bind that entity to the Terms and by accepting the Terms, you are doing so on behalf of that entity (and all references to "you" in the Terms refer to that entity). +c. Registration + +In order to access certain APIs you may be required to provide certain information (such as identification or contact details) as part of the registration process for the APIs, or as part of your continued use of the APIs. Any registration information you give to Google will always be accurate and up to date and you'll inform us promptly of any updates. +d. Subsidiaries and Affiliates + +Google has subsidiaries and affiliated legal entities around the world. These companies may provide the APIs to you on behalf of Google and the Terms will also govern your relationship with these companies. +Section 2: Using Our APIs +a. Your End Users + +You will require your end users to comply with (and not knowingly enable them to violate) applicable law, regulation, and the Terms. +b. Compliance with Law, Third Party Rights, and Other Google Terms of Service + +You will comply with all applicable law, regulation, and third party rights (including without limitation laws regarding the import or export of data or software, privacy, and local laws). You will not use the APIs to encourage or promote illegal activity or violation of third party rights. You will not violate any other terms of service with Google (or its affiliates). +c. Permitted Access + +You will only access (or attempt to access) an API by the means described in the documentation of that API. If Google assigns you developer credentials (e.g. client IDs), you must use them with the applicable APIs. You will not misrepresent or mask either your identity or your API Client's identity when using the APIs or developer accounts. +d. API Limitations + +Google sets and enforces limits on your use of the APIs (e.g. limiting the number of API requests that you may make or the number of users you may serve), in our sole discretion. You agree to, and will not attempt to circumvent, such limitations documented with each API. If you would like to use any API beyond these limits, you must obtain Google's express consent (and Google may decline such request or condition acceptance on your agreement to additional terms and/or charges for that use). To seek such approval, contact the relevant Google API team for information (e.g. by using the Google developers console). +e. Open Source Software + +Some of the software required by or included in our APIs may be offered under an open source license. Open source software licenses constitute separate written agreements. For certain APIs, open source software is listed in the documentation. To the limited extent the open source software license expressly supersedes the Terms, the open source license instead sets forth your agreement with Google for the applicable open source software. +f. Communication with Google + +We may send you certain communications in connection with your use of the APIs. Please review the applicable API documentation for information about opting out of certain types of communication. +g. Feedback + +If you provide feedback or suggestions about our APIs, then we (and those we allow) may use such information without obligation to you. +h. Non-Exclusivity + +The Terms are non-exclusive. You acknowledge that Google may develop products or services that may compete with the API Clients or any other products or services. +Section 3: Your API Clients +a. API Clients and Monitoring + +The APIs are designed to help you enhance your websites and applications ("API Client(s)"). YOU AGREE THAT GOOGLE MAY MONITOR USE OF THE APIS TO ENSURE QUALITY, IMPROVE GOOGLE PRODUCTS AND SERVICES, AND VERIFY YOUR COMPLIANCE WITH THE TERMS. This monitoring may include Google accessing and using your API Client, for example to identify security issues that could affect Google or its users. You will not interfere with this monitoring. Google may use any technical means to overcome such interference. Google may suspend access to the APIs by you or your API Client without notice if we reasonably believe that you are in violation of the Terms. +b. Security + +You will use commercially reasonable efforts to protect user information collected by your API Client, including personally identifiable information ("PII"), from unauthorized access or use and will promptly report to your users any unauthorized access or use of such information to the extent required by applicable law. +c. Ownership + +Google does not acquire ownership in your API Clients, and by using our APIs, you do not acquire ownership of any rights in our APIs or the content that is accessed through our APIs. +d. User Privacy and API Clients + +You will comply with all applicable privacy laws and regulations including those applying to PII. You will provide and adhere to a privacy policy for your API Client that clearly and accurately describes to users of your API Client what user information you collect and how you use and share such information (including for advertising) with Google and third parties. +Section 4: Prohibitions and Confidentiality +a. API Prohibitions + +When using the APIs, you may not (or allow those acting on your behalf to): + + Sublicense an API for use by a third party. Consequently, you will not create an API Client that functions substantially the same as the APIs and offer it for use by third parties. + Perform an action with the intent of introducing to Google products and services any viruses, worms, defects, Trojan horses, malware, or any items of a destructive nature. + Defame, abuse, harass, stalk, or threaten others. + Interfere with or disrupt the APIs or the servers or networks providing the APIs. + Promote or facilitate unlawful online gambling or disruptive commercial messages or advertisements. + Reverse engineer or attempt to extract the source code from any API or any related software, except to the extent that this restriction is expressly prohibited by applicable law. + Use the APIs for any activities where the use or failure of the APIs could lead to death, personal injury, or environmental damage (such as the operation of nuclear facilities, air traffic control, or life support systems). + Use the APIs to process or store any data that is subject to the International Traffic in Arms Regulations maintained by the U.S. Department of State. + Remove, obscure, or alter any Google terms of service or any links to or notices of those terms. + +Unless otherwise specified in writing by Google, Google does not intend use of the APIs to create obligations under the Health Insurance Portability and Accountability Act, as amended ("HIPAA"), and makes no representations that the APIs satisfy HIPAA requirements. If you are (or become) a "covered entity" or "business associate" as defined in HIPAA, you will not use the APIs for any purpose or in any manner involving transmitting protected health information to Google unless you have received prior written consent to such use from Google. +b. Confidential Matters + + Developer credentials (such as passwords, keys, and client IDs) are intended to be used by you and identify your API Client. You will keep your credentials confidential and make reasonable efforts to prevent and discourage other API Clients from using your credentials. Developer credentials may not be embedded in open source projects. + Our communications to you and our APIs may contain Google confidential information. Google confidential information includes any materials, communications, and information that are marked confidential or that would normally be considered confidential under the circumstances. If you receive any such information, then you will not disclose it to any third party without Google's prior written consent. Google confidential information does not include information that you independently developed, that was rightfully given to you by a third party without confidentiality obligation, or that becomes public through no fault of your own. You may disclose Google confidential information when compelled to do so by law if you provide us reasonable prior notice, unless a court orders that we not receive notice. + +Section 5: Content +a. Content Accessible Through our APIs + +Our APIs contain some third party content (such as text, images, videos, audio, or software). This content is the sole responsibility of the person that makes it available. We may sometimes review content to determine whether it is illegal or violates our policies or the Terms, and we may remove or refuse to display content. Finally, content accessible through our APIs may be subject to intellectual property rights, and, if so, you may not use it unless you are licensed to do so by the owner of that content or are otherwise permitted by law. Your access to the content provided by the API may be restricted, limited, or filtered in accordance with applicable law, regulation, and policy. +b. Submission of Content + +Some of our APIs allow the submission of content. Google does not acquire any ownership of any intellectual property rights in the content that you submit to our APIs through your API Client, except as expressly provided in the Terms. For the sole purpose of enabling Google to provide, secure, and improve the APIs (and the related service(s)) and only in accordance with the applicable Google privacy policies, you give Google a perpetual, irrevocable, worldwide, sublicensable, royalty-free, and non-exclusive license to Use content submitted, posted, or displayed to or from the APIs through your API Client. "Use" means use, host, store, modify, communicate, and publish. Before you submit content to our APIs through your API Client, you will ensure that you have the necessary rights (including the necessary rights from your end users) to grant us the license. +c. Retrieval of content + +When a user's non-public content is obtained through the APIs, you may not expose that content to other users or to third parties without explicit opt-in consent from that user. +d. Data Portability + +Google supports data portability. For as long as you use or store any user data that you obtained through the APIs, you agree to enable your users to export their equivalent data to other services or applications of their choice in a way that's substantially as fast and easy as exporting such data from Google products and services, subject to applicable laws, and you agree that you will not make that data available to third parties who do not also abide by this obligation. +e. Prohibitions on Content + +Unless expressly permitted by the content owner or by applicable law, you will not, and will not permit your end users or others acting on your behalf to, do the following with content returned from the APIs: + + Scrape, build databases, or otherwise create permanent copies of such content, or keep cached copies longer than permitted by the cache header; + Copy, translate, modify, create a derivative work of, sell, lease, lend, convey, distribute, publicly display, or sublicense to any third party; + Misrepresent the source or ownership; or + Remove, obscure, or alter any copyright, trademark, or other proprietary rights notices; or falsify or delete any author attributions, legal notices, or other labels of the origin or source of material. + +Section 6: Brand Features; Attribution +a. Brand Features + +"Brand Features" is defined as the trade names, trademarks, service marks, logos, domain names, and other distinctive brand features of each party. Except where expressly stated, the Terms do not grant either party any right, title, or interest in or to the other party's Brand Features. All use by you of Google's Brand Features (including any goodwill associated therewith) will inure to the benefit of Google. +b. Attribution + +You agree to display any attribution(s) required by Google as described in the documentation for the API. Google hereby grants to you a nontransferable, nonsublicenseable, nonexclusive license while the Terms are in effect to display Google's Brand Features for the purpose of promoting or advertising that you use the APIs. You must only use the Google Brand Features in accordance with the Terms and for the purpose of fulfilling your obligations under this Section. In using Google's Brand Features, you must follow the Google Brand Features Use Guidelines. You understand and agree that Google has the sole discretion to determine whether your attribution(s) and use of Google's Brand Features are in accordance with the above requirements and guidelines. +c. Publicity + +You will not make any statement regarding your use of an API which suggests partnership with, sponsorship by, or endorsement by Google without Google's prior written approval. +d. Promotional and Marketing Use + +In the course of promoting, marketing, or demonstrating the APIs you are using and the associated Google products, Google may produce and distribute incidental depictions, including screenshots, video, or other content from your API Client, and may use your company or product name. You grant us all necessary rights for the above purposes. +Section 7: Privacy and Copyright Protection +a. Google Privacy Policies + +By using our APIs, Google may use submitted information in accordance with our privacy policies. +b. Google DMCA Policy + +We provide information to help copyright holders manage their intellectual property online, but we can't determine whether something is being used legally or not without their input. We respond to notices of alleged copyright infringement and terminate accounts of repeat infringers according to the process set out in the U.S. Digital Millennium Copyright Act. If you think somebody is violating your copyrights and want to notify us, you can find information about submitting notices and Google's policy about responding to notices in our Help Center. +Section 8: Termination +a. Termination + +You may stop using our APIs at any time with or without notice. Further, if you want to terminate the Terms, you must provide Google with prior written notice and upon termination, cease your use of the applicable APIs. Google reserves the right to terminate the Terms with you or discontinue the APIs or any portion or feature or your access thereto for any reason and at any time without liability or other obligation to you. +b. Your Obligations Post-Termination + +Upon any termination of the Terms or discontinuation of your access to an API, you will immediately stop using the API, cease all use of the Google Brand Features, and delete any cached or stored content that was permitted by the cache header under Section 5. Google may independently communicate with any account owner whose account(s) are associated with your API Client and developer credentials to provide notice of the termination of your right to use an API. +c. Surviving Provisions + +When the Terms come to an end, those terms that by their nature are intended to continue indefinitely will continue to apply, including but not limited to: Sections 4b, 5, 8, 9, and 10. +Section 9: Liability for our APIs +a. WARRANTIES + +EXCEPT AS EXPRESSLY SET OUT IN THE TERMS, NEITHER GOOGLE NOR ITS SUPPLIERS OR DISTRIBUTORS MAKE ANY SPECIFIC PROMISES ABOUT THE APIS. FOR EXAMPLE, WE DON'T MAKE ANY COMMITMENTS ABOUT THE CONTENT ACCESSED THROUGH THE APIS, THE SPECIFIC FUNCTIONS OF THE APIS, OR THEIR RELIABILITY, AVAILABILITY, OR ABILITY TO MEET YOUR NEEDS. WE PROVIDE THE APIS "AS IS". + +SOME JURISDICTIONS PROVIDE FOR CERTAIN WARRANTIES, LIKE THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. EXCEPT AS EXPRESSLY PROVIDED FOR IN THE TERMS, TO THE EXTENT PERMITTED BY LAW, WE EXCLUDE ALL WARRANTIES, GUARANTEES, CONDITIONS, REPRESENTATIONS, AND UNDERTAKINGS. +b. LIMITATION OF LIABILITY + +WHEN PERMITTED BY LAW, GOOGLE, AND GOOGLE'S SUPPLIERS AND DISTRIBUTORS, WILL NOT BE RESPONSIBLE FOR LOST PROFITS, REVENUES, OR DATA; FINANCIAL LOSSES; OR INDIRECT, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES. + +TO THE EXTENT PERMITTED BY LAW, THE TOTAL LIABILITY OF GOOGLE, AND ITS SUPPLIERS AND DISTRIBUTORS, FOR ANY CLAIM UNDER THE TERMS, INCLUDING FOR ANY IMPLIED WARRANTIES, IS LIMITED TO THE AMOUNT YOU PAID US TO USE THE APPLICABLE APIS (OR, IF WE CHOOSE, TO SUPPLYING YOU THE APIS AGAIN) DURING THE SIX MONTHS PRIOR TO THE EVENT GIVING RISE TO THE LIABILITY. + +IN ALL CASES, GOOGLE, AND ITS SUPPLIERS AND DISTRIBUTORS, WILL NOT BE LIABLE FOR ANY EXPENSE, LOSS, OR DAMAGE THAT IS NOT REASONABLY FORESEEABLE. +c. Indemnification + +Unless prohibited by applicable law, if you are a business, you will defend and indemnify Google, and its affiliates, directors, officers, employees, and users, against all liabilities, damages, losses, costs, fees (including legal fees), and expenses relating to any allegation or third-party legal proceeding to the extent arising from: + + your misuse or your end user's misuse of the APIs; + your violation or your end user's violation of the Terms; or + any content or data routed into or used with the APIs by you, those acting on your behalf, or your end users. + +Section 10: General Provisions +a. Modification + +We may modify the Terms or any portion to, for example, reflect changes to the law or changes to our APIs. You should look at the Terms regularly. We'll post notice of modifications to the Terms within the documentation of each applicable API, to this website, and/or in the Google developers console. Changes will not apply retroactively and will become effective no sooner than 30 days after they are posted. But changes addressing new functions for an API or changes made for legal reasons will be effective immediately. If you do not agree to the modified Terms for an API, you should discontinue your use of that API. Your continued use of the API constitutes your acceptance of the modified Terms. +b. U.S. Federal Agency Entities + +The APIs were developed solely at private expense and are commercial computer software and related documentation within the meaning of the applicable U.S. Federal Acquisition Regulation and agency supplements thereto. +c. General Legal Terms + +We each agree to contract in the English language. If we provide a translation of the Terms, we do so for your convenience only and the English Terms will solely govern our relationship. The Terms do not create any third party beneficiary rights or any agency, partnership, or joint venture. Nothing in the Terms will limit either party's ability to seek injunctive relief. We are not liable for failure or delay in performance to the extent caused by circumstances beyond our reasonable control. If you do not comply with the Terms, and Google does not take action right away, this does not mean that Google is giving up any rights that it may have (such as taking action in the future). If it turns out that a particular term is not enforceable, this will not affect any other terms. The Terms are the entire agreement between you and Google relating to its subject and supersede any prior or contemporaneous agreements on that subject. For information about how to contact Google, please visit our contact page. + +Except as set forth below: (i) the laws of California, U.S.A., excluding California's conflict of laws rules, will apply to any disputes arising out of or related to the Terms or the APIs and (ii) ALL CLAIMS ARISING OUT OF OR RELATING TO THE TERMS OR THE APIS WILL BE LITIGATED EXCLUSIVELY IN THE FEDERAL OR STATE COURTS OF SANTA CLARA COUNTY, CALIFORNIA, USA, AND YOU AND GOOGLE CONSENT TO PERSONAL JURISDICTION IN THOSE COURTS. + +If you are accepting the Terms on behalf of a United States federal government entity, then the following applies instead of the paragraph above: the laws of the United States of America, excluding its conflict of laws rules, will apply to any disputes arising out of or related to the Terms or the APIs. Solely to the extent permitted by United States Federal law: (i) the laws of the State of California (excluding California's conflict of laws rules) will apply in the absence of applicable federal law; and (ii) FOR ALL CLAIMS ARISING OUT OF OR RELATING TO THE TERMS OR THE APIS, THE PARTIES CONSENT TO PERSONAL JURISDICTION IN, AND THE EXCLUSIVE VENUE OF, THE COURTS IN SANTA CLARA COUNTY, CALIFORNIA. + +If you are accepting the Terms on behalf of a United States city, county, or state government entity, then the following applies instead of the paragraph above: the parties agree to remain silent regarding governing law and venue. + + +USER-DATA POLICY + +Google API Services, including Google Sign-In, are part of an authentication and authorization framework that gives you, the developer, the ability to connect directly with Google users when you would like to request access to Google user data. The policy below, as well as the Google APIs Terms of Service, govern the use of Google API Services when you request access to Google user data. Please check back from time to time as these policies are occasionally updated. +Accurately represent your identity and intent + +If you wish to access Google user data you must provide Google users and Google with clear and accurate information regarding your use of Google API Services. This includes, without limitation, requirements to accurately represent: + + Who is requesting Google user data? All permission requests must accurately represent the identity of the application that seeks access to user data. If you have obtained authorized client credentials to access Google API Services, keep these credentials confidential. + What data are you requesting? You must provide clear and accurate information explaining the types of data being requested. In addition, if you plan to access or use a type of user data that was not originally disclosed in your privacy policy when a Google user initially authorized access, you must update your privacy policy and prompt the user to consent to any changes before you may access that data. + Why are you requesting Google user data? Be honest and transparent with users when you explain the purpose for which your application requests user data. If your application requests data for one reason but the data will also be utilized for a secondary purpose, you must notify Google users of both use cases. As a general matter, users should be able to readily understand the value of providing the data that your application requests, as well as the consequences of sharing that data with your application. + +Be transparent about the data you access with clear and prominent privacy disclosures + +You must publish a privacy policy that fully documents how your application interacts with user data. You must list the privacy policy URL in your OAuth client configuration when your application is made available to the public. + +Your Privacy Policy and all in-product privacy notifications should be accurate, comprehensive, and easily accessible. Your privacy policy and in-product privacy notifications must thoroughly disclose the manner in which your application accesses, uses, stores, or shares Google user data. Your use of Google user data must be limited to the practices explicitly disclosed in your published privacy policy, but you should consider the use of additional in-product notifications to ensure that users understand how your application will handle user data. If you change the way your application uses Google user data, you must notify users and prompt them to consent to an updated privacy policy before you make use of Google user data in a new way or for a different purpose that originally disclosed. + +Disclosures about data use should be prominent and timely. Your privacy policy and any in-product notifications regarding data use should be prominently displayed in your application interface so that users can find this information easily. Where possible, disclosures about data use should be timely and shown in context. +Request relevant permissions + +Permission requests should make sense to users, and should be limited to the critical information necessary to implement your application. + +Don't request access to information that you don't need. You may only request access to the Google user data that is necessary to implement existing features or services in your application. Don't attempt to "future proof" your access to user data by requesting access to information that might benefit services or features that have not yet been implemented. + +Request permissions in context where possible. Request access to user data in context (via incremental auth) whenever you can, so that users understand why you need the data. +Deceptive or unauthorized use of Google API Services is prohibited + +You are strictly prohibited from engaging in any activity that may deceive users or Google about your use of Google API Services. This includes without limitation the following requirements: + +Do not misrepresent what data is collected or what you do with Google user data. Be up front with users so that they can make an informed decision to grant authorization. You must disclose all user data that you access, use, store, delete, or share, as well as any actions you take on a user's behalf. + +You are not permitted to access, aggregate, or analyze Google user data if the data will be displayed, sold, or otherwise distributed to a third party conducting surveillance. + +Overall there should be no surprises for Google users: hidden features, services, or actions that are inconsistent with the marketed purpose of your application may lead Google to suspend your ability to access Google API Services. + +Do not mislead Google about an application's operating environment. You must accurately represent the environment in which the authentication page appears. For example, don't claim to be an Android application in the user agent header if your application is running on iOS, or represent that your application's authentication page is rendered in a desktop browser if instead the authentication page is rendered in an embedded web view. + +Do not use undocumented APIs without express permission. Don't reverse engineer undocumented Google API Services or otherwise attempt to derive or use the underlying source code of undocumented Google API Services. You may only access data from Google API Services according to the means stipulated in the official documentation of that API Service, as provided on Google's developer site. + +Do not make false or misleading statements about any entities that have allegedly authorized or managed your application. You must accurately represent the company, organization, or other authority that manages your application. Making false representations about client credentials to Google or Google users is grounds for suspension. +Child-directed apps + +The Children's Online Privacy Protection Act, or COPPA, applies to websites, apps, and services directed to children under the age of 13 and general audience apps, websites, or services with users known to be under the age of 13. While child-directed apps may use some Google services, developers are responsible for using these services according to their obligations under the law. Please review the FTC's guidance on COPPA (including information about the differences between mixed audience apps and apps directed primarily to children from the FTC's website) and consult with your own legal counsel. + +Child-directed apps: If your application is directed primarily at children, it should not use Google Sign-In or any other Google API Service that accesses data associated with a Google Account. This restriction includes Google Play Games Services and any other Google API Service using the OAuth technology for authentication and authorization. + +Mixed audience apps: Applications that are mixed audience shouldn't require users to sign in to a Google Account, but can offer, for example, Google Sign-In or Google Play Games Services as an optional feature. In these cases, users must be able to access the application in its entirety without signing into a Google Account. +Maintain a secure operating environment + +You must take reasonable and appropriate steps to protect all applications or systems that make use of Google API Services against unauthorized or unlawful access, use, destruction, loss, alteration, or disclosure. +Enforcement + +You must access Google API Services in accordance with the Google APIs Terms of Service. If you are found to be out of compliance with the Google APIs Terms of Service, this Google API Services: User Data Policy, or any Google product policies that are applicable to the Google API Service you are using, Google may revoke or suspend your access to Google API Services and other Google products and services. Your access to Google API Services may also be revoked if your application enables end-users or other parties to violate the Google APIs Terms of Service and/or Google policies. + + +FIREBASE ANALYTICS POLICY + + +By enabling Google Analytics for Firebase you enable the collection of data about App Users, including via identifiers for mobile devices (including Android Advertising ID and Advertising Identifier for iOS), cookies and similar technologies. + +You will not facilitate the merging of personally-identifiable information with non-personally identifiable information unless you have robust notice of, and the user's prior affirmative (i.e., opt-in) consent to, that merger. + +You are required to notify your App Users by disclosing the following information: + + The Google Analytics for Firebase features you have implemented. + How you and third-party vendors use first-party cookies, or other first-party identifiers, and third-party cookies and similar technologies, such as identifiers for mobile devices (including Android Advertising ID and Advertising Identifier for iOS), or other third-party identifiers, together. + How App Users can opt-out of the Google Analytics for Firebase features you use, including through applicable device settings, such as the device advertising settings for mobile apps, or any other available means. + +European Union User Consent Policy + +You must comply with the European Union User Consent Policy. +Interest-based advertising + +If you use Google Analytics for Firebase to collect sensitive information about your visitors, including information described in the Google AdWords sensitive category restrictions, you may not use Google Analytics for Firebase to collect data for the purpose of interest based advertising. + +Because laws across countries and territories vary, and because Google Analytics for Firebase can be used in many ways, Google is unable to provide the exact language you need to include in your privacy policy. Only you understand the unique aspects and special considerations of your business, and your privacy policy should account for this information that only you can provide. + +Last Updated: 2017/05/17 + + +END OF FULL TERMS OF SERVICE \ No newline at end of file diff --git a/README.md b/README.md index ac59c2f..061cb2e 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ [![BitCoinApp - Aptiode](https://img.shields.io/badge/Download%20-Aptoide-green.svg)](https://goo.gl/5sqsNP) [![BitCoinApp - Releases](https://img.shields.io/badge/Download%20-GitHub%20APK-green.svg)](https://goo.gl/qeaU85) +## Read the docs + +Here you have the link where you will be able to *read the docs* for this app: https://javinator9889.github.io/BitCoinPools/ + ## Changelog With the latest update, we have just integrated the option to *recieve notifications*: as this app is for getting information about [BitCoin Pools](https://en.wikipedia.org/wiki/Mining_pool) is important to know the value of the **BitCoin**. So now, you can configure notifications and more in the app. diff --git a/README_conflict-20180306-220131.md b/README_conflict-20180306-220131.md new file mode 100644 index 0000000..ac59c2f --- /dev/null +++ b/README_conflict-20180306-220131.md @@ -0,0 +1,36 @@ +# BitCoinPools + +**This is an app for retrieving latest information about BTC pools**. + +[![BitCoinApp - Aptiode](https://img.shields.io/badge/Download%20-Aptoide-green.svg)](https://goo.gl/5sqsNP) +[![BitCoinApp - Releases](https://img.shields.io/badge/Download%20-GitHub%20APK-green.svg)](https://goo.gl/qeaU85) + +## Changelog + +With the latest update, we have just integrated the option to *recieve notifications*: as this app is for getting information about [BitCoin Pools](https://en.wikipedia.org/wiki/Mining_pool) is important to know the value of the **BitCoin**. So now, you can configure notifications and more in the app. + +## What this app exactly does? + +The purpose of this application is *getting information about mining pools* avaiable on Internet [(click here for getting more information)](https://en.wikipedia.org/wiki/Mining_pool), and also giving the user **more information** for the BitCoin (as its current price) + +![Interface](https://github.com/Javinator9889/BitCoinPools/blob/master/screenshots/englishinterface.jpg) +![Buttons](https://github.com/Javinator9889/BitCoinPools/blob/master/screenshots/buttons.jpg) + +**Now, in the latest version** I have included a notifcation system that can be *enabled or disabled* by the user in order to know when the BitCoin **reaches or not** a specific price. + +![Settings](https://github.com/Javinator9889/BitCoinPools/blob/master/screenshots/choosingdays.jpg) + +## Downloading different versions + +Here you have to download the newest version available, on *Aptoide* or directly from *GitHub*: + +[![BitCoinApp - Aptiode](https://img.shields.io/badge/Download%20-Aptoide-green.svg)](https://goo.gl/5sqsNP) +[![BitCoinApp - Releases](https://img.shields.io/badge/Download%20-GitHub%20APK-green.svg)](https://goo.gl/qeaU85) + +## I have used it and I love it. What can I do? + +You can do: ++ **Fork** these project and *develop* your custom version: I will like to see it ++ **Rate**, giving me a star ⭐️ ++ **See my other projects** in which I spent so much time working on them 💻 ++ **And watch 👁** if you would like to recieve new upgardes diff --git a/Red_Arrow_Down.svg b/Red_Arrow_Down.svg new file mode 100644 index 0000000..9b627af --- /dev/null +++ b/Red_Arrow_Down.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index ede84a7..ff0974d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ android { applicationId "javinator9889.bitcoinpools" minSdkVersion 21 targetSdkVersion 27 - versionCode 30 + versionCode 59 versionName "1.18" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -31,7 +31,7 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'com.android.support:appcompat-v7:27.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' implementation 'com.google.firebase:firebase-crash:11.8.0' implementation 'com.google.firebase:firebase-invites:11.8.0' @@ -39,19 +39,19 @@ dependencies { androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3' - compile 'com.android.support:preference-v14:27.0.2' - compile 'com.android.support:preference-v7:27.0.2' - implementation 'com.android.support:design:27.0.2' + compile 'com.android.support:preference-v14:27.1.0' + compile 'com.android.support:preference-v7:27.1.0' + implementation 'com.android.support:design:27.1.0' implementation 'com.afollestad.material-dialogs:core:0.9.6.0' compile 'ch.acra:acra:4.9.2' implementation('com.mikepenz:aboutlibraries:6.0.2@aar') { transitive = true } - implementation 'com.android.support:recyclerview-v7:27.0.2' - implementation 'com.android.support:support-annotations:27.0.2' - compile 'com.android.support:appcompat-v7:27.0.2' - compile 'com.android.support:cardview-v7:27.0.2' - compile 'com.android.support:recyclerview-v7:27.0.2' + implementation 'com.android.support:recyclerview-v7:27.1.0' + implementation 'com.android.support:support-annotations:27.1.0' + compile 'com.android.support:appcompat-v7:27.1.0' + compile 'com.android.support:cardview-v7:27.1.0' + compile 'com.android.support:recyclerview-v7:27.1.0' compile 'com.jpardogo.materialtabstrip:library:1.1.1' compile 'com.google.firebase:firebase-core:11.8.0' compile('com.crashlytics.sdk.android:crashlytics:2.7.1@aar') { diff --git a/app/debug/app-debug.apk b/app/debug/app-debug.apk new file mode 100644 index 0000000..4c5f9df Binary files /dev/null and b/app/debug/app-debug.apk differ diff --git a/app/debug/output.json b/app/debug/output.json new file mode 100644 index 0000000..3d8741f --- /dev/null +++ b/app/debug/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":41},"path":"app-debug.apk","properties":{"packageId":"javinator9889.bitcoinpools","split":"","minSdkVersion":"21"}}] \ No newline at end of file diff --git a/app/release/BitCoinPools-1.18.apk b/app/release/BitCoinPools-1.18.apk new file mode 100644 index 0000000..62666e8 Binary files /dev/null and b/app/release/BitCoinPools-1.18.apk differ diff --git a/app/release/BitCoinPools.apk b/app/release/BitCoinPools.apk deleted file mode 100644 index b6b1429..0000000 Binary files a/app/release/BitCoinPools.apk and /dev/null differ diff --git a/app/release/output.json b/app/release/output.json index b2d3da5..bbcce77 100644 --- a/app/release/output.json +++ b/app/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":28},"path":"app-release.apk","properties":{"packageId":"javinator9889.bitcoinpools","split":"","minSdkVersion":"21"}}] \ No newline at end of file +[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":59},"path":"app-release.apk","properties":{"packageId":"javinator9889.bitcoinpools","split":"","minSdkVersion":"21"}}] \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1f73388..0947c1f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,36 +8,48 @@ + + android:theme="@style/CustomTheme"> + android:name=".DataLoaderScreen" + android:screenOrientation="portrait" + android:configChanges= + "screenSize|smallestScreenSize|screenLayout|orientation" + android:theme="@style/AppTheme.Launcher"> + + - + android:screenOrientation="portrait"/> - + - + diff --git a/app/src/main/java/javinator9889/bitcoinpools/AppUpdaterManager/CheckUpdates.java b/app/src/main/java/javinator9889/bitcoinpools/AppUpdaterManager/CheckUpdates.java index d0f0c58..ab3280d 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/AppUpdaterManager/CheckUpdates.java +++ b/app/src/main/java/javinator9889/bitcoinpools/AppUpdaterManager/CheckUpdates.java @@ -1,15 +1,10 @@ package javinator9889.bitcoinpools.AppUpdaterManager; -import android.app.DownloadManager; -import android.content.BroadcastReceiver; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageManager; import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.os.StrictMode; import android.support.annotation.NonNull; import android.util.Log; @@ -20,8 +15,6 @@ import org.json.JSONArray; import org.json.JSONException; -import java.io.File; -import java.lang.reflect.Method; import java.util.concurrent.ExecutionException; import javinator9889.bitcoinpools.BitCoinApp; @@ -36,55 +29,56 @@ public class CheckUpdates { private static final String GITHUB_API_URL = "https://api.github.com/repos/"; private static String CONNECTION_URL; private static String APP_VERSION; - private static JSONArray RETRIEVED_DATA; private static String LATEST_VERSION; private static String DOWNLOAD_URL = null; - private static String MORE_INFO; - private static String APK_NAME; + private static String MORE_INFO = Constants.GOOGLE_PLAY_URL; private static boolean HTML_PAGE = false; public CheckUpdates(@NonNull String GitHub_User, @NonNull String GitHub_Repo) { CONNECTION_URL = GITHUB_API_URL + GitHub_User + "/" + GitHub_Repo + "/releases"; try { - APP_VERSION = BitCoinApp.getAppContext().getPackageManager().getPackageInfo(BitCoinApp.getAppContext().getPackageName(), 0).versionName; + APP_VERSION = BitCoinApp.getAppContext().getPackageManager() + .getPackageInfo(BitCoinApp.getAppContext().getPackageName(), 0) + .versionName; } catch (PackageManager.NameNotFoundException e) { - Log.e(Constants.LOG.CTAG, Constants.LOG.NO_INFO + e.getMessage(), new PackageManager.NameNotFoundException()); + Log.e(Constants.LOG.CTAG, Constants.LOG.NO_INFO + e.getMessage(), + new PackageManager.NameNotFoundException()); } getData(); } - public void checkForUpdates(final Context dialogContext, String title, String description, String positiveText, String negativeText, String neutralText) { - if (!APP_VERSION.equals(LATEST_VERSION) && (Float.parseFloat(APP_VERSION) < Float.parseFloat(LATEST_VERSION))) { - Log.d(Constants.LOG.CTAG, Constants.LOG.NEW_VERSION + APP_VERSION + " | " + LATEST_VERSION); + public void checkForUpdates(final Context dialogContext, String title, String description, + String positiveText, String negativeText, String neutralText) { + if (!APP_VERSION.equals(LATEST_VERSION)) { + Log.d(Constants.LOG.CTAG, Constants.LOG.NEW_VERSION + APP_VERSION + + " | " + LATEST_VERSION); MaterialDialog materialDialog; if (!HTML_PAGE) { Log.d(Constants.LOG.CTAG, Constants.LOG.DOW_NOTIFICATION); materialDialog = new MaterialDialog.Builder(dialogContext) .title(title) .content(description) - .positiveText(positiveText) .negativeText(negativeText) - .neutralText(neutralText) + .neutralText(positiveText) .cancelable(false) .onAny(new MaterialDialog.SingleButtonCallback() { @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + public void onClick(@NonNull MaterialDialog dialog, + @NonNull DialogAction which) { switch (which) { - case POSITIVE: - DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(DOWNLOAD_URL)); - downloadRequest.setTitle(APK_NAME); - downloadRequest.allowScanningByMediaScanner(); - downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - downloadRequest.setVisibleInDownloadsUi(true); - downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, APK_NAME); - final DownloadManager manager = (DownloadManager) dialogContext.getSystemService(Context.DOWNLOAD_SERVICE); - assert manager != null; - manager.enqueue(downloadRequest); - break; case NEUTRAL: - Uri webUri = Uri.parse(MORE_INFO); - Intent launchBrowser = new Intent(Intent.ACTION_VIEW, webUri); - dialogContext.startActivity(launchBrowser); + try { + Uri webUri = Uri.parse( + "market://details?id=javinator9889.bitcoinpools"); + Intent launchBrowser = new Intent(Intent.ACTION_VIEW, + webUri); + dialogContext.startActivity(launchBrowser); + } catch (ActivityNotFoundException e) { + Uri webUri = Uri.parse(MORE_INFO); + Intent launchBrowser = new Intent(Intent.ACTION_VIEW, + webUri); + dialogContext.startActivity(launchBrowser); + } break; case NEGATIVE: dialog.dismiss(); @@ -127,7 +121,7 @@ private void getData() { NetworkConnection connection = new NetworkConnection(); connection.execute(CONNECTION_URL); try { - RETRIEVED_DATA = connection.get(); + JSONArray RETRIEVED_DATA = connection.get(); int jsonLength = RETRIEVED_DATA.length(); boolean noPreRelease = false; for (int i = 0; (i < jsonLength) && !noPreRelease; ++i) { @@ -135,20 +129,26 @@ private void getData() { noPreRelease = true; LATEST_VERSION = RETRIEVED_DATA.getJSONObject(i).getString("tag_name"); boolean apkFound = false; - for (int j = 0; (j < RETRIEVED_DATA.getJSONObject(i).getJSONArray("assets").length()) && !apkFound; ++j) { - if (!(RETRIEVED_DATA.getJSONObject(i).getJSONArray("assets").isNull(j) && (RETRIEVED_DATA.getJSONObject(i).getJSONArray("assets").getJSONObject(j).getString("name").contains(".apk")))) { + for (int j = 0; (j < RETRIEVED_DATA.getJSONObject(i).getJSONArray("assets") + .length()) && !apkFound; ++j) + { + if (!(RETRIEVED_DATA.getJSONObject(i).getJSONArray("assets").isNull(j) + && (RETRIEVED_DATA.getJSONObject(i).getJSONArray("assets") + .getJSONObject(j).getString("name").contains(".apk")))) + { apkFound = true; - DOWNLOAD_URL = RETRIEVED_DATA.getJSONObject(i).getJSONArray("assets").getJSONObject(0).getString("browser_download_url"); - APK_NAME = RETRIEVED_DATA.getJSONObject(i).getJSONArray("assets").getJSONObject(j).getString("name"); + DOWNLOAD_URL = RETRIEVED_DATA.getJSONObject(i) + .getJSONArray("assets").getJSONObject(0) + .getString("browser_download_url"); } } if (DOWNLOAD_URL == null) { HTML_PAGE = true; } - MORE_INFO = RETRIEVED_DATA.getJSONObject(i).getString("html_url"); } } - } catch (InterruptedException | ExecutionException | JSONException | NullPointerException e) { + } catch (InterruptedException | ExecutionException + | JSONException | NullPointerException e) { Crashlytics.logException(e); Log.e(Constants.LOG.CTAG, Constants.LOG.NO_INFO + e.getMessage()); } diff --git a/app/src/main/java/javinator9889/bitcoinpools/AppUpdaterManager/NetworkConnection.java b/app/src/main/java/javinator9889/bitcoinpools/AppUpdaterManager/NetworkConnection.java index 6af59ef..368eaf2 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/AppUpdaterManager/NetworkConnection.java +++ b/app/src/main/java/javinator9889/bitcoinpools/AppUpdaterManager/NetworkConnection.java @@ -1,6 +1,7 @@ package javinator9889.bitcoinpools.AppUpdaterManager; import android.os.AsyncTask; +import android.support.annotation.NonNull; import android.util.Log; import org.json.JSONArray; @@ -34,6 +35,7 @@ protected JSONArray doInBackground(String... url) { return null; } + @NonNull private String readAll(Reader httpsReader) throws IOException { StringBuilder response = new StringBuilder(); int valueRead; @@ -43,9 +45,11 @@ private String readAll(Reader httpsReader) throws IOException { return response.toString(); } + @NonNull private JSONArray readJSONFromURL(String url) throws IOException, JSONException { try (InputStream JSONStream = new URL(url).openStream()) { - BufferedReader br = new BufferedReader(new InputStreamReader(JSONStream, Charset.forName("UTF-8"))); + BufferedReader br = new BufferedReader( + new InputStreamReader(JSONStream, Charset.forName("UTF-8"))); String JSONText = readAll(br); return new JSONArray(JSONText); } diff --git a/app/src/main/java/javinator9889/bitcoinpools/BackgroundJobs/CacheJobSchedulerService.java b/app/src/main/java/javinator9889/bitcoinpools/BackgroundJobs/CacheJobSchedulerService.java new file mode 100644 index 0000000..c305f43 --- /dev/null +++ b/app/src/main/java/javinator9889/bitcoinpools/BackgroundJobs/CacheJobSchedulerService.java @@ -0,0 +1,89 @@ +package javinator9889.bitcoinpools.BackgroundJobs; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Locale; + +import javinator9889.bitcoinpools.BitCoinApp; +import javinator9889.bitcoinpools.CacheManaging; +import javinator9889.bitcoinpools.Constants; +import javinator9889.bitcoinpools.JSONTools.JSONTools; +import javinator9889.bitcoinpools.NetTools.net; + +/** + * Created by Javinator9889 on 04/03/2018. + * + * Controls cache + */ + +public class CacheJobSchedulerService extends JobService { + private boolean jobWorking = false; + private boolean jobCancelled = false; + + private Handler jobHandler = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + if (!jobCancelled) { + try { + updateCache(); + jobWorking = false; + jobFinished((JobParameters) msg.obj, false); + return false; + } catch (NullPointerException e) { + e.printStackTrace(); + jobWorking = false; + jobFinished((JobParameters) msg.obj, true); + return false; + } + } + return false; + } + }); + + @Override + public boolean onStartJob(JobParameters params) { + jobWorking = true; + jobCancelled = false; + jobHandler.sendMessage(Message.obtain(jobHandler, 2, params)); + Log.d("CACHEJOB", "Starting cache job"); + return jobWorking; + } + + @Override + public boolean onStopJob(JobParameters params) { + jobCancelled = true; + boolean needsReschedule = jobWorking; + jobHandler.removeMessages(2); + jobFinished(params, needsReschedule); + return needsReschedule; + } + + private void updateCache() { + String date = new SimpleDateFormat("dd-MM-yyyy HH:mm", Locale.US) + .format(Calendar.getInstance().getTime()); + CacheManaging cache = CacheManaging.newInstance(BitCoinApp.getAppContext()); + try { + cache.setupFile(); + net request = new net(); + request.execute(Constants.STATS_URL); + HashMap valuesObtained = JSONTools.convert2HashMap(request.get()); + HashMap newValuesToSave = new LinkedHashMap<>(); + newValuesToSave.put("date", date); + for (String key : valuesObtained.keySet()) { + newValuesToSave.put(key, String.valueOf(valuesObtained.get(key))); + } + cache.writeCache(newValuesToSave); + } catch (Exception e) { + e.printStackTrace(); + throw new NullPointerException("Impossible to obtain values"); + } + } +} diff --git a/app/src/main/java/javinator9889/bitcoinpools/BackgroundJobs/NotificationHandler.java b/app/src/main/java/javinator9889/bitcoinpools/BackgroundJobs/NotificationHandler.java index cd5856b..01142b4 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/BackgroundJobs/NotificationHandler.java +++ b/app/src/main/java/javinator9889/bitcoinpools/BackgroundJobs/NotificationHandler.java @@ -8,6 +8,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; +import android.support.annotation.NonNull; import android.util.Log; import org.json.JSONException; @@ -16,6 +17,7 @@ import javinator9889.bitcoinpools.BitCoinApp; import javinator9889.bitcoinpools.Constants; +import javinator9889.bitcoinpools.DataLoaderScreen; import javinator9889.bitcoinpools.MainActivity; import javinator9889.bitcoinpools.NetTools.net; import javinator9889.bitcoinpools.R; @@ -35,7 +37,8 @@ class NotificationHandler { private NotificationHandler() { final SharedPreferences sp = BitCoinApp.getSharedPreferences(); Log.d(Constants.LOG.NTAG, Constants.LOG.CREATING_NOTIFICATION); - NOTIFICATIONS_ENABLED = sp.getBoolean(Constants.SHARED_PREFERENCES.NOTIFICATIONS_ENABLED, false); + NOTIFICATIONS_ENABLED = sp.getBoolean(Constants.SHARED_PREFERENCES.NOTIFICATIONS_ENABLED, + false); NOTIFIED_HIGH = sp.getBoolean(Constants.SHARED_PREFERENCES.NOTIFIED_HIGH, false); NOTIFIED_LOW = sp.getBoolean(Constants.SHARED_PREFERENCES.NOTIFIED_LOW, false); SPECIFIC_VALUE = sp.getInt(Constants.SHARED_PREFERENCES.VALUE_TO_CHECK, 1000); @@ -53,29 +56,35 @@ void putNotification() { String notificationTitle = ""; String notificationText = ""; String notificationTextLong = ""; - NotificationManager notificationManager = (NotificationManager) BitCoinApp.getAppContext().getSystemService(Context.NOTIFICATION_SERVICE); + NotificationManager notificationManager = (NotificationManager) BitCoinApp.getAppContext() + .getSystemService(Context.NOTIFICATION_SERVICE); PendingIntent clickIntent = PendingIntent.getActivity( BitCoinApp.getAppContext(), Constants.REQUEST_CODE, - new Intent(BitCoinApp.getAppContext(), MainActivity.class), + new Intent(BitCoinApp.getAppContext(), DataLoaderScreen.class), PendingIntent.FLAG_UPDATE_CURRENT ); if (NOTIFICATIONS_ENABLED) { if ((MPU < SPECIFIC_VALUE) && !NOTIFIED_LOW) { notificationTitle = BitCoinApp.getAppContext().getString(R.string.lowerPrice); - notificationTextLong = BitCoinApp.getAppContext().getString(R.string.lowerPriceX) + SPECIFIC_VALUE + ". " + BitCoinApp.getAppContext().getString(R.string.actualCost) + MPU; - notificationText = BitCoinApp.getAppContext().getString(R.string.lowerPriceX) + SPECIFIC_VALUE; + notificationTextLong = BitCoinApp.getAppContext().getString(R.string.lowerPriceX) + + SPECIFIC_VALUE + ". " + + BitCoinApp.getAppContext().getString(R.string.actualCost) + MPU; + notificationText = BitCoinApp.getAppContext().getString(R.string.lowerPriceX) + + SPECIFIC_VALUE; NOTIFIED_HIGH = false; NOTIFIED_LOW = true; - notify = true; + notify = (MPU != -1); } else if ((MPU > SPECIFIC_VALUE) && !NOTIFIED_HIGH) { notificationTitle = BitCoinApp.getAppContext().getString(R.string.morePrice); - notificationTextLong = BitCoinApp.getAppContext().getString(R.string.morePriceX) + SPECIFIC_VALUE + ". " + BitCoinApp.getAppContext().getString(R.string.actualCost) + MPU; - notificationText = BitCoinApp.getAppContext().getString(R.string.morePriceX) + SPECIFIC_VALUE; + notificationTextLong = BitCoinApp.getAppContext().getString(R.string.morePriceX) + + SPECIFIC_VALUE + ". " + BitCoinApp.getAppContext().getString(R.string.actualCost) + MPU; + notificationText = BitCoinApp.getAppContext().getString(R.string.morePriceX) + + SPECIFIC_VALUE; NOTIFIED_HIGH = true; NOTIFIED_LOW = false; - notify = true; + notify = (MPU != -1); } if (notify) { Log.d(Constants.LOG.NTAG, Constants.LOG.NOTIFYING); @@ -86,7 +95,8 @@ void putNotification() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { int importance = android.app.NotificationManager.IMPORTANCE_HIGH; assert notificationManager != null; - NotificationChannel mChannel = notificationManager.getNotificationChannel(Constants.CHANNEL_ID); + NotificationChannel mChannel = notificationManager + .getNotificationChannel(Constants.CHANNEL_ID); if (mChannel == null) { mChannel = new NotificationChannel(Constants.CHANNEL_ID, name, importance); mChannel.setDescription(description); @@ -95,14 +105,16 @@ void putNotification() { notificationManager.createNotificationChannel(mChannel); } - notification = new Notification.Builder(BitCoinApp.getAppContext(), Constants.CHANNEL_ID) + notification = new Notification.Builder(BitCoinApp.getAppContext(), + Constants.CHANNEL_ID) .setSmallIcon(R.drawable.ic_stat_equalizer) .setCategory(Notification.CATEGORY_MESSAGE) .setContentTitle(notificationTitle) .setContentText(notificationText) .setAutoCancel(true) .setTicker(notificationTitle) - .setStyle(new Notification.BigTextStyle().bigText(notificationTextLong)); + .setStyle(new Notification.BigTextStyle() + .bigText(notificationTextLong)); } else { notification = new Notification.Builder(BitCoinApp.getAppContext()) .setSmallIcon(R.drawable.ic_stat_equalizer) @@ -111,7 +123,8 @@ void putNotification() { .setContentText(notificationText) .setAutoCancel(true) .setTicker(notificationTitle) - .setStyle(new Notification.BigTextStyle().bigText(notificationTextLong)); + .setStyle(new Notification.BigTextStyle() + .bigText(notificationTextLong)); } notification.setContentIntent(clickIntent); assert notificationManager != null; @@ -121,6 +134,7 @@ void putNotification() { } } + @NonNull static NotificationHandler newInstance() { return new NotificationHandler(); } @@ -129,9 +143,11 @@ private static float initMPU() { net market = new net(); market.execute("https://api.blockchain.info/stats"); try { - return MainActivity.round((float) market.get().getDouble("market_price_usd"), 2); - } catch (InterruptedException | ExecutionException | JSONException e) { - return 0; + return MainActivity.round((float) market.get().getDouble("market_price_usd"), + 2); + } catch (InterruptedException | ExecutionException + | JSONException | NullPointerException e) { + return -1; } } @@ -140,9 +156,12 @@ void updatePreferences() { } private static void updateSharedPreferences() { - final SharedPreferences.Editor sharedPreferencesEditor = BitCoinApp.getSharedPreferences().edit(); - sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES.NOTIFIED_HIGH, NOTIFIED_HIGH); - sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES.NOTIFIED_LOW, NOTIFIED_LOW); + final SharedPreferences.Editor sharedPreferencesEditor = BitCoinApp + .getSharedPreferences().edit(); + sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES.NOTIFIED_HIGH, + NOTIFIED_HIGH); + sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES.NOTIFIED_LOW, + NOTIFIED_LOW); sharedPreferencesEditor.apply(); } } diff --git a/app/src/main/java/javinator9889/bitcoinpools/BitCoinApp.java b/app/src/main/java/javinator9889/bitcoinpools/BitCoinApp.java index 5c080c9..3311bb4 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/BitCoinApp.java +++ b/app/src/main/java/javinator9889/bitcoinpools/BitCoinApp.java @@ -11,12 +11,27 @@ import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.support.annotation.NonNull; import android.util.Log; import com.crashlytics.android.Crashlytics; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +import javinator9889.bitcoinpools.BackgroundJobs.CacheJobSchedulerService; import javinator9889.bitcoinpools.BackgroundJobs.JobSchedulerService; +import static javinator9889.bitcoinpools.Constants.MILLIS_A_DAY; + /** * Created by Javinator9889 on 22/01/2018. * Based on: https://github.com/ZonaRMR/SimpleForFacebook/blob/master/app/src/main/java/com/creativetrends/simple/app/activities/SimpleApp.java @@ -26,6 +41,7 @@ public class BitCoinApp extends Application { @SuppressLint("StaticFieldLeak") private static Context APPLICATION_CONTEXT; private static SharedPreferences SHARED_PREFERENCES; + private static boolean isCacheCreated = false; public static Context getAppContext() { return APPLICATION_CONTEXT; @@ -38,16 +54,27 @@ public static SharedPreferences getSharedPreferences() { @Override public void onCreate() { APPLICATION_CONTEXT = getApplicationContext(); - SHARED_PREFERENCES = getSharedPreferences(Constants.SHARED_PREFERENCES.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); + SHARED_PREFERENCES = getSharedPreferences( + Constants.SHARED_PREFERENCES.SHARED_PREFERENCES_KEY, + Context.MODE_PRIVATE); initSharedPreferences(); + try { + isCacheCreated = CacheManaging.newInstance(this).setupFile(); + } catch (IOException e) { + isCacheCreated = false; + } startBackgroundJobs(); super.onCreate(); Log.d(Constants.LOG.BCTAG, Constants.LOG.CREATED_APP); } private static void startBackgroundJobs() { - JobScheduler mJobScheduler = (JobScheduler) APPLICATION_CONTEXT.getSystemService(Context.JOB_SCHEDULER_SERVICE); - JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(APPLICATION_CONTEXT.getPackageName(), JobSchedulerService.class.getName())); + JobScheduler mJobScheduler = (JobScheduler) APPLICATION_CONTEXT + .getSystemService(Context.JOB_SCHEDULER_SERVICE); + JobInfo.Builder builder = new JobInfo.Builder( + 1, + new ComponentName(APPLICATION_CONTEXT.getPackageName(), + JobSchedulerService.class.getName())); builder.setPeriodic(Constants.SCHEDULING_TIME); builder.setPersisted(Constants.PERSISTED); @@ -56,47 +83,116 @@ private static void startBackgroundJobs() { assert mJobScheduler != null; if (mJobScheduler.schedule(builder.build()) == JobScheduler.RESULT_FAILURE) { - Log.e(Constants.LOG.BCTAG, Constants.LOG.NO_INIT + "JobScheduler" + mJobScheduler.getAllPendingJobs().toString()); + Log.e(Constants.LOG.BCTAG, + Constants.LOG.NO_INIT + "JobScheduler" + mJobScheduler.getAllPendingJobs() + .toString()); + } + + if (isJobCreationNeeded(mJobScheduler)) { + JobInfo.Builder cacheBuilder = new JobInfo.Builder( + 2, + new ComponentName(APPLICATION_CONTEXT.getPackageName(), + CacheJobSchedulerService.class.getName())); + + cacheBuilder.setPeriodic(TimeUnit.DAYS.toMillis(1)); + cacheBuilder.setPersisted(Constants.PERSISTED); + cacheBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); + cacheBuilder.setBackoffCriteria(Constants.BACKOFF_CRITERIA, + JobInfo.BACKOFF_POLICY_LINEAR); + + if (mJobScheduler.schedule(cacheBuilder.build()) == JobScheduler.RESULT_FAILURE) { + Log.e(Constants.LOG.BCTAG, + Constants.LOG.NO_INIT + "JobScheduler" + mJobScheduler + .getAllPendingJobs().toString()); + } else { + SharedPreferences.Editor newValueForStartedJob = SHARED_PREFERENCES.edit(); + newValueForStartedJob.putBoolean(Constants.SHARED_PREFERENCES.CACHE_JOB, true); + newValueForStartedJob.apply(); + } } } private void initSharedPreferences() { - if (!SHARED_PREFERENCES.contains(Constants.SHARED_PREFERENCES.SHARED_PREFERENCES_INITIALIZED)) { + if (!SHARED_PREFERENCES.contains( + Constants.SHARED_PREFERENCES.SHARED_PREFERENCES_INITIALIZED)) + { Log.d(Constants.LOG.BCTAG, Constants.LOG.INIT_PREF); SharedPreferences.Editor sharedPreferencesEditor = SHARED_PREFERENCES.edit(); - sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES.SHARED_PREFERENCES_INITIALIZED, true); - sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES.NOTIFICATIONS_ENABLED, false); - sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES.NOTIFIED_LOW, false); - sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES.NOTIFIED_HIGH, false); - sharedPreferencesEditor.putInt(Constants.SHARED_PREFERENCES.DAYS_TO_CHECK, 1); - sharedPreferencesEditor.putInt(Constants.SHARED_PREFERENCES.VALUE_TO_CHECK, 1000); - sharedPreferencesEditor.putString(Constants.SHARED_PREFERENCES.APP_VERSION, appVersion()); + sharedPreferencesEditor.putBoolean( + Constants.SHARED_PREFERENCES.SHARED_PREFERENCES_INITIALIZED, true); + sharedPreferencesEditor.putBoolean( + Constants.SHARED_PREFERENCES.NOTIFICATIONS_ENABLED, false); + sharedPreferencesEditor.putBoolean( + Constants.SHARED_PREFERENCES.NOTIFIED_LOW, false); + sharedPreferencesEditor.putBoolean( + Constants.SHARED_PREFERENCES.NOTIFIED_HIGH, false); + sharedPreferencesEditor.putInt( + Constants.SHARED_PREFERENCES.DAYS_TO_CHECK, 1); + sharedPreferencesEditor.putInt( + Constants.SHARED_PREFERENCES.VALUE_TO_CHECK, 1000); + sharedPreferencesEditor.putString( + Constants.SHARED_PREFERENCES.APP_VERSION, appVersion()); sharedPreferencesEditor.apply(); } } public static void forceRestartBackgroundJobs() { Log.d(Constants.LOG.BCTAG, Constants.LOG.RESTART_JOB); - JobScheduler jobScheduler = (JobScheduler) APPLICATION_CONTEXT.getSystemService(Context.JOB_SCHEDULER_SERVICE); + JobScheduler jobScheduler = (JobScheduler) APPLICATION_CONTEXT + .getSystemService(Context.JOB_SCHEDULER_SERVICE); assert jobScheduler != null; jobScheduler.cancelAll(); startBackgroundJobs(); } public static boolean isOnline() { - ConnectivityManager connectionManager = (ConnectivityManager) getAppContext().getSystemService(Context.CONNECTIVITY_SERVICE); + ConnectivityManager connectionManager = (ConnectivityManager) getAppContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); assert connectionManager != null; NetworkInfo netInfo = connectionManager.getActiveNetworkInfo(); - return ((netInfo != null) && netInfo.isConnectedOrConnecting()); + return ((netInfo != null) && netInfo.isConnected()); } public static String appVersion() { try { - PackageInfo pInfo = getAppContext().getPackageManager().getPackageInfo(getAppContext().getPackageName(), 0); + PackageInfo pInfo = getAppContext().getPackageManager() + .getPackageInfo(getAppContext().getPackageName(), 0); return pInfo.versionName; } catch (PackageManager.NameNotFoundException e) { Crashlytics.logException(e); return "1.0"; } } + + public static boolean isJobCreationNeeded(JobScheduler applicationJobScheduler) { + if (SHARED_PREFERENCES.contains(Constants.SHARED_PREFERENCES.CACHE_JOB)) { + List pendingJobs = applicationJobScheduler.getAllPendingJobs(); + Iterator currentPendingJob = pendingJobs.iterator(); + boolean equals = false; + while (currentPendingJob.hasNext() && !equals) { + JobInfo pending = currentPendingJob.next(); + equals = pending.toString().equals(Constants.JOBINFO); + } + long timeDiff = timeDifference(); + return timeDiff >= MILLIS_A_DAY && !equals; + } else { + SharedPreferences.Editor newEntry = SHARED_PREFERENCES.edit(); + newEntry.putBoolean(Constants.SHARED_PREFERENCES.CACHE_JOB, false); + newEntry.apply(); + return true; + } + } + + private static long timeDifference() { + CacheManaging cache = CacheManaging.newInstance(APPLICATION_CONTEXT); + try { + HashMap cachedValues = cache.readCache(); + String date = cachedValues.get("date"); + DateFormat format = new SimpleDateFormat("dd-MM-yyyy HH:mm", Locale.US); + Date dateInCache = format.parse(date); + return (Calendar.getInstance().getTime().getTime() - dateInCache.getTime()); + } catch (Exception e) { + return Long.MAX_VALUE; + } + } } diff --git a/app/src/main/java/javinator9889/bitcoinpools/CacheManaging.java b/app/src/main/java/javinator9889/bitcoinpools/CacheManaging.java new file mode 100644 index 0000000..0795471 --- /dev/null +++ b/app/src/main/java/javinator9889/bitcoinpools/CacheManaging.java @@ -0,0 +1,56 @@ +package javinator9889.bitcoinpools; + +import android.content.Context; +import android.support.annotation.NonNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.HashMap; + +/** + * Created by Javinator9889 on 02/03/2018. + * Created for managing cache and writing data + */ + +public class CacheManaging { + private String filename; + + private CacheManaging(Context applicationContext) { + this.filename = applicationContext.getCacheDir().getPath() + File.separator + "DataCache"; + } + + @NonNull + public static CacheManaging newInstance(Context applicationContext) { + return new CacheManaging(applicationContext); + } + + public boolean setupFile() throws IOException { + File cacheFile = new File(filename); + return cacheFile.createNewFile(); + } + + public void writeCache(HashMap objectData) throws IOException { + File cacheDir = new File(filename); + ObjectOutputStream outputFile = new ObjectOutputStream(new FileOutputStream(cacheDir)); + outputFile.writeObject(objectData); + outputFile.flush(); + outputFile.close(); + } + + @SuppressWarnings("unchecked") + public HashMap readCache() throws IOException, ClassNotFoundException { + try { + File cacheDir = new File(filename); + ObjectInputStream inputFile = new ObjectInputStream(new FileInputStream(cacheDir)); + Object readData = inputFile.readObject(); + inputFile.close(); + return (HashMap) readData; + } catch (Exception e) { + return null; + } + } +} diff --git a/app/src/main/java/javinator9889/bitcoinpools/Constants.java b/app/src/main/java/javinator9889/bitcoinpools/Constants.java index d6bb0c2..cc344da 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/Constants.java +++ b/app/src/main/java/javinator9889/bitcoinpools/Constants.java @@ -12,7 +12,9 @@ public class Constants { public static final long SCHEDULING_TIME = TimeUnit.HOURS.toMillis(1); public static final long BACKOFF_CRITERIA = TimeUnit.SECONDS.toMillis(30); public static final int JOB_ID = 1; - public static final int REQUEST_INVITE = 0; + public static final String JOBINFO = "(job:2/javinator9889.bitcoinpools/.BackgroundJobs.CacheJobSchedulerService)"; + public static final long MILLIS_A_DAY = 86400000; + public static class PAYMENTS { public static final String GOOGLE_PUBKEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgt71diQE3dVAJ/KEJSpt+ZIEeeKDOWl5cBdwRirkjiVPtzwbOlOyJsf+tJzQrYvxJejmkfdwR5TlG4Z+NAZDtcS4mq63JVoPyEbmx0wvVYC3+zav2MbJO9P/gSmwTK0KGwVSyItcH5sqXjK9Mv280uj2jM0IMW0UpM91vzeitCGCbJRwMe1CnzLzFPFI01YJ/QjG+1KY7MzIhn3P2ZbS9C7fhP0BwJIBPoZJkp64pKhXf7iI5qsbZGby4V+iQiU5ONiS+ggy8X076IAB1DijL90BUnbTXCwa1WufChb3da7xV/AiPEHl9UJ2J70I3+/1Dx9MXOrYkBmOKAYFLJlcQwIDAQAB"; public static final String[] GOOGLE_CATALOG = new String[]{"ntpsync.donation.1", @@ -20,6 +22,8 @@ public static class PAYMENTS { "ntpsync.donation.13"}; public static final String PAYPALME = "https://paypal.me/Javinator9889"; } + public static final String GOOGLE_PLAY_URL = "https://play.google.com/store/apps/details?id=javinator9889.bitcoinpools"; + public static final String API_URL = "https://api.coindesk.com/v1/bpi/historical/close.json"; public static final class SHARED_PREFERENCES { public static final String SHARED_PREFERENCES_KEY = "javinator9889.bitcoinpools.usrPreferences"; public static final String NOTIFICATIONS_ENABLED = "notifications_enabled"; @@ -29,6 +33,7 @@ public static final class SHARED_PREFERENCES { public static final String VALUE_TO_CHECK = "value_to_check"; public static final String SHARED_PREFERENCES_INITIALIZED = "initialized"; public static final String APP_VERSION = "APP_VERSION"; + public static final String CACHE_JOB = "CACHE_JOB"; } public static final String CHANNEL_ID = "javinator9889.bitcoinpools.Alerts"; public static final int NOTIFICATION_ID = 1; @@ -38,7 +43,7 @@ public static final class SHARED_PREFERENCES { public static final String STATS_URL = "https://api.blockchain.info/stats"; public static final String MARKET_NAME = "market_price_usd"; public static final String POOLS_URL = "https://api.blockchain.info/pools?timespan="; - public static final String GITHUB_URL = "https://github.com/Javinator9889/BitCoinPools/releases#latest"; + public static final class LOG { public static final String UNCAUGHT_ERROR = "Uncaught error on: "; @@ -56,7 +61,6 @@ public static final class LOG { public static final String CREATING_CHART = "Creating application chart"; public static final String INIT_VALUES = "Initialising application values"; public static final String LISTENING = "Listening to buttons"; - public static final String CHECKING_PERMISSIONS = "Checking for required permissions"; public static final String LOADING_MPU = "Loading MPU in a new thread..."; public static final String LOADING_RD = "Loading data in a new thread..."; public static final String LOADING_CHART = "Loading PieChart in a new thread..."; diff --git a/app/src/main/java/javinator9889/bitcoinpools/DataLoaderScreen.java b/app/src/main/java/javinator9889/bitcoinpools/DataLoaderScreen.java new file mode 100644 index 0000000..7881bfb --- /dev/null +++ b/app/src/main/java/javinator9889/bitcoinpools/DataLoaderScreen.java @@ -0,0 +1,241 @@ +package javinator9889.bitcoinpools; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; + +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.Date; +import java.util.HashMap; + +import javax.net.ssl.HttpsURLConnection; + +import javinator9889.bitcoinpools.JSONTools.JSONTools; + +import static javinator9889.bitcoinpools.MainActivity.round; + +/** + * Created by Javinator9889 on 04/03/2018. + * Based on: https://stackoverflow.com/questions/10115403/progressdialog-while-load-activity + */ + +public class DataLoaderScreen extends AppCompatActivity { + public static AppCompatActivity dataLoaderScreenActivity; + + private float mpu; + private HashMap retrievedData; + private HashMap cardsData; + private HashMap btcPrice; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + dataLoaderScreenActivity = this; + setContentView(R.layout.activity_loading); + if (BitCoinApp.isOnline()) { + Log.d(Constants.LOG.MATAG, Constants.LOG.CREATING_MAINVIEW); + new DataLoader().execute(); + } else { + new MaterialDialog.Builder(this) + .title(R.string.noConnectionTitle) + .content(R.string.noConnectionDesc) + .cancelable(false) + .positiveText(R.string.accept) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, + @NonNull DialogAction which) { + onBackPressed(); + } + }) + .build() + .show(); + } + } + + @SuppressLint("StaticFieldLeak") + class DataLoader extends AsyncTask { + private Thread marketPriceThread; + private Thread poolsDataThread; + private Thread cardsDataThread; + private Thread btcPriceThread; + private Thread.UncaughtExceptionHandler threadExceptions = new Thread + .UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + isAnyExceptionThrown = true; + Log.e("DataLoaderScreen", "Exception on thread: " + t.getName() + + " | Message: " + e.getMessage()); + } + }; + private boolean isAnyExceptionThrown = false; + + @Override + protected void onPreExecute() { + } + + @Override + protected void onPostExecute(Boolean result) { + if (result && !isAnyExceptionThrown) { + Intent activityMainIntent = new Intent(DataLoaderScreen.this, + MainActivity.class); + activityMainIntent.putExtra("MPU", mpu); + activityMainIntent.putExtra("RD", retrievedData); + activityMainIntent.putExtra("CARDS", cardsData); + activityMainIntent.putExtra("BTCPRICE", btcPrice); + startActivity(activityMainIntent); + overridePendingTransition(R.anim.activity_in, R.anim.activity_out); + } else { + new MaterialDialog.Builder(DataLoaderScreen.this) + .positiveText(R.string.accept) + .cancelable(false) + .title(R.string.errorLoading) + .content(R.string.errorLoadingDescription, + true) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, + @NonNull DialogAction which) { + DataLoaderScreen.this.onBackPressed(); + } + }) + .build().show(); + } + } + + @Override + protected Boolean doInBackground(@Nullable Void... params) { + try { + getBitCoinMarketPrice(); + getPoolsData(); + getCardsData(); + getBitCoinPriceHistory(); + marketPriceThread.join(); + poolsDataThread.join(); + cardsDataThread.join(); + btcPriceThread.join(); + return true; + } catch (InterruptedException | DataLoaderException e) { + Log.e(Constants.LOG.MATAG, Constants.LOG.JOIN_ERROR + e.getMessage()); + return false; + } + } + + private JSONObject getHTTPSRequest(String requestUrl) throws Exception { + StringBuilder response = new StringBuilder(); + URL urlObject = new URL(requestUrl); + HttpsURLConnection connection = (HttpsURLConnection) urlObject.openConnection(); + connection.setRequestMethod("GET"); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(connection.getInputStream())); + + String line; + while ((line = bufferedReader.readLine()) != null) { + response.append(line); + } + bufferedReader.close(); + return new JSONObject(response.toString()); + } + + private void getBitCoinMarketPrice() throws DataLoaderException { + marketPriceThread = new Thread(new Runnable() { + @Override + public void run() { + Log.d(Constants.LOG.MATAG, Constants.LOG.LOADING_MPU); + try { + mpu = round((float) getHTTPSRequest(Constants.STATS_URL) + .getDouble(Constants.MARKET_NAME), 2); + } catch (Exception e) { + Log.e(Constants.LOG.MATAG, + Constants.LOG.MARKET_PRICE_ERROR + e.getMessage()); + mpu = -1; + throw new DataLoaderException( + "Failed to get data from: " + Constants.STATS_URL); + } + } + }); + marketPriceThread.setUncaughtExceptionHandler(threadExceptions); + marketPriceThread.setName("MarketPriceThread"); + marketPriceThread.start(); + } + + private void getPoolsData() { + poolsDataThread = new Thread(new Runnable() { + @Override + public void run() { + int days = BitCoinApp.getSharedPreferences() + .getInt(Constants.SHARED_PREFERENCES.DAYS_TO_CHECK, 1); + Log.d(Constants.LOG.MATAG, Constants.LOG.LOADING_RD); + String url = Constants.POOLS_URL + days + "days"; + try { + retrievedData = JSONTools.convert2HashMap(getHTTPSRequest(url)); + } catch (Exception e) { + retrievedData = null; + Log.e(Constants.LOG.MATAG, Constants.LOG.DATA_ERROR + e.getMessage()); + throw new DataLoaderException("Unable to get data from: " + url); + } + } + }); + poolsDataThread.setUncaughtExceptionHandler(threadExceptions); + poolsDataThread.setName("PoolsDataThread"); + poolsDataThread.start(); + } + + private void getCardsData() { + cardsDataThread = new Thread(new Runnable() { + @Override + public void run() { + try { + cardsData = JSONTools.convert2HashMap(getHTTPSRequest(Constants.STATS_URL)); + } catch (Exception e) { + cardsData = null; + Log.e(Constants.LOG.MATAG, Constants.LOG.DATA_ERROR + e.getMessage()); + throw new DataLoaderException( + "Unable to get data from: " + Constants.STATS_URL); + } + } + }); + cardsDataThread.setUncaughtExceptionHandler(threadExceptions); + cardsDataThread.setName("CardsDataThread"); + cardsDataThread.start(); + } + + private void getBitCoinPriceHistory() { + btcPriceThread = new Thread(new Runnable() { + @Override + public void run() { + try { + btcPrice = JSONTools.convert2DateHashMap(getHTTPSRequest(Constants.API_URL) + .getJSONObject("bpi")); + } catch (Exception e) { + btcPrice = null; + Log.e(Constants.LOG.MATAG, Constants.LOG.DATA_ERROR + e.getMessage()); + throw new DataLoaderException( + "Unable to get data from: " + Constants.API_URL); + } + } + }); + btcPriceThread.setUncaughtExceptionHandler(threadExceptions); + btcPriceThread.setName("BtcPriceThread"); + btcPriceThread.start(); + } + } + + class DataLoaderException extends RuntimeException { + DataLoaderException(@NonNull String message) { + super(message); + } + } +} diff --git a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CardsAdapter.java b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CardsAdapter.java index 73d6810..6788009 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CardsAdapter.java +++ b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CardsAdapter.java @@ -2,6 +2,8 @@ import android.annotation.SuppressLint; import android.content.Context; +import android.graphics.Color; +import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; @@ -10,7 +12,10 @@ import com.afollestad.materialdialogs.MaterialDialog; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.List; +import java.util.Locale; import javinator9889.bitcoinpools.R; @@ -24,13 +29,14 @@ public class CardsAdapter extends RecyclerView.Adapter btcData; public class MyViewHolder extends RecyclerView.ViewHolder { - TextView title, body; + TextView title, body, oldData; View v; MyViewHolder(View view) { super(view); title = view.findViewById(R.id.title_text); body = view.findViewById(R.id.body_text); + oldData = view.findViewById(R.id.balance); this.v = view; } } @@ -40,91 +46,351 @@ public class MyViewHolder extends RecyclerView.ViewHolder { this.btcData = btcData; } + @NonNull @Override - public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.bitcoin_card, parent, false); + public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.bitcoin_card, + parent, false); return new MyViewHolder(itemView); } @Override - public void onBindViewHolder(final MyViewHolder holder, @SuppressLint("RecyclerView") final int position) { + public void onBindViewHolder(@NonNull final MyViewHolder holder, + @SuppressLint("RecyclerView") final int position) { final CardsContent content = btcData.get(position); holder.title.setText(content.getTitle()); holder.body.setText(content.getBody()); - holder.v.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - switch (position) { - case 0: - new MaterialDialog.Builder(context) - .title(context.getString(R.string.market_price)) - .content(R.string.market_price_desc, true) - .cancelable(true) - .positiveText(R.string.accept) - .build().show(); - break; - case 1: - new MaterialDialog.Builder(context) - .title(context.getString(R.string.hash_rate)) - .content(R.string.hash_rate_desc, true) - .cancelable(true) - .positiveText(R.string.accept) - .build().show(); - break; - case 2: - new MaterialDialog.Builder(context) - .title(context.getString(R.string.difficulty)) - .content(R.string.difficulty_desc, true) - .cancelable(true) - .positiveText(R.string.accept) - .build().show(); - break; - case 3: - new MaterialDialog.Builder(context) - .title(context.getString(R.string.min_blocks)) - .content(R.string.min_blocks_desc, true) - .cancelable(true) - .positiveText(R.string.accept) - .build().show(); - break; - case 4: - new MaterialDialog.Builder(context) - .title(context.getString(R.string.minutes_blocks)) - .content(R.string.minutes_blocks_desc, true) - .cancelable(true) - .positiveText(R.string.accept) - .build().show(); - break; - case 5: - new MaterialDialog.Builder(context) - .title(context.getString(R.string.total_fees)) - .content(R.string.total_fees_desc, true) - .cancelable(true) - .positiveText(R.string.accept) - .build().show(); - break; - case 6: - new MaterialDialog.Builder(context) - .title(context.getString(R.string.total_trans)) - .content(R.string.total_trans_desc, true) - .cancelable(true) - .positiveText(R.string.accept) - .build().show(); - break; - case 7: - new MaterialDialog.Builder(context) - .title(context.getString(R.string.min_benefit)) - .content(R.string.min_benefit_desc, true) - .cancelable(true) - .positiveText(R.string.accept) - .build().show(); - break; - default: - break; - } - return false; + DecimalFormat decimalFormat = new DecimalFormat("#.##", + new DecimalFormatSymbols(Locale.US)); + if (content.getOldData() == null) + holder.oldData.setText(""); + else { + switch (position) { + case 0: + float newPrice = Float.parseFloat( + content.getBody().replaceAll("[^\\d.]", "")); + float oldPrice = Float.parseFloat( + decimalFormat.format(Float.parseFloat(content.getOldData()))); + float pricePercentage = percentageCalculator(newPrice, oldPrice); + switch (Float.compare(pricePercentage, 0f)) { + case 0: + holder.oldData.setText("0%"); + break; + case 1: + String positiveText = "+" + decimalFormat.format(pricePercentage) + "%"; + holder.oldData.setText(positiveText); + holder.oldData.setTextColor(Color.GREEN); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_green_arrow_up_darker, 0, 0, 0); + break; + case -1: + String negativeText = decimalFormat.format(pricePercentage) + "%"; + holder.oldData.setText(negativeText); + holder.oldData.setTextColor(Color.RED); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_red_arrow_down, 0, 0, 0); + break; + } + break; + case 1: + float newPower = Float.parseFloat( + content.getBody().replaceAll("[^\\d.]", "")); + float oldPower = Float.parseFloat( + decimalFormat.format(Float.parseFloat(content.getOldData()))); + float powerPercentage = percentageCalculator(newPower, oldPower); + switch (Float.compare(powerPercentage, 0f)) { + case 0: + holder.oldData.setText("0%"); + break; + case 1: + String positiveText = "+" + decimalFormat.format(powerPercentage) + "%"; + holder.oldData.setText(positiveText); + holder.oldData.setTextColor(Color.GREEN); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_green_arrow_up_darker, 0, 0, 0); + break; + case -1: + String negativeText = decimalFormat.format(powerPercentage) + "%"; + holder.oldData.setText(negativeText); + holder.oldData.setTextColor(Color.RED); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_red_arrow_down, 0, 0, 0); + break; + } + break; + case 2: + float newDifficulty = Float.parseFloat( + content.getBody().replaceAll("[^\\d.]", "")); + float oldDifficulty = Float.parseFloat( + decimalFormat.format(Float.parseFloat(content.getOldData()))); + float difficultyPercentage; + float result = percentageCalculator(newDifficulty, oldDifficulty); + if (result == 0) + difficultyPercentage = 0; + else + difficultyPercentage = - result; + switch (Float.compare(difficultyPercentage, 0f)) { + case 0: + holder.oldData.setText("0%"); + break; + case 1: + String positiveText = "+" + decimalFormat + .format(difficultyPercentage) + "%"; + holder.oldData.setText(positiveText); + holder.oldData.setTextColor(Color.GREEN); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_green_arrow_up_darker, 0, 0, 0); + break; + case -1: + String negativeText = decimalFormat + .format(- difficultyPercentage) + "%"; + holder.oldData.setText(negativeText); + holder.oldData.setTextColor(Color.RED); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_red_arrow_down, 0, 0, 0); + break; + } + break; + case 3: + float newBlock = Float.parseFloat( + content.getBody().replaceAll("[^\\d.]", "")); + float oldBlock = Float.parseFloat( + decimalFormat.format(Float.parseFloat( + content.getOldData()) / 10)); + float blockPercentage = percentageCalculator(newBlock, oldBlock); + switch (Float.compare(blockPercentage, 0f)) { + case 0: + holder.oldData.setText("0%"); + break; + case 1: + String positiveText = "+" + decimalFormat.format(blockPercentage) + "%"; + holder.oldData.setText(positiveText); + holder.oldData.setTextColor(Color.GREEN); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_green_arrow_up_darker, 0, 0, 0); + break; + case -1: + String negativeText = decimalFormat.format(blockPercentage) + "%"; + holder.oldData.setText(negativeText); + holder.oldData.setTextColor(Color.RED); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_red_arrow_down, 0, 0, 0); + break; + } + break; + case 4: + float newMinutes = Float.parseFloat( + content.getBody().replaceAll("[^\\d.]", "")); + float oldMinutes = Float.parseFloat( + decimalFormat.format(Float.parseFloat(content.getOldData()))); + float minutesPercentage; + float resultMinutes = percentageCalculator(newMinutes, oldMinutes); + if (resultMinutes == 0) + minutesPercentage = 0; + else + minutesPercentage = - resultMinutes; + switch (Float.compare(minutesPercentage, 0f)) { + case 0: + holder.oldData.setText("0%"); + break; + case 1: + String positiveText = "+" + decimalFormat + .format(minutesPercentage) + "%"; + holder.oldData.setText(positiveText); + holder.oldData.setTextColor(Color.GREEN); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_green_arrow_up_darker, 0, 0, 0); + break; + case -1: + String negativeText = decimalFormat.format(minutesPercentage) + "%"; + holder.oldData.setText(negativeText); + holder.oldData.setTextColor(Color.RED); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_red_arrow_down, 0, 0, 0); + break; + } + break; + case 5: + float newBtcFees = Float.parseFloat( + content.getBody().replaceAll("[^\\d.]", "")); + float oldBtcFees = Float.parseFloat( + decimalFormat.format(Float.parseFloat( + content.getOldData()) / 10000000)); + float feePercentage; + float resultFees = percentageCalculator(newBtcFees, oldBtcFees); + if (resultFees == 0) + feePercentage = 0; + else + feePercentage = - resultFees; + switch (Float.compare(feePercentage, 0f)) { + case 0: + holder.oldData.setText("0%"); + break; + case 1: + String positiveText = "+" + decimalFormat.format(feePercentage) + "%"; + holder.oldData.setText(positiveText); + holder.oldData.setTextColor(Color.GREEN); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_green_arrow_up_darker, 0, 0, 0); + break; + case -1: + String negativeText = decimalFormat.format(feePercentage) + "%"; + holder.oldData.setText(negativeText); + holder.oldData.setTextColor(Color.RED); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_red_arrow_down, 0, 0, 0); + break; + } + break; + case 6: + float newTrans = Float.parseFloat( + content.getBody().replaceAll("[^\\d.]", "")); + float oldTrans = Float.parseFloat( + decimalFormat.format(Float.parseFloat(content.getOldData()))); + float transPercentage = percentageCalculator(newTrans, oldTrans); + switch (Float.compare(transPercentage, 0f)) { + case 0: + holder.oldData.setText("0%"); + break; + case 1: + String positiveText = "+" + decimalFormat.format(transPercentage) + "%"; + holder.oldData.setText(positiveText); + holder.oldData.setTextColor(Color.GREEN); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_green_arrow_up_darker, 0, 0, 0); + break; + case -1: + String negativeText = decimalFormat.format(transPercentage) + "%"; + holder.oldData.setText(negativeText); + holder.oldData.setTextColor(Color.RED); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_red_arrow_down, 0, 0, 0); + break; + } + break; + case 7: + float newBenefit = Float.parseFloat( + content.getBody().replaceAll("[^\\d.]", "")); + float oldBenefit = Float.parseFloat( + decimalFormat.format(Float.parseFloat( + content.getOldData()) / 100)); + float benefitPercentage = percentageCalculator(newBenefit, oldBenefit); + switch (Float.compare(benefitPercentage, 0f)) { + case 0: + holder.oldData.setText("0%"); + break; + case 1: + String positiveText = "+" + decimalFormat + .format(benefitPercentage) + "%"; + holder.oldData.setText(positiveText); + holder.oldData.setTextColor(Color.GREEN); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_green_arrow_up_darker, 0, 0, 0); + break; + case -1: + String negativeText = decimalFormat.format(benefitPercentage) + "%"; + holder.oldData.setText(negativeText); + holder.oldData.setTextColor(Color.RED); + holder.oldData.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_red_arrow_down, 0, 0, 0); + break; + } + break; + default: + holder.oldData.setText(content.getOldData()); } - }); + holder.v.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + switch (position) { + case 0: + new MaterialDialog.Builder(context) + .title(context.getString(R.string.market_price)) + .content(R.string.market_price_desc, true) + .cancelable(true) + .positiveText(R.string.accept) + .build().show(); + break; + case 1: + new MaterialDialog.Builder(context) + .title(context.getString(R.string.hash_rate)) + .content(R.string.hash_rate_desc, true) + .cancelable(true) + .positiveText(R.string.accept) + .build().show(); + break; + case 2: + new MaterialDialog.Builder(context) + .title(context.getString(R.string.difficulty)) + .content(R.string.difficulty_desc, true) + .cancelable(true) + .positiveText(R.string.accept) + .build().show(); + break; + case 3: + new MaterialDialog.Builder(context) + .title(context.getString(R.string.min_blocks)) + .content(R.string.min_blocks_desc, true) + .cancelable(true) + .positiveText(R.string.accept) + .build().show(); + break; + case 4: + new MaterialDialog.Builder(context) + .title(context.getString(R.string.minutes_blocks)) + .content(R.string.minutes_blocks_desc, true) + .cancelable(true) + .positiveText(R.string.accept) + .build().show(); + break; + case 5: + new MaterialDialog.Builder(context) + .title(context.getString(R.string.total_fees)) + .content(R.string.total_fees_desc, true) + .cancelable(true) + .positiveText(R.string.accept) + .build().show(); + break; + case 6: + new MaterialDialog.Builder(context) + .title(context.getString(R.string.total_trans)) + .content(R.string.total_trans_desc, true) + .cancelable(true) + .positiveText(R.string.accept) + .build().show(); + break; + case 7: + new MaterialDialog.Builder(context) + .title(context.getString(R.string.min_benefit)) + .content(R.string.min_benefit_desc, true) + .cancelable(true) + .positiveText(R.string.accept) + .build().show(); + break; + default: + break; + } + return false; + } + }); + } + } + + /** + * Based on: https://www.calculatorsoup.com/calculators/algebra/percent-difference-calculator.php + * @param newValue new value + * @param oldValue old value + * @return difference in percentage of values + */ + private float percentageCalculator(float newValue, float oldValue) { + if (newValue == oldValue) + return 0; + else { + float difference = newValue - oldValue; + float averageValues = (newValue + oldValue) / 2; + float diffDividedAverage = difference / averageValues; + return (diffDividedAverage * 100); + } } @Override diff --git a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CardsContent.java b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CardsContent.java index 5e89410..f1e6df0 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CardsContent.java +++ b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CardsContent.java @@ -1,5 +1,7 @@ package javinator9889.bitcoinpools.FragmentViews; +import android.support.annotation.Nullable; + /** * Created by Javinator9889 on 31/01/2018. * Simple class containing cards information @@ -8,10 +10,12 @@ public class CardsContent { private String title; private String body; + private String oldData; - public CardsContent(String title, String body) { + public CardsContent(String title, String body, @Nullable String oldData) { this.title = title; this.body = body; + this.oldData = oldData; } public String getBody() { @@ -21,4 +25,8 @@ public String getBody() { public String getTitle() { return title; } + + public String getOldData() { + return oldData; + } } diff --git a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CustomMarkerView.java b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CustomMarkerView.java index 4c493a1..602f7a0 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CustomMarkerView.java +++ b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/CustomMarkerView.java @@ -1,5 +1,6 @@ package javinator9889.bitcoinpools.FragmentViews; +import android.annotation.SuppressLint; import android.content.Context; import android.widget.TextView; @@ -17,6 +18,7 @@ * Based on: https://github.com/PhilJay/MPAndroidChart/blob/master/MPChartExample/src/com/xxmassdeveloper/mpchartexample/custom/MyMarkerView.java */ +@SuppressLint("ViewConstructor") public class CustomMarkerView extends MarkerView { private TextView tvContent; @@ -29,11 +31,13 @@ public CustomMarkerView(Context context, int layoutResource) { // callbacks everytime the MarkerView is redrawn, can be used to update the // content (user-interface) + @SuppressLint("SetTextI18n") @Override public void refreshContent(Entry e, Highlight highlight) { if (e instanceof CandleEntry) { CandleEntry ce = (CandleEntry) e; - tvContent.setText("" + Utils.formatNumber(ce.getHigh(), 0, true)); + tvContent.setText("" + Utils.formatNumber(ce.getHigh(), 0, + true)); } else { tvContent.setText("" + Utils.formatNumber(e.getY(), 0, true)); } diff --git a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/DonationsActivity.java b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/DonationsActivity.java index e0009ad..47e6621 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/DonationsActivity.java +++ b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/DonationsActivity.java @@ -1,15 +1,23 @@ package javinator9889.bitcoinpools.FragmentViews; +import android.app.Activity; import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; +import android.util.Log; import android.view.View; import android.widget.Button; +import com.afollestad.materialdialogs.MaterialDialog; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; + import org.sufficientlysecure.donations.BuildConfig; import org.sufficientlysecure.donations.DonationsFragment; @@ -25,23 +33,31 @@ */ public class DonationsActivity extends FragmentActivity { + private DonationsFragment donationsFragment = null; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.donations_activity); - FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); - DonationsFragment donationsFragment; + if ((isGooglePlayServicesAvailable(this)) && (Build.VERSION.SDK_INT > 22)) { + FragmentTransaction fragmentTransaction = getSupportFragmentManager() + .beginTransaction(); - donationsFragment = DonationsFragment.newInstance(BuildConfig.DEBUG, true, GOOGLE_PUBKEY, GOOGLE_CATALOG, - getResources().getStringArray(R.array.donation_google_catalog_values), false, null,null, - null, false, null, null, false, null); + this.donationsFragment = DonationsFragment.newInstance(BuildConfig.DEBUG, true, + GOOGLE_PUBKEY, GOOGLE_CATALOG, + getResources().getStringArray(R.array.donation_google_catalog_values), + false, null, null, + null, false, null, + null, false, null); - fragmentTransaction.replace(R.id.donations_activity_container, donationsFragment, "donationsFragment"); - fragmentTransaction.commit(); + fragmentTransaction.replace(R.id.donations_activity_container, this.donationsFragment, + "donationsFragment"); + fragmentTransaction.commit(); + } - Button paypalButton = findViewById(R.id.donations__paypal_modified_donate_button); - paypalButton.setOnClickListener(new View.OnClickListener() { + Button payPalButton = findViewById(R.id.donations__paypal_modified_donate_button); + payPalButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Uri uri = Uri.parse(Constants.PAYMENTS.PAYPALME); @@ -51,6 +67,40 @@ public void onClick(View v) { }); } + @Override + protected void onPostCreate(@Nullable Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + try { + Button googleDonationsButton = donationsFragment.getActivity() + .findViewById(R.id.donations__google_android_market_donate_button); + googleDonationsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + donationsFragment.donateGoogleOnClick(v); + } catch (IllegalStateException e) { + new MaterialDialog.Builder(DonationsActivity.this) + .title(R.string.donations__google_android_market_not_supported_title) + .content(R.string.donations__google_android_market_not_supported) + .cancelable(true) + .positiveText(R.string.accept) + .build().show(); + + } + } + }); + } catch (NullPointerException e) { + Log.e("DonationsActivity", "Unable to get button-fragment. Full trace: " + + e.getMessage()); + } + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + overridePendingTransition(R.anim.activity_back_in, R.anim.activity_back_out); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); @@ -61,4 +111,18 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { fragment.onActivityResult(requestCode, resultCode, data); } } + + public boolean isGooglePlayServicesAvailable(Activity activity) { + GoogleApiAvailability googleApiAvailabilityForPlayServices = GoogleApiAvailability + .getInstance(); + int status = googleApiAvailabilityForPlayServices.isGooglePlayServicesAvailable(activity); + if (status != ConnectionResult.SUCCESS) { + if (googleApiAvailabilityForPlayServices.isUserResolvableError(status)) { + googleApiAvailabilityForPlayServices.getErrorDialog(activity, status, 2404) + .show(); + } + return false; + } + return true; + } } diff --git a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/Tab1PoolsChart.java b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/Tab1PoolsChart.java index e0cf482..15320da 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/Tab1PoolsChart.java +++ b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/Tab1PoolsChart.java @@ -22,23 +22,16 @@ import com.github.mikephil.charting.data.PieEntry; import com.github.mikephil.charting.utils.ColorTemplate; -import org.json.JSONException; - import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutionException; -import javinator9889.bitcoinpools.BitCoinApp; import javinator9889.bitcoinpools.Constants; -import javinator9889.bitcoinpools.JSONTools.JSONTools; import javinator9889.bitcoinpools.MainActivity; -import javinator9889.bitcoinpools.NetTools.net; import javinator9889.bitcoinpools.R; -import static javinator9889.bitcoinpools.MainActivity.round; - /** * Created by Javinator9889 on 28/01/2018. * Creates view for main chart (pools chart) @@ -48,13 +41,22 @@ public class Tab1PoolsChart extends Fragment { private static Map RETRIEVED_DATA = new LinkedHashMap<>(); private static ViewGroup.LayoutParams TABLE_PARAMS; private static float MARKET_PRICE_USD; - private Thread rdThread; private Thread tableThread; - private Thread mpuThread; public Tab1PoolsChart() { } + @SuppressWarnings("unchecked") + public static Tab1PoolsChart newInstance(Object... params) { + Bundle args = new Bundle(); + args.putFloat("MPU", Float.valueOf(String.valueOf(params[0]))); + args.putSerializable("RD", (HashMap) params[1]); + Tab1PoolsChart fragment = new Tab1PoolsChart(); + fragment.setArguments(args); + return fragment; + } + + @SuppressWarnings("unchecked") @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -62,17 +64,14 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, final PieChart chart = createdView.findViewById(R.id.chart); final TableLayout tableLayout = createdView.findViewById(R.id.poolstable); + MARKET_PRICE_USD = getArguments().getFloat("MPU"); + RETRIEVED_DATA = (HashMap) getArguments().getSerializable("RD"); - initRD(); initT(createdView); - initMPU(); - createPieChart(chart); createTable(tableLayout, createdView); - try { tableThread.join(); - mpuThread.join(); return createdView; } catch (InterruptedException e) { e.printStackTrace(); @@ -102,12 +101,14 @@ private void createPieChart(final PieChart destinationChart) { public void run() { Log.d(Constants.LOG.MATAG, Constants.LOG.LOADING_CHART); List values = new ArrayList<>(); - List> entryList = new ArrayList<>(RETRIEVED_DATA.entrySet()); + List> entryList = new ArrayList<>( + RETRIEVED_DATA.entrySet()); Map.Entry getEntry; int count = 0; for (int i = entryList.size() - 1; (i >= 0) && (count < 10); --i) { getEntry = entryList.get(i); - Log.i(Constants.LOG.MATAG, "Accessing at: " + i + " | Key: " + getEntry.getKey() + " | Value: " + getEntry.getValue()); + Log.i(Constants.LOG.MATAG, "Accessing at: " + i + " | Key: " + + getEntry.getKey() + " | Value: " + getEntry.getValue()); values.add(new PieEntry(getEntry.getValue(), getEntry.getKey())); ++count; } @@ -133,7 +134,8 @@ private void createTable(final TableLayout destinationTable, final View view) { tableThread = new Thread() { public void run() { Log.d(Constants.LOG.MATAG, Constants.LOG.LOADING_TABLE); - List> entryList = new ArrayList<>(RETRIEVED_DATA.entrySet()); + List> entryList = new ArrayList<>( + RETRIEVED_DATA.entrySet()); Map.Entry getEntry; int count = 1; @@ -178,56 +180,12 @@ public void run() { destinationTable.invalidate(); } }; - try { - rdThread.join(); - } catch (InterruptedException e) { - Log.e(Constants.LOG.MATAG, Constants.LOG.JOIN_ERROR + rdThread.getName()); - } finally { - tableThread.setName("table_thread"); - tableThread.start(); - } - } - - private void initRD() { - rdThread = new Thread() { - public void run() { - int days = BitCoinApp.getSharedPreferences().getInt(Constants.SHARED_PREFERENCES.DAYS_TO_CHECK, 1); - Log.d(Constants.LOG.MATAG, Constants.LOG.LOADING_RD); - String url = Constants.POOLS_URL + days + "days"; - net httpsResponse = new net(); - httpsResponse.execute(url); - try { - RETRIEVED_DATA = JSONTools.sortByValue(JSONTools.convert2HashMap(httpsResponse.get())); - } catch (InterruptedException | ExecutionException e) { - RETRIEVED_DATA = null; - Log.e(Constants.LOG.MATAG, Constants.LOG.DATA_ERROR + e.getMessage()); - } - } - }; - rdThread.setName("rd_thread"); - rdThread.start(); + tableThread.setName("table_thread"); + tableThread.start(); } private void initT(View view) { TableRow masterRow = view.findViewById(R.id.masterRow); TABLE_PARAMS = masterRow.getLayoutParams(); } - - private void initMPU() { - mpuThread = new Thread() { - public void run() { - Log.d(Constants.LOG.MATAG, Constants.LOG.LOADING_MPU); - net market = new net(); - market.execute(Constants.STATS_URL); - try { - MARKET_PRICE_USD = round((float) market.get().getDouble(Constants.MARKET_NAME), 2); - } catch (InterruptedException | ExecutionException | JSONException e) { - Log.e(Constants.LOG.MATAG, Constants.LOG.MARKET_PRICE_ERROR + e.getMessage()); - MARKET_PRICE_USD = 0; - } - } - }; - mpuThread.setName("mpu_thread"); - mpuThread.start(); - } } diff --git a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/Tab2BTCChart.java b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/Tab2BTCChart.java index e4200ad..9bcd8bb 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/Tab2BTCChart.java +++ b/app/src/main/java/javinator9889/bitcoinpools/FragmentViews/Tab2BTCChart.java @@ -11,6 +11,7 @@ import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.constraint.ConstraintLayout; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; @@ -27,7 +28,6 @@ import android.widget.DatePicker; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.LineData; @@ -37,18 +37,22 @@ import org.json.JSONException; +import java.io.IOException; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ExecutionException; +import javinator9889.bitcoinpools.BitCoinApp; +import javinator9889.bitcoinpools.CacheManaging; import javinator9889.bitcoinpools.Constants; import javinator9889.bitcoinpools.JSONTools.JSONTools; import javinator9889.bitcoinpools.MainActivity; @@ -62,6 +66,7 @@ public class Tab2BTCChart extends Fragment implements DatePickerDialog.OnDateSetListener { private static Map BTCPRICE = new LinkedHashMap<>(); + private HashMap cardsContentData; private static final String API_URL = "https://api.coindesk.com/v1/bpi/historical/close.json"; private static String REQUEST_URL; private static LineChart DESTINATIONLINECHART; @@ -76,12 +81,24 @@ public class Tab2BTCChart extends Fragment implements DatePickerDialog.OnDateSet private boolean date_set = false; private List cardsContents; + public Tab2BTCChart() { } - @SuppressLint("SimpleDateFormat") + @SuppressWarnings("unchecked") + public static Tab2BTCChart newInstance(Object... params) { + Bundle args = new Bundle(); + args.putSerializable("CARDS", (HashMap) params[0]); + args.putSerializable("BTCPRICE", (HashMap) params[1]); + Tab2BTCChart fragment = new Tab2BTCChart(); + fragment.setArguments(args); + return fragment; + } + + @SuppressWarnings("unchecked") @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { DisplayMetrics dp = this.getResources().getDisplayMetrics(); float dpHeight = dp.heightPixels; @@ -97,16 +114,20 @@ public void onClick(View v) { REQUEST_URL = API_URL; - setupValues(); + BTCPRICE = (HashMap) getArguments().getSerializable("BTCPRICE"); + this.cardsContentData = (HashMap) getArguments() + .getSerializable("CARDS"); cardsContents = new ArrayList<>(); CardsAdapter adapter = new CardsAdapter(getContext(), cardsContents); DESTINATIONLINECHART = createdView.findViewById(R.id.lineChart); RecyclerView recyclerView = createdView.findViewById(R.id.recycler_view); - RecyclerView.LayoutManager layoutManager = new GridLayoutManager(createdView.getContext(), 1); + RecyclerView.LayoutManager layoutManager = new GridLayoutManager(createdView.getContext(), + 1); recyclerView.setLayoutManager(layoutManager); - recyclerView.addItemDecoration(new GridSpacingItemDecoration(2, dpToPx(10), true)); + recyclerView.addItemDecoration(new GridSpacingItemDecoration(2, + dpToPx(10), true)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(adapter); recyclerView.setNestedScrollingEnabled(false); @@ -119,24 +140,40 @@ public void onClick(View v) { a.recycle(); int FINALDP = (int) ((dpHeight - size) * 0.7); - ConstraintLayout.LayoutParams lp = (ConstraintLayout.LayoutParams) DESTINATIONLINECHART.getLayoutParams(); + ConstraintLayout.LayoutParams lp = (ConstraintLayout.LayoutParams) + DESTINATIONLINECHART.getLayoutParams(); lp.height = FINALDP; lp.matchConstraintMaxHeight = (int) dpHeight; DESTINATIONLINECHART.setLayoutParams(lp); DESTINATIONLINECHART.invalidate(); FRAGMENT_CONTEXT = createdView.getContext(); - ((TextView) createdView.findViewById(R.id.longPressInfo)).setText(R.string.longclick); - - return createdView; + String longPressInfo; + try { + CacheManaging cache = CacheManaging.newInstance(createdView.getContext()); + String date = cache.readCache().get("date"); + if (date != null) { + longPressInfo = getString(R.string.longclick) + "\n" + + getString(R.string.comparationDate) + date; + } else + longPressInfo = getString(R.string.longclick); + ((TextView) createdView.findViewById(R.id.longPressInfo)).setText(longPressInfo); + return createdView; + } catch (Exception e) { + longPressInfo = getString(R.string.longclick); + ((TextView) createdView.findViewById(R.id.longPressInfo)).setText(longPressInfo); + return createdView; + } } private void setupValues() { net httpsResponse = new net(); httpsResponse.execute(REQUEST_URL); try { - BTCPRICE = JSONTools.sortDateByValue(JSONTools.convert2DateHashMap(httpsResponse.get().getJSONObject("bpi"))); - } catch (InterruptedException | ExecutionException | JSONException e) { + BTCPRICE = JSONTools.sortDateByValue(JSONTools.convert2DateHashMap( + httpsResponse.get().getJSONObject("bpi"))); + } catch (InterruptedException | ExecutionException | + JSONException | NullPointerException e) { BTCPRICE = null; Log.e(Constants.LOG.MATAG, Constants.LOG.DATA_ERROR + e.getMessage()); } @@ -145,8 +182,6 @@ private void setupValues() { @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); - System.out.println(lineChartCreated); - System.out.println("Is view visible for user? " + isVisibleToUser); if (isVisibleToUser && !lineChartCreated) { createLineChart(DESTINATIONLINECHART, FRAGMENT_CONTEXT); } @@ -154,7 +189,8 @@ public void setUserVisibleHint(boolean isVisibleToUser) { MainActivity.MAINACTIVITY_TOOLBAR.setTitle(getString(R.string.btcinfo)); } - private void createLineChart(@NonNull final LineChart destinationChart, @NonNull final Context fragmentContext) { + private void createLineChart(@NonNull final LineChart destinationChart, + @NonNull final Context fragmentContext) { new Handler().postDelayed(new Runnable() { @Override public void run() { @@ -174,7 +210,8 @@ public void run() { ++i; } LineDataSet lineDataSet; - if ((destinationChart.getData() != null) && (destinationChart.getData().getDataSetCount() > 0)) { + if ((destinationChart.getData() != null) && + (destinationChart.getData().getDataSetCount() > 0)) { lineDataSet = (LineDataSet) destinationChart.getData().getDataSetByIndex(0); lineDataSet.setValues(values); destinationChart.getData().notifyDataChanged(); @@ -192,10 +229,12 @@ public void run() { lineDataSet.setValueTextSize(9f); lineDataSet.setDrawFilled(true); lineDataSet.setFormLineWidth(1f); - lineDataSet.setFormLineDashEffect(new DashPathEffect(new float[]{10f, 5f}, 0f)); + lineDataSet.setFormLineDashEffect(new DashPathEffect(new float[]{10f, 5f}, + 0f)); lineDataSet.setFormSize(15.f); lineDataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER); - lineDataSet.setFillDrawable(ContextCompat.getDrawable(fragmentContext, R.drawable.fade_red)); + lineDataSet.setFillDrawable(ContextCompat.getDrawable(fragmentContext, + R.drawable.fade_red)); lineDataSet.setDrawCircles(false); ArrayList dataSets = new ArrayList<>(); dataSets.add(lineDataSet); @@ -212,6 +251,7 @@ public void run() { @Override public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { + Calendar actualDate = Calendar.getInstance(); if (year <= 2010) { this.year = 2010; if ((month <= 6) && (dayOfMonth < 17)) { @@ -229,10 +269,22 @@ public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { this.year = year; } } else { - this.day = dayOfMonth; - this.month = month; - this.writable_month = ++month; - this.year = year; + if (dayOfMonth >= actualDate.get(Calendar.DAY_OF_MONTH)) { + if (month >= actualDate.get(Calendar.MONTH)) { + if (year >= actualDate.get(Calendar.YEAR)) { + actualDate.add(Calendar.DAY_OF_MONTH, -2); + this.day = actualDate.get(Calendar.DAY_OF_MONTH); + this.month = actualDate.get(Calendar.MONTH); + this.writable_month = ++month; + this.year = actualDate.get(Calendar.YEAR); + } + } + } else { + this.day = dayOfMonth; + this.month = month; + this.writable_month = ++month; + this.year = year; + } } this.date_set = true; String buttonText = getString(R.string.since) + " " + parseDate(); @@ -256,6 +308,7 @@ private String parseDate() { @NonNull public Dialog createDialog() { final Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DAY_OF_MONTH, -2); final Calendar limitDate = Calendar.getInstance(); limitDate.set(2010, 6, 17); @@ -265,9 +318,13 @@ public Dialog createDialog() { this.day = calendar.get(Calendar.DAY_OF_MONTH); this.date_set = true; } - System.out.println(this.year + " " + this.month + " " + this.day); - DatePickerDialog dialog = new DatePickerDialog(getActivity(), this, this.year, this.month, this.day); + DatePickerDialog dialog = new DatePickerDialog( + getActivity(), + this, + this.year, + this.month, + this.day); calendar.add(Calendar.DATE, -1); @@ -277,11 +334,11 @@ public Dialog createDialog() { return dialog; } - @SuppressLint("SimpleDateFormat") public void forceReload() { String dateParsed = parseDate(); - REQUEST_URL = API_URL + "?start=" + dateParsed + "&end=" + new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime()); - System.out.println(REQUEST_URL); + REQUEST_URL = API_URL + "?start=" + dateParsed + "&end=" + + new SimpleDateFormat("yyyy-MM-dd", Locale.US) + .format(Calendar.getInstance().getTime()); setupValues(); createLineChart(DESTINATIONLINECHART, FRAGMENT_CONTEXT); } @@ -291,7 +348,6 @@ public static void setLineChartCreated() { } public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { - private int spanCount; private int spacing; private boolean includeEdge; @@ -308,39 +364,75 @@ public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge */ private int dpToPx(int dp) { Resources r = getResources(); - return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics())); + return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, + r.getDisplayMetrics())); } - @SuppressLint("DefaultLocale") private void prepareCards() { - net httpsResponse = new net(); - Map cardsData = new LinkedHashMap<>(); - httpsResponse.execute(STATS_URL); + HashMap cachedMap = getCachedMap(); + String market_price = null; + String hash_rate = null; + String difficulty = null; + String blocks_mined = null; + String minutes = null; + String total_fees = null; + String tx = null; + String miners_revenue = null; + if (cachedMap != null) { + market_price = cachedMap.get("market_price_usd"); + hash_rate = cachedMap.get("hash_rate"); + difficulty = cachedMap.get("difficulty"); + blocks_mined = cachedMap.get("n_blocks_mined"); + minutes = cachedMap.get("minutes_between_blocks"); + total_fees = cachedMap.get("total_fees_btc"); + tx = cachedMap.get("n_tx"); + miners_revenue = cachedMap.get("miners_revenue_usd"); + System.out.println(cachedMap.toString()); + } + DecimalFormat df = new DecimalFormat("#.##", new DecimalFormatSymbols(Locale.US)); + cardsContents.add(new CardsContent(getString(R.string.market_price), + "$" + df.format(this.cardsContentData.get("market_price_usd")), + market_price)); + cardsContents.add(new CardsContent(getString(R.string.hash_rate), + df.format(this.cardsContentData.get("hash_rate")) + " GH/s", + hash_rate)); + cardsContents.add(new CardsContent(getString(R.string.difficulty), + df.format(this.cardsContentData.get("difficulty")), + difficulty)); + cardsContents.add(new CardsContent(getString(R.string.min_blocks), + df.format(this.cardsContentData.get("n_blocks_mined") / 10) + + " " + getString(R.string.blocks_name), + blocks_mined)); + cardsContents.add(new CardsContent(getString(R.string.minutes_blocks), + df.format(this.cardsContentData.get("minutes_between_blocks")) + + " " + getString(R.string.minutes_name), + minutes)); + cardsContents.add(new CardsContent(getString(R.string.total_fees), + df.format(this.cardsContentData.get("total_fees_btc") / 10000000) + + " BTC", + total_fees)); + cardsContents.add(new CardsContent(getString(R.string.total_trans), + df.format(this.cardsContentData.get("n_tx")), + tx)); + cardsContents.add(new CardsContent(getString(R.string.min_benefit), + "$" + df.format(this.cardsContentData.get("miners_revenue_usd") / 100), + miners_revenue)); + } + + @Nullable + private HashMap getCachedMap() { + CacheManaging cache = CacheManaging.newInstance(BitCoinApp.getAppContext()); try { - cardsData = JSONTools.sortByValue(JSONTools.convert2HashMap(httpsResponse.get())); - } catch (InterruptedException | ExecutionException e) { - cardsData = null; - Log.e(Constants.LOG.MATAG, Constants.LOG.DATA_ERROR + e.getMessage()); - Crashlytics.logException(e); - } finally { - assert cardsData != null; - DecimalFormat df = new DecimalFormat("#.##", new DecimalFormatSymbols(Locale.US)); - cardsContents.add(new CardsContent(getString(R.string.market_price), - "$" + df.format(cardsData.get("market_price_usd")))); - cardsContents.add(new CardsContent(getString(R.string.hash_rate), - df.format(cardsData.get("hash_rate")) + " GH/s")); - cardsContents.add(new CardsContent(getString(R.string.difficulty), - df.format(cardsData.get("difficulty")))); - cardsContents.add(new CardsContent(getString(R.string.min_blocks), - df.format(cardsData.get("n_blocks_mined") / 10) + " " + getString(R.string.blocks_name))); - cardsContents.add(new CardsContent(getString(R.string.minutes_blocks), - df.format(cardsData.get("minutes_between_blocks")) + " " + getString(R.string.minutes_name))); - cardsContents.add(new CardsContent(getString(R.string.total_fees), - df.format(cardsData.get("total_fees_btc") / 10000000) + " BTC")); - cardsContents.add(new CardsContent(getString(R.string.total_trans), - df.format(cardsData.get("n_tx")))); - cardsContents.add(new CardsContent(getString(R.string.min_benefit), - "$" + df.format(cardsData.get("miners_revenue_usd") / 100))); + cache.setupFile(); + } catch (IOException e) { + Log.e(Constants.LOG.MATAG, "Unable to create cache file"); + } + try { + return cache.readCache(); + } catch (IOException | ClassNotFoundException e) { + System.out.println("Error while reading cache. Full trace: " + e.getMessage()); + e.printStackTrace(); + return null; } } } diff --git a/app/src/main/java/javinator9889/bitcoinpools/JSONTools/JSONTools.java b/app/src/main/java/javinator9889/bitcoinpools/JSONTools/JSONTools.java index 3943553..c9f88f1 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/JSONTools/JSONTools.java +++ b/app/src/main/java/javinator9889/bitcoinpools/JSONTools/JSONTools.java @@ -1,6 +1,6 @@ package javinator9889.bitcoinpools.JSONTools; -import android.annotation.SuppressLint; +import android.support.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; @@ -15,6 +15,7 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.TreeMap; @@ -24,26 +25,27 @@ */ public class JSONTools { - public static Map sortByValue(Map unsortMap) { + public static HashMap sortByValue(HashMap unsortMap) { // 1. Convert Map to List of Map - List> list = + List> list = new LinkedList<>(unsortMap.entrySet()); - Collections.sort(list, new Comparator>() { - public int compare(Map.Entry o1, Map.Entry o2) { + Collections.sort(list, new Comparator>() { + public int compare(HashMap.Entry o1, HashMap.Entry o2) { return (o1.getValue()).compareTo(o2.getValue()); } }); // 3. Loop the sorted list and put it into a new insertion order Map LinkedHashMap - Map sortedMap = new LinkedHashMap<>(); + HashMap sortedMap = new LinkedHashMap<>(); for (Map.Entry entry : list) { sortedMap.put(entry.getKey(), entry.getValue()); } return sortedMap; } + @Nullable public static HashMap convert2HashMap(JSONObject object) { HashMap hReturn = new HashMap<>(); Iterator iterator = object.keys(); @@ -58,10 +60,11 @@ public static HashMap convert2HashMap(JSONObject object) { } } + @Nullable public static HashMap convert2DateHashMap(JSONObject object) { HashMap hReturn = new HashMap<>(); Iterator iterator = object.keys(); - @SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US); try { while (iterator.hasNext()) { String key = iterator.next(); @@ -73,11 +76,9 @@ public static HashMap convert2DateHashMap(JSONObject object) { } } - public static Map sortDateByValue(Map unsortMap) { - -// DateFormat df = new SimpleDateFormat("dd-MM-yyyy", Locale.US); + public static HashMap sortDateByValue(HashMap unsortMap) { Map m1 = new TreeMap<>(unsortMap); - Map returnMap = new LinkedHashMap<>(); + HashMap returnMap = new LinkedHashMap<>(); for (Map.Entry entry : m1.entrySet()) { returnMap.put(entry.getKey(), entry.getValue()); } diff --git a/app/src/main/java/javinator9889/bitcoinpools/License.java b/app/src/main/java/javinator9889/bitcoinpools/License.java index 4fbf2a1..043c587 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/License.java +++ b/app/src/main/java/javinator9889/bitcoinpools/License.java @@ -4,16 +4,9 @@ import android.support.v4.app.FragmentManager; import android.support.v7.app.AppCompatActivity; import android.util.Log; -import android.view.View; -import android.widget.Toast; -import com.mikepenz.aboutlibraries.LibTaskCallback; -import com.mikepenz.aboutlibraries.Libs; import com.mikepenz.aboutlibraries.LibsBuilder; -import com.mikepenz.aboutlibraries.LibsConfiguration; -import com.mikepenz.aboutlibraries.entity.Library; import com.mikepenz.aboutlibraries.ui.LibsSupportFragment; -import com.mikepenz.fastadapter.adapters.ItemAdapter; /** * Created by Javinator9889 on 22/12/2017. @@ -21,6 +14,12 @@ */ public class License extends AppCompatActivity { + @Override + public void onBackPressed() { + super.onBackPressed(); + overridePendingTransition(R.anim.activity_back_in, R.anim.activity_back_out); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -39,82 +38,11 @@ protected void onCreate(Bundle savedInstanceState) { .withAutoDetect(true) .withAboutDescription(getString(R.string.bitcoindesc)) .withLicenseDialog(true) + .withLibraries(getString(R.string.library_donations_libraryName)) .supportFragment(); FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction().replace(R.id.frame_container, fragment).commit(); Log.d(Constants.LOG.LTAG, Constants.LOG.INIT_L); - } - - LibTaskCallback libTaskCallback = new LibTaskCallback() { - @Override - public void onLibTaskStarted() { - Log.e("AboutLibraries", "started"); - } - - @Override - public void onLibTaskFinished(ItemAdapter fastItemAdapter) { - Log.e("AboutLibraries", "finished"); - } - }; - - LibsConfiguration.LibsUIListener libsUIListener = new LibsConfiguration.LibsUIListener() { - @Override - public View preOnCreateView(View view) { - return view; - } - - @Override - public View postOnCreateView(View view) { - return view; - } - }; - - LibsConfiguration.LibsListener libsListener = new LibsConfiguration.LibsListener() { - @Override - public void onIconClicked(View v) { - Toast.makeText(v.getContext(), "We are able to track this now ;)", Toast.LENGTH_LONG).show(); - } - - @Override - public boolean onLibraryAuthorClicked(View v, Library library) { - return false; - } - - @Override - public boolean onLibraryContentClicked(View v, Library library) { - return false; - } - - @Override - public boolean onLibraryBottomClicked(View v, Library library) { - return false; - } - - @Override - public boolean onExtraClicked(View v, Libs.SpecialButton specialButton) { - return false; - } - - @Override - public boolean onIconLongClicked(View v) { - return false; - } - - @Override - public boolean onLibraryAuthorLongClicked(View v, Library library) { - return false; - } - - @Override - public boolean onLibraryContentLongClicked(View v, Library library) { - return false; - } - - @Override - public boolean onLibraryBottomLongClicked(View v, Library library) { - return false; - } - }; } diff --git a/app/src/main/java/javinator9889/bitcoinpools/MainActivity.java b/app/src/main/java/javinator9889/bitcoinpools/MainActivity.java index 4450d9c..f1f1eb7 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/MainActivity.java +++ b/app/src/main/java/javinator9889/bitcoinpools/MainActivity.java @@ -1,16 +1,11 @@ package javinator9889.bitcoinpools; -import android.Manifest; import android.annotation.SuppressLint; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; import android.support.design.widget.TabLayout; -import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; @@ -22,56 +17,56 @@ import android.view.MenuItem; import android.widget.Toast; -import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; -import com.google.android.gms.appinvite.AppInviteInvitation; -import com.google.firebase.analytics.FirebaseAnalytics; import java.math.BigDecimal; -import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; import javinator9889.bitcoinpools.AppUpdaterManager.CheckUpdates; import javinator9889.bitcoinpools.FragmentViews.DonationsActivity; import javinator9889.bitcoinpools.FragmentViews.Tab1PoolsChart; import javinator9889.bitcoinpools.FragmentViews.Tab2BTCChart; +import javinator9889.bitcoinpools.JSONTools.JSONTools; +@SuppressLint("StaticFieldLeak") public class MainActivity extends AppCompatActivity { - @SuppressLint("StaticFieldLeak") public static Toolbar MAINACTIVITY_TOOLBAR; - private FirebaseAnalytics mFirebaseAnalytics; + public static AppCompatActivity mainActivity; + + private float mpu; + private HashMap retrievedData; + private HashMap cardsData; + private HashMap btcPrice; @Override + @SuppressWarnings("unchecked") protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (BitCoinApp.isOnline()) { - Log.d(Constants.LOG.MATAG, Constants.LOG.CREATING_MAINVIEW); - setContentView(R.layout.activity_main); - mFirebaseAnalytics = FirebaseAnalytics.getInstance(this); - Bundle appOpen = new Bundle(); - appOpen.putString("Date", Calendar.getInstance().getTime().toString()); - appOpen.putString("DeviceBrand", Build.BRAND); - appOpen.putString("DeviceID", Build.ID); - appOpen.putString("DeviceName", Build.PRODUCT); - appOpen.putString("AndroidVersion", Build.VERSION.RELEASE); - mFirebaseAnalytics.logEvent("main_activity", appOpen); - - if (BitCoinApp.getSharedPreferences().contains(Constants.SHARED_PREFERENCES.APP_VERSION)) { - if (!BitCoinApp.getSharedPreferences().getString(Constants.SHARED_PREFERENCES.APP_VERSION, "1.0").equals(BitCoinApp.appVersion())) { - new MaterialDialog.Builder(this) - .title("Changelog") - .content(R.string.changelog, true) - .cancelable(true) - .positiveText(R.string.accept) - .build().show(); - SharedPreferences.Editor editor = BitCoinApp.getSharedPreferences().edit(); - editor.putString(Constants.SHARED_PREFERENCES.APP_VERSION, BitCoinApp.appVersion()); - editor.apply(); - } - } else { + mainActivity = this; + Log.d(Constants.LOG.MATAG, Constants.LOG.CREATING_MAINVIEW); + setContentView(R.layout.activity_main); + Intent dataFromDataLoaderClass = getIntent(); + this.mpu = dataFromDataLoaderClass.getFloatExtra("MPU", 0); + this.retrievedData = JSONTools.sortByValue + ((HashMap) dataFromDataLoaderClass + .getSerializableExtra("RD")); + this.cardsData = JSONTools.sortByValue + ((HashMap) dataFromDataLoaderClass + .getSerializableExtra("CARDS")); + this.btcPrice = JSONTools.sortDateByValue + ((HashMap) dataFromDataLoaderClass + .getSerializableExtra("BTCPRICE")); + if (BitCoinApp.getSharedPreferences().contains(Constants.SHARED_PREFERENCES.APP_VERSION)) { + if (!BitCoinApp.getSharedPreferences() + .getString(Constants.SHARED_PREFERENCES.APP_VERSION, "1.0") + .equals(BitCoinApp.appVersion())) + { new MaterialDialog.Builder(this) .title("Changelog") - .content(R.string.changelog, true) + .content(R.string.changelog, + true) .cancelable(true) .positiveText(R.string.accept) .build().show(); @@ -79,44 +74,62 @@ protected void onCreate(Bundle savedInstanceState) { editor.putString(Constants.SHARED_PREFERENCES.APP_VERSION, BitCoinApp.appVersion()); editor.apply(); } - - Log.d(Constants.LOG.MATAG, Constants.LOG.INIT_VALUES); - checkPermissions(); - CheckUpdates ck = new CheckUpdates(Constants.GITHUB_USER, Constants.GITHUB_REPO); - - SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); - MAINACTIVITY_TOOLBAR = findViewById(R.id.toolbar); - setSupportActionBar(MAINACTIVITY_TOOLBAR); - - ViewPager viewPager = findViewById(R.id.viewContainer); - viewPager.setAdapter(mSectionsPagerAdapter); - - TabLayout tabLayout = findViewById(R.id.tabs); - setupTabs(tabLayout); - viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout)); - tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager)); - - Log.d(Constants.LOG.MATAG, Constants.LOG.CREATING_CHART); - - Log.d(Constants.LOG.MATAG, Constants.LOG.LISTENING); - ck.checkForUpdates(this, getString(R.string.updateAvailable), getString(R.string.updateDescrip), getString(R.string.updateNow), getString(R.string.updateLater), getString(R.string.updatePage)); } else { new MaterialDialog.Builder(this) - .title(R.string.noConnectionTitle) - .content(R.string.noConnectionDesc) - .cancelable(false) + .title("Changelog") + .content(R.string.changelog, + true) + .cancelable(true) .positiveText(R.string.accept) - .onPositive(new MaterialDialog.SingleButtonCallback() { - @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - closeApp(); - } - }) - .build() - .show(); + .build().show(); + SharedPreferences.Editor editor = BitCoinApp.getSharedPreferences().edit(); + editor.putString(Constants.SHARED_PREFERENCES.APP_VERSION, BitCoinApp.appVersion()); + editor.apply(); + } + + Log.d(Constants.LOG.MATAG, Constants.LOG.INIT_VALUES); + + CheckUpdates ck = new CheckUpdates(Constants.GITHUB_USER, Constants.GITHUB_REPO); + + SectionsPagerAdapter mSectionsPagerAdapter = + new SectionsPagerAdapter(getSupportFragmentManager()); + MAINACTIVITY_TOOLBAR = findViewById(R.id.toolbar); + setSupportActionBar(MAINACTIVITY_TOOLBAR); + + ViewPager viewPager = findViewById(R.id.viewContainer); + viewPager.setAdapter(mSectionsPagerAdapter); + + TabLayout tabLayout = findViewById(R.id.tabs); + setupTabs(tabLayout); + viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout)); + tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager)); + + Log.d(Constants.LOG.MATAG, Constants.LOG.CREATING_CHART); + Log.d(Constants.LOG.MATAG, Constants.LOG.LISTENING); + try { + ck.checkForUpdates(this, + getString(R.string.updateAvailable), + getString(R.string.updateDescrip), + getString(R.string.updateNow), + getString(R.string.updateLater), + getString(R.string.updatePage)); + } catch (NullPointerException e) { + Log.e(Constants.LOG.MATAG, "Unable to get updates"); } } + @Override + protected void onResume() { + super.onResume(); + DataLoaderScreen.dataLoaderScreenActivity.finish(); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + overridePendingTransition(R.anim.activity_back_in, R.anim.activity_back_out); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_menu, menu); @@ -125,8 +138,12 @@ public boolean onCreateOptionsMenu(Menu menu) { private void refresh() { Tab2BTCChart.setLineChartCreated(); - Intent intentMain = new Intent(MainActivity.this, MainActivity.class); + Intent intentMain = new Intent(MainActivity.this, DataLoaderScreen.class); intentMain.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + /*intentMain.putExtra("MPU", mpu); + intentMain.putExtra("RD", retrievedData); + intentMain.putExtra("CARDS", cardsData); + intentMain.putExtra("BTCPRICE", btcPrice);*/ startActivity(intentMain); MainActivity.this.finish(); } @@ -144,9 +161,10 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.settings: Thread settingsThread = new Thread() { public void run() { - Intent intentSettings = new Intent(MainActivity.this, SpinnerActivity.class); + Intent intentSettings = new Intent(MainActivity.this, + SpinnerActivity.class); startActivity(intentSettings); - MainActivity.this.finish(); + overridePendingTransition(R.anim.activity_in, R.anim.activity_out); } }; settingsThread.setName("settings_thread"); @@ -155,8 +173,10 @@ public void run() { case R.id.license: Thread licenseThread = new Thread() { public void run() { - Intent intentLicense = new Intent(MainActivity.this, License.class); + Intent intentLicense = new Intent(MainActivity.this, + License.class); startActivity(intentLicense); + overridePendingTransition(R.anim.activity_in, R.anim.activity_out); } }; licenseThread.setName("license_thread"); @@ -167,36 +187,31 @@ public void run() { Toast.makeText(this, R.string.updated, Toast.LENGTH_LONG).show(); break; case R.id.share: - Intent intent = new AppInviteInvitation.IntentBuilder(getString(R.string.invitation_title)) - .setMessage(getString(R.string.inv_message)) - .setDeepLink(Uri.parse(Constants.GITHUB_URL)) - .build(); - startActivityForResult(intent, Constants.REQUEST_CODE); + Intent shareAppIntent = new Intent(Intent.ACTION_SEND); + shareAppIntent.setType("text/plain"); + shareAppIntent.putExtra(Intent.EXTRA_SUBJECT, "BitCoin Pools"); + final Uri googlePlayLink = Uri.parse(Constants.GOOGLE_PLAY_URL); + shareAppIntent.putExtra(Intent.EXTRA_TEXT, + getString(R.string.inv_message) + + " - Google Play Store: " + + googlePlayLink.toString()); + startActivity(Intent.createChooser(shareAppIntent, getString(R.string.invitation_title))); break; case R.id.donate: - Intent donateIntent = new Intent(MainActivity.this, DonationsActivity.class); + Intent donateIntent = new Intent(MainActivity.this, + DonationsActivity.class); startActivity(donateIntent); + overridePendingTransition(R.anim.activity_in, R.anim.activity_out); break; } return true; } - private void checkPermissions() { - Log.d(Constants.LOG.MATAG, Constants.LOG.CHECKING_PERMISSIONS); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); - } - } - } - - private void closeApp() { - this.onBackPressed(); - } - private void setupTabs(TabLayout destinationTab) { - destinationTab.addTab(destinationTab.newTab().setText(R.string.poolsChart).setIcon(R.drawable.ic_poll_white_24dp)); - destinationTab.addTab(destinationTab.newTab().setText(R.string.bitcoinChart).setIcon(R.drawable.ic_attach_money_white_24dp)); + destinationTab.addTab(destinationTab.newTab(). + setText(R.string.poolsChart).setIcon(R.drawable.ic_poll_white_24dp)); + destinationTab.addTab(destinationTab.newTab(). + setText(R.string.bitcoinChart).setIcon(R.drawable.ic_attach_money_white_24dp)); } /** @@ -211,14 +226,11 @@ public class SectionsPagerAdapter extends FragmentPagerAdapter { @Override public Fragment getItem(int position) { - // getItem is called to instantiate the fragment for the given page. - // Return a PlaceholderFragment (defined as a static inner class below). - //return PlaceholderFragment.newInstance(position + 1); switch (position) { case 0: - return new Tab1PoolsChart(); + return Tab1PoolsChart.newInstance(mpu, retrievedData); case 1: - return new Tab2BTCChart(); + return Tab2BTCChart.newInstance(cardsData, btcPrice); default: return null; } @@ -226,7 +238,6 @@ public Fragment getItem(int position) { @Override public int getCount() { - // Show 3 total pages. return 2; } diff --git a/app/src/main/java/javinator9889/bitcoinpools/NetTools/net.java b/app/src/main/java/javinator9889/bitcoinpools/NetTools/net.java index 5b123c9..dc5631a 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/NetTools/net.java +++ b/app/src/main/java/javinator9889/bitcoinpools/NetTools/net.java @@ -1,16 +1,17 @@ package javinator9889.bitcoinpools.NetTools; import android.os.AsyncTask; +import android.support.annotation.NonNull; -import org.json.JSONArray; import org.json.JSONObject; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; -import javax.net.ssl.HttpsURLConnection; import java.net.URL; +import javax.net.ssl.HttpsURLConnection; + /** * Created by Javinator9889 on 20/12/2017. * Based on: https://stackoverflow.com/questions/1485708/how-do-i-do-a-http-get-in-java @@ -27,12 +28,14 @@ protected JSONObject doInBackground(String... url) { } } + @NonNull public static JSONObject getHttpRequest(String url) throws Exception { StringBuilder response = new StringBuilder(); URL urlObject = new URL(url); HttpURLConnection connection = (HttpURLConnection) urlObject.openConnection(); connection.setRequestMethod("GET"); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(connection.getInputStream())); String line; while ((line = bufferedReader.readLine()) != null) { @@ -42,12 +45,14 @@ public static JSONObject getHttpRequest(String url) throws Exception { return new JSONObject(response.toString()); } + @NonNull public static JSONObject getHttpsRequest(String url) throws Exception { StringBuilder response = new StringBuilder(); URL urlObject = new URL(url); HttpsURLConnection connection = (HttpsURLConnection) urlObject.openConnection(); connection.setRequestMethod("GET"); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(connection.getInputStream())); String line; while ((line = bufferedReader.readLine()) != null) { @@ -55,6 +60,5 @@ public static JSONObject getHttpsRequest(String url) throws Exception { } bufferedReader.close(); return new JSONObject(response.toString()); - //return response.toString(); } } diff --git a/app/src/main/java/javinator9889/bitcoinpools/SpinnerActivity.java b/app/src/main/java/javinator9889/bitcoinpools/SpinnerActivity.java index 6ea25f7..6161126 100644 --- a/app/src/main/java/javinator9889/bitcoinpools/SpinnerActivity.java +++ b/app/src/main/java/javinator9889/bitcoinpools/SpinnerActivity.java @@ -38,47 +38,59 @@ protected void onCreate(@Nullable final Bundle savedInstanceState) { setTitle(R.string.settingsTitle); initArrayOfValues(); - ACTUAL_DAYS = BitCoinApp.getSharedPreferences().getInt(Constants.SHARED_PREFERENCES.DAYS_TO_CHECK, 1); - ACTUAL_PRICE = BitCoinApp.getSharedPreferences().getInt(Constants.SHARED_PREFERENCES.VALUE_TO_CHECK, 1000); - ACTUAL_ENABLED = BitCoinApp.getSharedPreferences().getBoolean(Constants.SHARED_PREFERENCES.NOTIFICATIONS_ENABLED, false); + ACTUAL_DAYS = BitCoinApp.getSharedPreferences() + .getInt(Constants.SHARED_PREFERENCES.DAYS_TO_CHECK, 1); + ACTUAL_PRICE = BitCoinApp.getSharedPreferences() + .getInt(Constants.SHARED_PREFERENCES.VALUE_TO_CHECK, 1000); + ACTUAL_ENABLED = BitCoinApp.getSharedPreferences() + .getBoolean(Constants.SHARED_PREFERENCES.NOTIFICATIONS_ENABLED, false); NEW_VALUE_ENABLED = ACTUAL_ENABLED; - TextView tv = (TextView) findViewById(R.id.daysTitle); - final Spinner spinner = (Spinner) findViewById(R.id.spinner2); + TextView tv = findViewById(R.id.daysTitle); + final Spinner spinner = findViewById(R.id.spinner2); Log.d(Constants.LOG.STAG, Constants.LOG.INIT_SPINNER); spinner.setOnItemSelectedListener(this); - ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.days, android.R.layout.simple_spinner_item); + ArrayAdapter adapter = ArrayAdapter + .createFromResource(this, R.array.days, + android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); spinner.setSelection(ACTUAL_DAYS - 1); String output = getString(R.string.cDays); tv.setText(output); - final Spinner spinner1 = (Spinner) findViewById(R.id.spinner3); + final Spinner spinner1 = findViewById(R.id.spinner3); spinner1.setOnItemSelectedListener(this); - ArrayAdapter adapter1 = ArrayAdapter.createFromResource(this, R.array.prices, android.R.layout.simple_spinner_item); + ArrayAdapter adapter1 = ArrayAdapter + .createFromResource(this, R.array.prices, + android.R.layout.simple_spinner_item); adapter1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner1.setAdapter(adapter1); spinner1.setSelection(PRICES_VALUES.get(ACTUAL_PRICE)); spinner1.setEnabled(ACTUAL_ENABLED); Log.d(Constants.LOG.STAG, Constants.LOG.INIT_SWITCH + ACTUAL_ENABLED); - final Switch settingsSwitch = (Switch) findViewById(R.id.switch1); + final Switch settingsSwitch = findViewById(R.id.switch1); settingsSwitch.setChecked(ACTUAL_ENABLED); settingsSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Log.d(Constants.LOG.STAG, Constants.LOG.CHANGE_PREFERENCES + "switch (enables/disables options)"); + Log.d(Constants.LOG.STAG, Constants.LOG.CHANGE_PREFERENCES + + "switch (enables/disables options)"); if (isChecked) { - SharedPreferences.Editor sharedPreferencesEditor = BitCoinApp.getSharedPreferences().edit(); - sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES.NOTIFICATIONS_ENABLED, true); + SharedPreferences.Editor sharedPreferencesEditor = BitCoinApp + .getSharedPreferences().edit(); + sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES + .NOTIFICATIONS_ENABLED, true); sharedPreferencesEditor.apply(); spinner1.setEnabled(true); NEW_VALUE_ENABLED = true; } else { - SharedPreferences.Editor sharedPreferencesEditor = BitCoinApp.getSharedPreferences().edit(); - sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES.NOTIFICATIONS_ENABLED, false); + SharedPreferences.Editor sharedPreferencesEditor = BitCoinApp + .getSharedPreferences().edit(); + sharedPreferencesEditor.putBoolean(Constants.SHARED_PREFERENCES + .NOTIFICATIONS_ENABLED, false); sharedPreferencesEditor.apply(); spinner1.setEnabled(false); NEW_VALUE_ENABLED = false; @@ -91,21 +103,30 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { public void onItemSelected(AdapterView parent, View view, int position, long id) { switch (parent.getId()) { case R.id.spinner2: - Log.d(Constants.LOG.STAG, Constants.LOG.CHANGE_PREFERENCES + "spinner2 (days to check)"); + Log.d(Constants.LOG.STAG, Constants.LOG.CHANGE_PREFERENCES + + "spinner2 (days to check)"); NEW_VALUE_DAYS = Integer.parseInt(parent.getItemAtPosition(position).toString()); - SharedPreferences.Editor sharedPreferencesEditor = BitCoinApp.getSharedPreferences().edit(); - sharedPreferencesEditor.putInt(Constants.SHARED_PREFERENCES.DAYS_TO_CHECK, NEW_VALUE_DAYS); + SharedPreferences.Editor sharedPreferencesEditor = BitCoinApp + .getSharedPreferences().edit(); + sharedPreferencesEditor.putInt(Constants.SHARED_PREFERENCES.DAYS_TO_CHECK, + NEW_VALUE_DAYS); sharedPreferencesEditor.apply(); break; case R.id.spinner3: - Log.d(Constants.LOG.STAG, Constants.LOG.CHANGE_PREFERENCES + "spinner3 (price to check)"); - NEW_VALUE_PRICE = Integer.parseInt(parent.getItemAtPosition(position).toString().replace("$", "")); - SharedPreferences.Editor sharedPreferencesEditor2 = BitCoinApp.getSharedPreferences().edit(); - sharedPreferencesEditor2.putInt(Constants.SHARED_PREFERENCES.VALUE_TO_CHECK, NEW_VALUE_PRICE); + Log.d(Constants.LOG.STAG, Constants.LOG.CHANGE_PREFERENCES + + "spinner3 (price to check)"); + NEW_VALUE_PRICE = Integer.parseInt(parent.getItemAtPosition(position).toString() + .replace("$", "")); + SharedPreferences.Editor sharedPreferencesEditor2 = BitCoinApp + .getSharedPreferences().edit(); + sharedPreferencesEditor2.putInt(Constants.SHARED_PREFERENCES.VALUE_TO_CHECK, + NEW_VALUE_PRICE); sharedPreferencesEditor2.apply(); break; default: - Log.e(Constants.LOG.STAG, Constants.LOG.UNCAUGHT_ERROR + "SpinnerActivity.onItemSelected(AdapterView parent, View view, int position, long id)", new UnknownError()); + Log.e(Constants.LOG.STAG, Constants.LOG.UNCAUGHT_ERROR + + "SpinnerActivity.onItemSelected(AdapterView parent, View view, int position, long id)", + new UnknownError()); break; } } @@ -116,16 +137,21 @@ public void onNothingSelected(AdapterView parent) {} @Override public void onBackPressed() { Log.d(Constants.LOG.STAG, Constants.LOG.BACK_TO_MC); - if ((NEW_VALUE_DAYS != ACTUAL_DAYS) || (NEW_VALUE_PRICE != ACTUAL_PRICE) || (NEW_VALUE_ENABLED != ACTUAL_ENABLED)) { + if ((NEW_VALUE_DAYS != ACTUAL_DAYS) || (NEW_VALUE_PRICE != ACTUAL_PRICE) + || (NEW_VALUE_ENABLED != ACTUAL_ENABLED)) + { Toast.makeText(this, R.string.prefUpdated, Toast.LENGTH_LONG).show(); if (NEW_VALUE_ENABLED && !ACTUAL_ENABLED) BitCoinApp.forceRestartBackgroundJobs(); + refresh(); } - refresh(); + super.onBackPressed(); + overridePendingTransition(R.anim.activity_back_in, R.anim.activity_back_out); } private void refresh() { - Intent intentMain = new Intent(SpinnerActivity.this, MainActivity.class); + MainActivity.mainActivity.finish(); + Intent intentMain = new Intent(SpinnerActivity.this, DataLoaderScreen.class); intentMain.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); startActivity(intentMain); SpinnerActivity.this.finish(); diff --git a/app/src/main/res/anim/accelerate_interpolator.xml b/app/src/main/res/anim/accelerate_interpolator.xml new file mode 100644 index 0000000..4b1464b --- /dev/null +++ b/app/src/main/res/anim/accelerate_interpolator.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_back_in.xml b/app/src/main/res/anim/activity_back_in.xml new file mode 100644 index 0000000..f388432 --- /dev/null +++ b/app/src/main/res/anim/activity_back_in.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_back_out.xml b/app/src/main/res/anim/activity_back_out.xml new file mode 100644 index 0000000..9bf5df2 --- /dev/null +++ b/app/src/main/res/anim/activity_back_out.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..0214195 --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..4ba91c1 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_green_arrow_up_darker.xml b/app/src/main/res/drawable/ic_green_arrow_up_darker.xml new file mode 100644 index 0000000..d9a6424 --- /dev/null +++ b/app/src/main/res/drawable/ic_green_arrow_up_darker.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_red_arrow_down.xml b/app/src/main/res/drawable/ic_red_arrow_down.xml new file mode 100644 index 0000000..c79329c --- /dev/null +++ b/app/src/main/res/drawable/ic_red_arrow_down.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/launch_screen.xml b/app/src/main/res/drawable/launch_screen.xml new file mode 100644 index 0000000..d0843ef --- /dev/null +++ b/app/src/main/res/drawable/launch_screen.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_loading.xml b/app/src/main/res/layout/activity_loading.xml new file mode 100644 index 0000000..17347cf --- /dev/null +++ b/app/src/main/res/layout/activity_loading.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bitcoin_card.xml b/app/src/main/res/layout/bitcoin_card.xml index 580724f..099190e 100644 --- a/app/src/main/res/layout/bitcoin_card.xml +++ b/app/src/main/res/layout/bitcoin_card.xml @@ -9,10 +9,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" - android:layout_marginBottom="@dimen/card_margin" + android:layout_marginBottom="8dp" android:layout_marginEnd="@dimen/card_margin" - android:layout_marginLeft="@dimen/card_margin" - android:layout_marginRight="@dimen/card_margin" android:layout_marginStart="@dimen/card_margin" android:layout_marginTop="8dp" android:elevation="3dp" @@ -24,15 +22,33 @@ android:layout_height="wrap_content" android:orientation="vertical"> - + + + + + + - + app:layout_constraintTop_toBottomOf="@+id/include"> + + + diff --git a/app/src/main/res/layout/donations_activity.xml b/app/src/main/res/layout/donations_activity.xml index 453fcd8..f88ef64 100644 --- a/app/src/main/res/layout/donations_activity.xml +++ b/app/src/main/res/layout/donations_activity.xml @@ -48,7 +48,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" android:text="@string/donations__google_android_market_donate_button" /> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index eeb5d01..04d97b8 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -70,10 +70,17 @@ No hay conexión a Internet Esta aplicación necesita conexión a Internet para funcionar. Por favor, activa tu conexión de datos o conéctate a una red WiFi Aceptar - obtén información sobre las últimas pools de BTC y más
Copyright © 2017 - Javinator9889
Proyecto en GitHub]]>
+ obtén información sobre las últimas pools de BTC y más
Copyright © 2017 - Javinator9889
Proyecto en GitHub
Click para los Términos de Servicio completos]]>
GitHub]]> Versión 1.18 (06/03/18) + - Pantalla de carga: muestra un logotipo y una barra de progreso mientras cargan los datos de la app
+ - Nuevos iconos en las tarjetas: ahora podrás ver la evolución de los distintos valores del BitCoin
+ - Optimización de memoria y mejoras gráficas de la IU
+ - Animaciones en las transiciones entre pantallas
+ - Actualizado el diálogo de actualización: desde ahora se usará el canal oficial de Google Play Store
+ - Mejoras en la velocidad y en la optimización

Versión 1.17 (21/02/18)

- Optimización de la IU y corrección de errores
- Corregidos los datos del BTC
@@ -84,7 +91,7 @@ - Incluido Firebase - analíticas y más

Versión 1.15 (01/02/18)

GRAN ACTUALIZACIÓN CON UN MONTÓN DE CAMBIOS:
- - Añadidas nuevas optiones: vista en pestañas en la interfaz principal
+ - Añadidas nuevas opciones: vista en pestañas en la interfaz principal
- Código optimizado, la aplicación más rápida que nunca
- Información del BitCoin en tiempo real
- Nuevos gráficos, tarjetas y más
@@ -178,7 +185,15 @@ Descarga BitCoin Pools, una aplicación para obtener información sobre el BitCoin y más Cargando datos… Obteniendo la última información… Por favor, espera - ¿Te ha parecido útil la app? ¡Motívame a seguir trabjando!\nApóyame con una pequeña donación, te lo agradeceré :D + ¿Te ha parecido útil la app? ¡Motívame a seguir trabajando!\nApóyame con una pequeña donación, te lo agradeceré :D Google se queda con un 30% de lo que selecciones ¡Apóyame! + Se ha producido un error + + + Si ocurre esto a menudo, por favor contacta a esta dirección de correo + ]]> + + Porcentajes comparados con la fecha: \ No newline at end of file diff --git a/app/src/main/res/values/aboutlibraries_donations.xml b/app/src/main/res/values/aboutlibraries_donations.xml new file mode 100644 index 0000000..8113fca --- /dev/null +++ b/app/src/main/res/values/aboutlibraries_donations.xml @@ -0,0 +1,21 @@ + + + + + PrivacyApps + https://github.com/PrivacyApps/ + + Donations + Android Donations Lib supports donations by Google Play Store, Flattr, PayPal, and Bitcoin. + https://github.com/PrivacyApps/donations + 2.5 + + true + https://github.com/PrivacyApps/donations + + + + apache_2_0 + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index eb59481..20b7eda 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -9,4 +9,6 @@ #000000 #000000 #000030 + #00FF00 + #FF0000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 55c013b..2ad18ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -42,7 +42,9 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-

END OF TERMS AND CONDITIONS

]]>
+

END OF TERMS AND CONDITIONS

+ +]]>
More info about MIT license: https://goo.gl/tiitU5 View full project and other licenses here: https://goo.gl/LbLmBq Code based on: @@ -91,9 +93,17 @@ SOFTWARE.
No Internet connection This app requires Internet connection for working. Please, turn on your mobile data or connect to a WiFi network OK - get information about the latest BTC pools
and more
Copyright © 2017 - Javinator9889
GitHub project]]>
+ get information about the latest BTC pools and more
Copyright © 2017 - Javinator9889
GitHub project
Click for full Terms of Service]]>
GitHub]]> - + Version 1.18 (06/03/18) + - Load screen: now the logo is shown and also a progress bar while the app requests information
+ - New icons in cards: you will be able to see the evolution of the different BitCoin values
+ - Memory optimization and UI improvements
+ - Transitions between screens
+ - New update dialog: now the app is using official Google Play Store channel for updates
+ - Speed improvements and optimization too

Version 1.17 (21/02/18)

- UI optimizations and bug fixes
- BTC data corrected
@@ -210,4 +220,14 @@ SOFTWARE.
13 € (Euros) Support me! + There was an error + + + If this happens to you usually, please contact me to this email. + ]]> + + Percentages compared with the date: + Have you found this app useful? Support me and my work!\nDonate me what you want, I will be thankful :D + Google charges a fee of 30% diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 2c10e7e..1263c8e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -36,8 +36,9 @@ #FAFAFA -