├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── android.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── wiki │ │ └── fgo │ │ └── app │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── css │ │ │ └── content.css │ │ └── js │ │ │ └── filter.js │ ├── ic_launcher-playstore.png │ ├── java │ │ └── wiki │ │ │ └── fgo │ │ │ └── app │ │ │ ├── model │ │ │ └── UserViewModel.kt │ │ │ ├── ui │ │ │ ├── activity │ │ │ │ ├── AboutActivity.kt │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ └── StartActivity.kt │ │ │ ├── adapter │ │ │ │ └── TabAdapter.kt │ │ │ ├── fragment │ │ │ │ ├── MultiWebViewFragment.kt │ │ │ │ └── SwipeRefreshWebViewFragment.kt │ │ │ ├── notification │ │ │ │ ├── MiPushBroadcast.kt │ │ │ │ └── NetworkReceiver.kt │ │ │ ├── view │ │ │ │ └── ScaleImage.kt │ │ │ └── webview │ │ │ │ └── WebViewInit.kt │ │ │ └── utils │ │ │ ├── io │ │ │ └── MediaStoreHandler.kt │ │ │ └── network │ │ │ ├── ContentHandler.kt │ │ │ ├── HttpCallback.kt │ │ │ └── HttpUtil.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── about_page_card.xml │ │ ├── ic_action_favorite.xml │ │ ├── ic_action_favorite_empty.xml │ │ ├── ic_action_notice.xml │ │ ├── ic_action_reload.xml │ │ ├── ic_action_search.xml │ │ ├── ic_action_share.xml │ │ ├── ic_arrow_back.xml │ │ ├── ic_certificate.xml │ │ ├── ic_comment_slash.xml │ │ ├── ic_cubes.xml │ │ ├── ic_diagnoses.xml │ │ ├── ic_dice.xml │ │ ├── ic_download.xml │ │ ├── ic_expand.xml │ │ ├── ic_fire.xml │ │ ├── ic_gamepad.xml │ │ ├── ic_graduation_cap.xml │ │ ├── ic_hand_holding_heart.xml │ │ ├── ic_home.xml │ │ ├── ic_info_black.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_map.xml │ │ ├── ic_menu_camera.xml │ │ ├── ic_menu_gallery.xml │ │ ├── ic_menu_slideshow.xml │ │ ├── ic_microphone.xml │ │ ├── ic_music.xml │ │ ├── ic_o.xml │ │ ├── ic_paint_brush.xml │ │ ├── ic_scale.xml │ │ ├── ic_skull.xml │ │ ├── ic_street_view.xml │ │ ├── ic_tasks.xml │ │ ├── ic_tshirt.xml │ │ ├── ic_users.xml │ │ ├── ic_x.xml │ │ └── side_nav_bar.xml │ │ ├── layout │ │ ├── activity_about.xml │ │ ├── activity_main.xml │ │ ├── developers_list_item.xml │ │ ├── float_window.xml │ │ ├── fragment_multi.xml │ │ ├── item_mutable.xml │ │ ├── licenses_list_item.xml │ │ ├── nav_header.xml │ │ ├── swipe_refresh_webview.xml │ │ └── webview.xml │ │ ├── menu │ │ ├── sidebar.xml │ │ └── top_bar_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_float.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_float.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_float.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_float.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_float.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── wiki │ └── fgo │ └── app │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: BUILD & RELEASE 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths: 7 | - app/** 8 | tags: 9 | - v** 10 | - prod 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set Up JDK 1.8 17 | uses: actions/setup-java@v1 18 | with: 19 | java-version: 1.8 20 | - name: Write google-services.json 21 | run: echo '${{ secrets.GOOGLE_SERVICE }}' >> app/google-services.json 22 | - name: Get MiPush SDK 23 | run: 'mkdir app/libs && wget https://api.github.com/repos${{ secrets.FILE_PATH }} --header "Authorization: token ${{ secrets.TOKEN }}" --header "Accept: application/vnd.github.v3.raw" -O app/libs/MiPush_SDK_Client_3_7_5.jar' 24 | - name: Build with Gradle 25 | run: ./gradlew build -PMI_PUSH_APP_ID=${{ secrets.MI_PUSH_APP_ID }} -PMI_PUSH_APP_KEY=${{ secrets.MI_PUSH_APP_KEY }} 26 | - name: Create Release 27 | id: create_release 28 | uses: actions/create-release@v1 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | with: 32 | tag_name: ${{ github.run_id }} 33 | release_name: auto build 34 | body: debug version of ${{ github.sha }} 35 | draft: false 36 | prerelease: false 37 | - name: Upload Debug Asset 38 | id: upload-debug-asset 39 | uses: actions/upload-release-asset@v1 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | with: 43 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps 44 | asset_path: app/build/outputs/apk/debug/app-debug.apk 45 | asset_name: mooncell-client-debug.apk 46 | asset_content_type: application/vnd.android.package-archive 47 | - name: Upload Release Asset 48 | id: upload-release-asset 49 | uses: actions/upload-release-asset@v1 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | with: 53 | upload_url: ${{ steps.create_release.outputs.upload_url }} 54 | asset_path: app/build/outputs/apk/release/app-release-unsigned.apk 55 | asset_name: mooncell-client-release-unsigned.apk 56 | asset_content_type: application/vnd.android.package-archive 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | .idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | /app/google-services.json 11 | /app/release/ 12 | /app/libs/MiPush_SDK_Client_3_7_5.jar -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Attribution-NonCommercial-ShareAlike 4.0 International 2 | 3 | Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. 4 | 5 | ### Using Creative Commons Public Licenses 6 | 7 | Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. 8 | 9 | * __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). 10 | 11 | * __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). 12 | 13 | ## Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License 14 | 15 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 16 | 17 | ### Section 1 – Definitions. 18 | 19 | a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 20 | 21 | b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. 22 | 23 | c. __BY-NC-SA Compatible License__ means a license listed at [creativecommons.org/compatiblelicenses](http://creativecommons.org/compatiblelicenses), approved by Creative Commons as essentially the equivalent of this Public License. 24 | 25 | d. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 26 | 27 | e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 28 | 29 | f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 30 | 31 | g. __License Elements__ means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution, NonCommercial, and ShareAlike. 32 | 33 | h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 34 | 35 | i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 36 | 37 | h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License. 38 | 39 | i. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. 40 | 41 | j. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 42 | 43 | k. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 44 | 45 | l. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 46 | 47 | ### Section 2 – Scope. 48 | 49 | a. ___License grant.___ 50 | 51 | 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 52 | 53 | A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and 54 | 55 | B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only. 56 | 57 | 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 58 | 59 | 3. __Term.__ The term of this Public License is specified in Section 6(a). 60 | 61 | 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 62 | 63 | 5. __Downstream recipients.__ 64 | 65 | A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 66 | 67 | B. __Additional offer from the Licensor – Adapted Material.__ Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply. 68 | 69 | C. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 70 | 71 | 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 72 | 73 | b. ___Other rights.___ 74 | 75 | 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 76 | 77 | 2. Patent and trademark rights are not licensed under this Public License. 78 | 79 | 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. 80 | 81 | ### Section 3 – License Conditions. 82 | 83 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 84 | 85 | a. ___Attribution.___ 86 | 87 | 1. If You Share the Licensed Material (including in modified form), You must: 88 | 89 | A. retain the following if it is supplied by the Licensor with the Licensed Material: 90 | 91 | i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 92 | 93 | ii. a copyright notice; 94 | 95 | iii. a notice that refers to this Public License; 96 | 97 | iv. a notice that refers to the disclaimer of warranties; 98 | 99 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 100 | 101 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 102 | 103 | C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 104 | 105 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 106 | 107 | 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 108 | 109 | b. ___ShareAlike.___ 110 | 111 | In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. 112 | 113 | 1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-NC-SA Compatible License. 114 | 115 | 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. 116 | 117 | 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. 118 | 119 | ### Section 4 – Sui Generis Database Rights. 120 | 121 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 122 | 123 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only; 124 | 125 | b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and 126 | 127 | c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 128 | 129 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 130 | 131 | ### Section 5 – Disclaimer of Warranties and Limitation of Liability. 132 | 133 | a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__ 134 | 135 | b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__ 136 | 137 | c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 138 | 139 | ### Section 6 – Term and Termination. 140 | 141 | a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 142 | 143 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 144 | 145 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 146 | 147 | 2. upon express reinstatement by the Licensor. 148 | 149 | For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 150 | 151 | c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 152 | 153 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 154 | 155 | ### Section 7 – Other Terms and Conditions. 156 | 157 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 158 | 159 | b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 160 | 161 | ### Section 8 – Interpretation. 162 | 163 | a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 164 | 165 | b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 166 | 167 | c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 168 | 169 | d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. 170 | 171 | > Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. 172 | > 173 | > Creative Commons may be contacted at creativecommons.org 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: CC BY-NC-SA 4.0](https://licensebuttons.net/l/by-nc-sa/4.0/80x15.png)](https://creativecommons.org/licenses/by-nc-sa/4.0/) 2 | [![Language](https://img.shields.io/badge/language-kotlin-orange.svg)](https://kotlinlang.org/) 3 | ![BUILD & RELEASE](https://github.com/StarHeartHunt/Mooncell-Client-Kotlin/workflows/BUILD%20&%20RELEASE/badge.svg) 4 | # Mooncell-Client-Kotlin 5 | this repo contains the source code for the official [fgo.wiki android client](https://fgo.wiki/w/Mooncell:Appclient) 6 | ## build 7 | 1. move your MiPush_SDK_Client_3_7_5.jar to /app/libs 8 | 2. move your google-services.json to app/ 9 | 3. In your android studio, File>Settings>BUILD,Execution,Deployment>Compiler>Command-line Options, fill the blank with: 10 |
-PMI_PUSH_APP_ID=yourMiPushAppId -PMI_PUSH_APP_KEY=yourMiPushAppKey
11 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | apply plugin: 'com.google.gms.google-services' 8 | 9 | apply plugin: 'com.google.firebase.firebase-perf' 10 | 11 | apply plugin: 'com.google.firebase.crashlytics' 12 | 13 | android { 14 | compileSdkVersion 29 15 | buildToolsVersion "29.0.2" 16 | defaultConfig { 17 | applicationId "wiki.fgo.app" 18 | minSdkVersion 23 19 | targetSdkVersion 29 20 | versionCode 14 21 | versionName "1.4.4" 22 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 23 | def MI_PUSH_APP_ID = project.getProperties().get("MI_PUSH_APP_ID") 24 | def MI_PUSH_APP_KEY = project.getProperties().get("MI_PUSH_APP_KEY") 25 | buildConfigField "String", "MI_PUSH_APP_ID", "\"${MI_PUSH_APP_ID}\"" 26 | buildConfigField "String", "MI_PUSH_APP_KEY", "\"${MI_PUSH_APP_KEY}\"" 27 | } 28 | applicationVariants.all { variant -> 29 | variant.resValue "string", "versionName", "版本号: " + variant.versionName 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | // To inline the bytecode built with JVM target 1.8 into 38 | // bytecode that is being built with JVM target 1.6. (e.g. navArgs) 39 | 40 | 41 | compileOptions { 42 | sourceCompatibility JavaVersion.VERSION_1_8 43 | targetCompatibility JavaVersion.VERSION_1_8 44 | } 45 | kotlinOptions { 46 | jvmTarget = "1.8" 47 | } 48 | } 49 | 50 | dependencies { 51 | implementation 'com.drakeet.multitype:multitype:4.2.0' 52 | implementation 'com.github.yhaolpz:FloatWindow:1.0.9' 53 | implementation 'com.github.bumptech.glide:glide:4.9.0' 54 | implementation 'com.github.salomonbrys.kotson:kotson:2.5.0' 55 | implementation 'com.github.princekin-f:EasyFloat:1.3.0' 56 | implementation 'com.google.firebase:firebase-perf:19.1.0' 57 | implementation 'com.google.firebase:firebase-analytics:18.0.2' 58 | implementation 'com.google.firebase:firebase-crashlytics:17.3.1' 59 | implementation fileTree(include: ['*.jar'], dir: 'libs') 60 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 61 | implementation 'androidx.navigation:navigation-fragment:2.3.0' 62 | implementation 'androidx.navigation:navigation-ui:2.3.0' 63 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 64 | implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0' 65 | implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' 66 | "org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.3.0-alpha01" 67 | implementation 'androidx.appcompat:appcompat:1.1.0' 68 | implementation 'androidx.core:core-ktx:1.3.1' 69 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 70 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' 71 | implementation 'androidx.navigation:navigation-fragment:2.3.0' 72 | implementation 'androidx.navigation:navigation-ui:2.3.0' 73 | implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0' 74 | implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' 75 | implementation 'androidx.navigation:navigation-dynamic-features-fragment:2.3.0' 76 | implementation 'com.google.android.material:material:1.1.0' 77 | testImplementation 'junit:junit:4.12' 78 | androidTestImplementation 'androidx.navigation:navigation-testing:2.3.0' 79 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 80 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 81 | implementation files('libs/MiPush_SDK_Client_3_7_5.jar') 82 | } 83 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/wiki/fgo/app/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("wiki.fgo.app", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 54 | 60 | 64 | 67 | 68 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 81 | 82 | 83 | 84 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /app/src/main/assets/css/content.css: -------------------------------------------------------------------------------- 1 | .header-container{ 2 | display:none; 3 | } 4 | 5 | .minerva-footer{ 6 | display:none; 7 | } -------------------------------------------------------------------------------- /app/src/main/assets/js/filter.js: -------------------------------------------------------------------------------- 1 | var style = document.createElement("style"); 2 | style.type = "text/css"; 3 | style.innerHTML=".header-container{display:none;}.minerva-footer{display:none;}"; 4 | style.id="addStyle"; 5 | document.getElementsByTagName("HEAD").item(0).appendChild(style); -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/model/UserViewModel.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.model 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | 7 | class UserViewModel : ViewModel() { 8 | private val userName = MutableLiveData("未登录") 9 | private val userId = MutableLiveData("") 10 | fun getUserName(): LiveData { 11 | return userName 12 | } 13 | 14 | fun getUserId(): LiveData { 15 | return userId 16 | } 17 | 18 | fun userName(name: String) { 19 | userName.value = name 20 | } 21 | 22 | fun userId(id: String) { 23 | userId.value = id 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/ui/activity/AboutActivity.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.ui.activity 2 | 3 | import android.content.Intent 4 | import android.graphics.Rect 5 | import android.net.Uri 6 | import android.os.Bundle 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.ImageView 11 | import android.widget.TextView 12 | import androidx.appcompat.app.AppCompatActivity 13 | import androidx.recyclerview.widget.LinearLayoutManager 14 | import androidx.recyclerview.widget.RecyclerView 15 | import kotlinx.android.synthetic.main.activity_about.* 16 | import wiki.fgo.app.R 17 | 18 | 19 | class AboutActivity : AppCompatActivity() { 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.activity_about) 24 | setSupportActionBar(toolbar) 25 | toolbar.setNavigationIcon(R.drawable.ic_arrow_back) 26 | toolbar.setNavigationOnClickListener(fun(_: View) { 27 | finish() 28 | }) 29 | val list = findViewById(R.id.licenses_list) 30 | list.adapter = MyAdapter(data) 31 | list.layoutManager = LinearLayoutManager(this) 32 | list.addItemDecoration(MyItemDecoration()) 33 | val list1 = findViewById(R.id.developers_list) 34 | list1.adapter = DeveloperAdapter(developerData) 35 | list1.layoutManager = LinearLayoutManager(this) 36 | list1.addItemDecoration(MyItemDecoration()) 37 | } 38 | 39 | private val data = arrayOf( 40 | mapOf( 41 | "name" to "Glide", 42 | "source" to "https://github.com/bumptech/glide", 43 | "license" to "https://raw.githubusercontent.com/bumptech/glide/master/LICENSE" 44 | ), 45 | mapOf( 46 | "name" to "OkHttp", 47 | "source" to "https://github.com/square/okhttp", 48 | "license" to "https://raw.githubusercontent.com/square/okhttp/master/LICENSE.txt" 49 | ), 50 | mapOf( 51 | "name" to "FloatWindow", 52 | "source" to "https://github.com/yhaolpz/FloatWindow", 53 | "license" to "https://raw.githubusercontent.com/yhaolpz/FloatWindow/master/LICENSE.txt" 54 | ), 55 | mapOf( 56 | "name" to "EasyFloat", 57 | "source" to "https://github.com/princekin-f/EasyFloat", 58 | "license" to "https://raw.githubusercontent.com/princekin-f/EasyFloat/master/LICENSE" 59 | ), 60 | mapOf( 61 | "name" to "Demo_MiPush", 62 | "source" to "https://github.com/Carson-Ho/Demo_MiPush", 63 | "license" to "https://github.com/Carson-Ho/Demo_MiPush" 64 | ), 65 | mapOf( 66 | "name" to "about-page", 67 | "source" to "https://github.com/PureWriter/about-page", 68 | "license" to "https://raw.githubusercontent.com/PureWriter/about-page/master/LICENSE" 69 | ) 70 | ) 71 | 72 | private val developerData = arrayOf( 73 | mapOf( 74 | "name" to "StarHeartHunt", 75 | "avatar" to "http://avatar.mooncell.wiki/mc/558/128.png", 76 | "desc" to "Main Developer" 77 | ), 78 | mapOf( 79 | "name" to "夕舞八弦", 80 | "avatar" to "http://avatar.mooncell.wiki/mc/558/128.png", 81 | "desc" to "Main Developer" 82 | ) 83 | ) 84 | 85 | inner class MyAdapter(private val list: Array>) : 86 | RecyclerView.Adapter() { 87 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { 88 | val v: View = LayoutInflater.from(parent.context) 89 | .inflate(R.layout.licenses_list_item, parent, false) 90 | return MyViewHolder(v) 91 | } 92 | 93 | override fun getItemCount(): Int { 94 | return list.size 95 | } 96 | 97 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) { 98 | holder.name.text = list[position]["name"] 99 | holder.source.text = list[position]["source"] 100 | holder.license.text = list[position]["license"] 101 | holder.itemView.setOnClickListener { 102 | val contentUrl: Uri = Uri.parse(holder.source.text as String?) 103 | val intent = Intent(Intent.ACTION_VIEW, contentUrl) 104 | startActivity(intent) 105 | } 106 | } 107 | } 108 | 109 | inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) { 110 | val name: TextView = view.findViewById(R.id.dependent_name) 111 | val source: TextView = view.findViewById(R.id.dependent_source) 112 | val license: TextView = view.findViewById(R.id.dependent_license) 113 | } 114 | 115 | inner class MyItemDecoration : RecyclerView.ItemDecoration() { 116 | override fun getItemOffsets( 117 | outRect: Rect, 118 | view: View, 119 | parent: RecyclerView, 120 | state: RecyclerView.State 121 | ) { 122 | outRect.set(0, 0, 0, 3) 123 | } 124 | } 125 | 126 | inner class DeveloperAdapter(private val list1: Array>) : 127 | RecyclerView.Adapter() { 128 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeveloperViewHolder { 129 | val v: View = LayoutInflater.from(parent.context) 130 | .inflate(R.layout.developers_list_item, parent, false) 131 | return DeveloperViewHolder(v) 132 | } 133 | 134 | override fun getItemCount(): Int { 135 | return list1.size 136 | } 137 | 138 | override fun onBindViewHolder(holder: DeveloperViewHolder, position: Int) { 139 | holder.avatar.setImageDrawable(resources.getDrawable(R.mipmap.ic_launcher, null)) 140 | holder.name.text = list1[position]["name"] 141 | holder.desc.text = list1[position]["desc"] 142 | } 143 | } 144 | 145 | inner class DeveloperViewHolder(view: View) : RecyclerView.ViewHolder(view) { 146 | val avatar: ImageView = view.findViewById(R.id.avatar) 147 | val name: TextView = view.findViewById(R.id.name) 148 | val desc: TextView = view.findViewById(R.id.desc) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/ui/activity/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.ui.activity 2 | 3 | import android.app.ActivityManager 4 | import android.app.Application 5 | import android.content.Context 6 | import android.content.IntentFilter 7 | import android.net.ConnectivityManager 8 | import android.os.Handler 9 | import android.os.HandlerThread 10 | import android.os.Message 11 | import android.os.Process 12 | import com.xiaomi.mipush.sdk.MiPushClient 13 | import wiki.fgo.app.BuildConfig 14 | import wiki.fgo.app.ui.notification.NetworkReceiver 15 | 16 | 17 | //主要要继承Application 18 | class BaseActivity : Application() { 19 | //网络监听变量 20 | var netWorkStateReceiver: NetworkReceiver? = null 21 | 22 | //回调线程 23 | var mHandlerThread: HandlerThread? = null 24 | 25 | //为了提高推送服务的注册率,我建议在Application的onCreate中初始化推送服务 26 | //你也可以根据需要,在其他地方初始化推送服务 27 | override fun onCreate() { 28 | super.onCreate() 29 | 30 | //创建回调线程 31 | initBackground() 32 | if (shouldInit()) { 33 | //注册推送服务 34 | //注册成功后会向DemoMessageReceiver发送广播 35 | // 可以从DemoMessageReceiver的onCommandResult方法中MiPushCommandMessage对象参数中获取注册信息 36 | MiPushClient.registerPush( 37 | this, 38 | APP_ID, 39 | APP_KEY 40 | ) 41 | } 42 | } 43 | 44 | //创建回调线程 45 | private fun initBackground() { 46 | //通过实例化mHandlerThread从而创建新线程 47 | mHandlerThread = HandlerThread("handlerThread") 48 | //启动新线程 49 | mHandlerThread!!.start() 50 | //消息处理的操作 51 | AppHandler = 52 | object : Handler(mHandlerThread!!.looper) { 53 | override fun handleMessage(msg: Message) { 54 | 55 | //0=立即注册推送服务,1=延时注册推送服务,2=注册广播,3=注销广播 56 | when (msg.what) { 57 | 0 -> { 58 | println("立即注册推送服务") 59 | startPush() 60 | } 61 | 1 -> { 62 | println("正在回调延时(1分钟)注册推送服务....") 63 | try { 64 | Thread.sleep(10000) 65 | } catch (e: InterruptedException) { 66 | e.printStackTrace() 67 | } 68 | println("延时完毕,开始重新注册推送服务") 69 | startPush() 70 | } 71 | 2 -> { 72 | println("正在回调注册广播...") 73 | regBroadcastReceiver() 74 | } 75 | 3 -> { 76 | println("正在回调注销广播...") 77 | unRegBroadcastReceiver() 78 | } 79 | else -> { 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | //通过判断手机里的所有进程是否有这个App的进程 87 | //从而判断该App是否有打开 88 | private fun shouldInit(): Boolean { 89 | 90 | //通过ActivityManager我们可以获得系统里正在运行的activities 91 | //包括进程(Process)等、应用程序/包、服务(Service)、任务(Task)信息。 92 | val am = 93 | getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager 94 | val processInfos = 95 | am.runningAppProcesses 96 | val mainProcessName = packageName 97 | 98 | //获取本App的唯一标识 99 | val myPid = Process.myPid() 100 | //利用一个增强for循环取出手机里的所有进程 101 | for (info in processInfos) { 102 | //通过比较进程的唯一标识和包名判断进程里是否存在该App 103 | if (info.pid == myPid && mainProcessName == info.processName) { 104 | return true 105 | } 106 | } 107 | return false 108 | } 109 | 110 | //注册推送服务 111 | fun startPush() { 112 | if (shouldInit() && pushState != 3) { 113 | println("注册推送服务") 114 | MiPushClient.registerPush( 115 | this, 116 | APP_ID, 117 | APP_KEY 118 | ) 119 | } 120 | } 121 | 122 | //注册广播 123 | fun regBroadcastReceiver() { 124 | netWorkStateReceiver = NetworkReceiver() 125 | val filter = IntentFilter() 126 | filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION) 127 | registerReceiver(netWorkStateReceiver, filter) 128 | println("注册广播成功") 129 | broadcastNet_State = 2 130 | } 131 | 132 | //注销广播 133 | fun unRegBroadcastReceiver() { 134 | try { 135 | unregisterReceiver(netWorkStateReceiver) 136 | println("注销广播成功") 137 | broadcastNet_State = 1 138 | } 139 | catch (e: IllegalArgumentException) { 140 | println("注销广播失败") 141 | e.printStackTrace() 142 | } 143 | } 144 | 145 | // 程序终止的时候执行 146 | override fun onTerminate() { 147 | println("程序终止的时候执行") 148 | releaseHandler() 149 | super.onTerminate() 150 | } 151 | 152 | //释放线程,防止内存泄露 153 | private fun releaseHandler() { 154 | AppHandler!!.removeCallbacksAndMessages(null) 155 | } 156 | 157 | companion object { 158 | 159 | private const val APP_ID = BuildConfig.MI_PUSH_APP_ID 160 | 161 | private const val APP_KEY = BuildConfig.MI_PUSH_APP_KEY 162 | 163 | //推送服务状态量 164 | var pushState = 1 165 | 166 | //网络检测注册广播状态量 167 | var broadcastNet_State = 1 168 | var AppHandler: Handler? = null 169 | } 170 | } -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/ui/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.ui.activity 2 | 3 | import android.Manifest 4 | import android.annotation.SuppressLint 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.content.SharedPreferences 8 | import android.content.pm.PackageManager 9 | import android.net.Uri 10 | import android.os.Build 11 | import android.os.Bundle 12 | import android.os.Handler 13 | import android.os.Message 14 | import android.util.Log 15 | import android.view.* 16 | import android.webkit.WebChromeClient 17 | import android.webkit.WebSettings 18 | import android.webkit.WebView 19 | import android.webkit.WebViewClient 20 | import android.widget.CheckBox 21 | import android.widget.FrameLayout 22 | import android.widget.ImageView 23 | import android.widget.RelativeLayout 24 | import androidx.appcompat.app.ActionBarDrawerToggle 25 | import androidx.appcompat.app.AlertDialog 26 | import androidx.appcompat.app.AppCompatActivity 27 | import androidx.appcompat.widget.SearchView 28 | import androidx.core.app.ActivityCompat 29 | import androidx.core.content.ContextCompat 30 | import androidx.core.view.GravityCompat 31 | import androidx.fragment.app.Fragment 32 | import androidx.fragment.app.FragmentTransaction 33 | import androidx.lifecycle.Observer 34 | import androidx.lifecycle.ViewModelProvider 35 | import androidx.navigation.findNavController 36 | import androidx.navigation.ui.AppBarConfiguration 37 | import androidx.navigation.ui.navigateUp 38 | import com.bumptech.glide.Glide 39 | import com.bumptech.glide.load.engine.DiskCacheStrategy 40 | import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade 41 | import com.github.salomonbrys.kotson.fromJson 42 | import com.github.salomonbrys.kotson.get 43 | import com.google.android.material.navigation.NavigationView 44 | import com.google.gson.Gson 45 | import com.google.gson.JsonElement 46 | import com.lzf.easyfloat.EasyFloat 47 | import com.lzf.easyfloat.enums.ShowPattern 48 | import com.lzf.easyfloat.interfaces.OnInvokeView 49 | import com.lzf.easyfloat.interfaces.OnPermissionResult 50 | import com.lzf.easyfloat.permission.PermissionUtils 51 | import com.yhao.floatwindow.FloatWindow 52 | import com.yhao.floatwindow.Screen 53 | import kotlinx.android.synthetic.main.activity_main.* 54 | import kotlinx.android.synthetic.main.float_window.view.* 55 | import kotlinx.android.synthetic.main.nav_header.* 56 | import okhttp3.Call 57 | import okhttp3.Callback 58 | import okhttp3.Response 59 | import wiki.fgo.app.BuildConfig 60 | import wiki.fgo.app.R 61 | import wiki.fgo.app.model.UserViewModel 62 | import wiki.fgo.app.ui.fragment.MultiWebViewFragment 63 | import wiki.fgo.app.ui.fragment.SwipeRefreshWebViewFragment 64 | import wiki.fgo.app.ui.view.ScaleImage 65 | import wiki.fgo.app.utils.network.HttpUtil 66 | import wiki.fgo.app.utils.network.HttpUtil.Companion.avatarUrlConcat 67 | import wiki.fgo.app.utils.network.HttpUtil.Companion.urlConcat 68 | import java.io.IOException 69 | import java.util.regex.Matcher 70 | import java.util.regex.Pattern 71 | 72 | 73 | class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { 74 | private val sidebarFetchUrl = 75 | "https://m.fgo.wiki/api.php?action=parse&format=json&page=%E6%A8%A1%E6%9D%BF%3AMFSidebarAutoEvents/App&disablelimitreport=1" 76 | 77 | private val checkUpdateUrl = 78 | "https://fgo.wiki/images/wiki/merlin/client/update.json" 79 | 80 | private var isFloatBallCreated: Boolean? = false 81 | 82 | private var searchBaseUrl: String = "https://fgo.wiki/index.php?search=" 83 | 84 | private lateinit var appBarConfiguration: AppBarConfiguration 85 | 86 | private val PERMISSIONS_MIPUSH_GROUP = 1 87 | 88 | lateinit var sharedPref: SharedPreferences 89 | 90 | private lateinit var user: UserViewModel 91 | 92 | private lateinit var headIv: ImageView 93 | 94 | private lateinit var fc: FragmentsController 95 | 96 | // 当前活动 title查url 97 | private var recentActivityTitleUrlMap: MutableMap = 98 | mutableMapOf() 99 | 100 | inner class FragmentsController(private var isTab: Boolean) { 101 | var isFirst = true 102 | private val fragments = mutableMapOf() 103 | private fun beMulti(): Fragment { 104 | if (fragments[1] == null) { 105 | fragments[1] = MultiWebViewFragment() 106 | } 107 | isTab = true 108 | return fragments[1]!! 109 | } 110 | 111 | private fun beSingle(): Fragment { 112 | if (fragments[0] == null) { 113 | fragments[0] = SwipeRefreshWebViewFragment() 114 | } 115 | isTab = false 116 | return fragments[0]!! 117 | } 118 | 119 | fun switch(tran: FragmentTransaction) { 120 | val f: Fragment 121 | val curr = getCurr() 122 | if (isTab) { 123 | isTab = false 124 | f = beSingle() 125 | } else { 126 | isTab = true 127 | f = beMulti() 128 | } 129 | if (isFirst) { 130 | tran.hide(curr) 131 | tran.add(R.id.fragment_container_view, f) 132 | tran.commit() 133 | isFirst = false 134 | } else { 135 | tran.hide(curr) 136 | tran.show(f) 137 | tran.commit() 138 | } 139 | } 140 | 141 | fun getCurr(): Fragment { 142 | return if (isTab) { 143 | beMulti() 144 | } else { 145 | beSingle() 146 | } 147 | } 148 | 149 | fun getIsTab(): Boolean { 150 | return isTab 151 | } 152 | } 153 | 154 | private fun getCurrentWebView(): WebView { 155 | val curr = fc.getCurr() 156 | return if (fc.getIsTab()) { 157 | (curr as MultiWebViewFragment).getCurrentWebViewFragment()!!.webView 158 | } else { 159 | (curr as SwipeRefreshWebViewFragment).webView 160 | } 161 | } 162 | 163 | override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { 164 | R.id.action_float -> { 165 | if (PermissionUtils.checkPermission(this)) { 166 | if (isFloatBallCreated == false) { 167 | createFloatBall() 168 | isFloatBallCreated = true 169 | } 170 | FloatWindow.get().hide() 171 | EasyFloat.with(this) 172 | .setLayout(R.layout.float_window, OnInvokeView { it -> 173 | it.findViewById(R.id.float_webView).setFloatWebView() 174 | val url = getCurrentWebView().url 175 | it.findViewById(R.id.float_webView).loadUrl(url) 176 | it.findViewById(R.id.ivClose).setOnClickListener { 177 | EasyFloat.dismissAppFloat() 178 | if (isFloatBallCreated == true) { 179 | FloatWindow.get().show() 180 | FloatWindow.destroy() 181 | isFloatBallCreated = false 182 | } 183 | } 184 | it.findViewById(R.id.checkbox) 185 | .setOnCheckedChangeListener { _, isChecked -> 186 | EasyFloat.appFloatDragEnable(isChecked) 187 | } 188 | it.findViewById(R.id.ivFloatBallCheck).setOnClickListener { 189 | EasyFloat.hideAppFloat() 190 | FloatWindow.get().show() 191 | } 192 | val floatWebviewTemp = it.findViewById(R.id.float_webView) 193 | it.findViewById(R.id.ivLeftArrow).setOnClickListener { 194 | if (floatWebviewTemp.canGoBack()) { 195 | floatWebviewTemp.goBack() 196 | } 197 | } 198 | val content = it.findViewById(R.id.rlContent) 199 | val params = content.layoutParams as FrameLayout.LayoutParams 200 | it.findViewById( 201 | R.id.ivScale 202 | ).onScaledListener = 203 | object : ScaleImage.OnScaledListener { 204 | override fun onScaled(x: Float, y: Float, event: MotionEvent) { 205 | params.width += x.toInt() 206 | params.height += y.toInt() 207 | content.layoutParams = params 208 | } 209 | } 210 | }) 211 | .setShowPattern(ShowPattern.ALL_TIME) 212 | .show() 213 | } else { 214 | AlertDialog.Builder(this) 215 | .setMessage("若要使用悬浮窗功能,您需要授权Mooncell悬浮窗权限。") 216 | .setPositiveButton("去开启") { _, _ -> 217 | requestFloatPermission() 218 | } 219 | .setNegativeButton("取消") { _, _ -> } 220 | .show() 221 | } 222 | true 223 | } 224 | 225 | R.id.action_login -> { 226 | if (user.getUserId().value == "") { 227 | getCurrentWebView().loadUrl("https://fgo.wiki/w/特殊:用户登录") 228 | } else { 229 | getCurrentWebView().loadUrl("https://fgo.wiki/w/特殊:用户退出") 230 | } 231 | true 232 | } 233 | 234 | R.id.action_switch -> { 235 | fc.switch(supportFragmentManager.beginTransaction()) 236 | with(sharedPref.edit()) { 237 | putBoolean("isTab", fc.getIsTab()) 238 | apply() 239 | } 240 | true 241 | } 242 | 243 | R.id.action_notice -> { 244 | getCurrentWebView().loadUrl("https://fgo.wiki/w/特殊:通知") 245 | true 246 | } 247 | 248 | R.id.action_settings -> { 249 | getCurrentWebView().loadUrl("https://fgo.wiki/w/特殊:参数设置") 250 | true 251 | } 252 | 253 | R.id.action_exit -> { 254 | finish() 255 | true 256 | } 257 | 258 | /* 259 | TODO 260 | R.id.action_favorite -> { 261 | if (!isChecked) { 262 | my_toolbar.menu.findItem(R.id.action_favorite).icon = 263 | ContextCompat.getDrawable(this, R.drawable.ic_action_favorite) 264 | isChecked = true 265 | Snackbar.make(webView, "收藏成功", Snackbar.LENGTH_SHORT).show() 266 | } else { 267 | my_toolbar.menu.findItem(R.id.action_favorite).icon = 268 | ContextCompat.getDrawable(this, R.drawable.ic_action_favorite_empty) 269 | isChecked = false 270 | Snackbar.make(webView, "取消收藏", Snackbar.LENGTH_SHORT).show() 271 | } 272 | true 273 | } 274 | 275 | */ 276 | else -> { 277 | // If we got here, the user's action was not recognized. 278 | // Invoke the superclass to handle it. 279 | super.onOptionsItemSelected(item) 280 | } 281 | } 282 | 283 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 284 | // Inflate the menu; this adds items to the action bar if it is present. 285 | if (menu !== null) { 286 | menuInflater.inflate(R.menu.top_bar_menu, menu) 287 | } 288 | return true 289 | } 290 | 291 | override fun onPrepareOptionsMenu(menu: Menu?): Boolean { 292 | menu!!.findItem(R.id.action_switch).title = 293 | if (fc.getIsTab()) "单栏模式" else "多栏模式" 294 | if (user.getUserId().value == "") { 295 | menu.findItem(R.id.action_notice).isVisible = false 296 | menu.findItem(R.id.action_login).title = "登录" 297 | } else { 298 | menu.findItem(R.id.action_notice).isVisible = true 299 | menu.findItem(R.id.action_login).title = "登出" 300 | } 301 | return super.onPrepareOptionsMenu(menu) 302 | } 303 | 304 | override fun onSupportNavigateUp(): Boolean { 305 | val navController = findNavController(R.id.nav_host_fragment) 306 | return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() 307 | } 308 | 309 | override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { 310 | // Check if the key event was the Back button and if there's history 311 | if (keyCode == KeyEvent.KEYCODE_BACK && getCurrentWebView().canGoBack() && !drawer_layout.isDrawerOpen( 312 | GravityCompat.START 313 | ) 314 | ) { 315 | getCurrentWebView().goBack() 316 | return true 317 | } 318 | // If it wasn't the Back key or there's no web page history, bubble up to the default 319 | // system behavior (probably exit the activity) 320 | return super.onKeyDown(keyCode, event) 321 | } 322 | 323 | override fun onBackPressed() { 324 | if (drawer_layout.isDrawerOpen(GravityCompat.START)) { 325 | drawer_layout.closeDrawer(GravityCompat.START) 326 | } 327 | if (!m_search_view.isIconified) { 328 | m_search_view.isIconified = true 329 | } else { 330 | super.onBackPressed() 331 | } 332 | } 333 | 334 | override fun onNavigationItemSelected(item: MenuItem): Boolean { 335 | if (item.groupId == 1) { 336 | //当前活动 337 | val url = this.recentActivityTitleUrlMap[item.title]; 338 | if (url != null) { 339 | closeDrawerAfterClick(item, url); 340 | return true 341 | } 342 | } 343 | when (item.itemId) { 344 | R.id.main_page -> closeDrawerAfterClick(item) 345 | R.id.svt_overview -> closeDrawerAfterClick(item) 346 | R.id.ce_overview -> closeDrawerAfterClick(item, "礼装图鉴") 347 | R.id.cc_overview -> closeDrawerAfterClick(item) 348 | R.id.weekly_mission -> closeDrawerAfterClick(item, "御主任务/周常") 349 | R.id.new_cards -> closeDrawerAfterClick(item, "模板:新增卡牌") 350 | R.id.simulate_gacha -> closeDrawerAfterClick(item, "抽卡模拟器") 351 | R.id.quest -> closeDrawerAfterClick(item) 352 | R.id.enemy_overview -> closeDrawerAfterClick(item) 353 | R.id.items_overview -> closeDrawerAfterClick(item) 354 | R.id.skill_overview -> closeDrawerAfterClick(item) 355 | R.id.mst_equip -> closeDrawerAfterClick(item) 356 | R.id.clothes_overview -> closeDrawerAfterClick(item) 357 | R.id.music_overview -> closeDrawerAfterClick(item) 358 | R.id.cv_overview -> closeDrawerAfterClick(item) 359 | R.id.illust_overview -> closeDrawerAfterClick(item) 360 | R.id.jp_client_dl -> closeDrawerAfterClick(item, "Mooncell:Jpclient") 361 | R.id.sponsor -> closeDrawerAfterClick( 362 | item, 363 | "Mooncell:如何帮助我们完善网站#除了贡献内容外,您还可以资助我们改善服务器资源" 364 | ) 365 | R.id.faq -> closeDrawerAfterClick(item, "Mooncell:反馈与建议") 366 | R.id.comment -> closeDrawerAfterClick(item, "Mooncell:评论须知") 367 | R.id.action_about -> closeDrawerAfterClick(item, "Mooncell:关于") 368 | R.id.action_about_client -> { 369 | val intent = Intent() 370 | intent.setClass(this, AboutActivity::class.java) 371 | startActivity(intent) 372 | } 373 | else -> activityClickListener(item) 374 | } 375 | return true 376 | } 377 | 378 | override fun onCreate(savedInstanceState: Bundle?) { 379 | super.onCreate(savedInstanceState) 380 | setContentView(R.layout.activity_main) 381 | setSupportActionBar(findViewById(R.id.my_toolbar)) 382 | checkPermissions() 383 | sharedPref = getPreferences(Context.MODE_PRIVATE) 384 | user = ViewModelProvider(this).get(UserViewModel::class.java) 385 | readLogUserPreference() 386 | initDrawer() 387 | setDrawer() 388 | fc = FragmentsController(sharedPref.getBoolean("isTab", false)) 389 | val tran = supportFragmentManager.beginTransaction() 390 | tran.add(R.id.fragment_container_view, fc.getCurr()) 391 | tran.commit() 392 | sendRequestWithOkHttp(sidebarFetchUrl, 1) 393 | sendRequestWithOkHttp(checkUpdateUrl, 2) 394 | setQueryListener() 395 | supportActionBar?.setDisplayShowTitleEnabled(false) 396 | user.getUserName().observe(this, Observer { 397 | with(sharedPref.edit()) { 398 | putString("userName", it) 399 | apply() 400 | } 401 | }) 402 | user.getUserId().observe(this, Observer { 403 | try { 404 | Glide.with(this) 405 | .load(avatarUrlConcat(it)) 406 | .transition(withCrossFade()) 407 | .centerCrop() 408 | .override(200, 200) 409 | .diskCacheStrategy(DiskCacheStrategy.NONE) 410 | .skipMemoryCache(false) 411 | .into(headIv) 412 | invalidateOptionsMenu() 413 | } catch (ex: Exception) { 414 | ex.printStackTrace() 415 | } 416 | with(sharedPref.edit()) { 417 | putString("userId", it) 418 | apply() 419 | } 420 | }) 421 | } 422 | 423 | override fun onResume() { 424 | super.onResume() 425 | //1 = 注册失败(没网);1 = 广播还没注册 426 | if (BaseActivity.pushState == 1 && BaseActivity.broadcastNet_State == 1) { 427 | println("在首页注册广播") 428 | val msg = BaseActivity.AppHandler!!.obtainMessage() 429 | msg.what = 2 430 | BaseActivity.AppHandler!!.sendMessage(msg) 431 | } else { 432 | println("不用在首页注册广播") 433 | } 434 | } 435 | 436 | override fun onPause() { 437 | super.onPause() 438 | //2 = 广播已注册 439 | if (BaseActivity.broadcastNet_State == 2) { 440 | println("在首页注销广播") 441 | val msg = BaseActivity.AppHandler!!.obtainMessage() 442 | msg.what = 3 443 | BaseActivity.AppHandler!!.sendMessage(msg) 444 | } else { 445 | println("不用在首页注销广播") 446 | } 447 | } 448 | 449 | private fun setQueryListener() { 450 | m_search_view.setOnQueryTextListener(object : SearchView.OnQueryTextListener { 451 | override fun onQueryTextSubmit(query: String?): Boolean { 452 | val searchUrl = searchBaseUrl + query.toString() 453 | getCurrentWebView().loadUrl(searchUrl) 454 | m_search_view.setQuery("", false) 455 | return true 456 | } 457 | 458 | override fun onQueryTextChange(newText: String?): Boolean { 459 | return true 460 | } 461 | }) 462 | } 463 | 464 | private fun initDrawer() { 465 | val headView: View = nav_view.inflateHeaderView(R.layout.nav_header) 466 | headIv = headView.findViewById(R.id.imageView) as ImageView 467 | } 468 | 469 | private fun setDrawer() { 470 | nav_view.setNavigationItemSelectedListener(this) 471 | val toggle = object : ActionBarDrawerToggle( 472 | this, 473 | drawer_layout, 474 | my_toolbar, 475 | R.string.navigation_drawer_open, 476 | R.string.navigation_drawer_close 477 | ) { 478 | override fun onDrawerOpened(drawerView: View) { 479 | if (nav_header_title?.text != user.getUserName().value) { 480 | nav_header_title?.text = user.getUserName().value 481 | } 482 | super.onDrawerOpened(drawerView) 483 | } 484 | } 485 | drawer_layout.addDrawerListener(toggle) 486 | toggle.syncState() 487 | } 488 | 489 | private fun closeDrawerAfterClick(item: MenuItem, custom: String? = null) { 490 | if (custom != null) { 491 | drawer_layout.closeDrawer(GravityCompat.START) 492 | getCurrentWebView().loadUrl(urlConcat(custom)) 493 | } else { 494 | drawer_layout.closeDrawer(GravityCompat.START) 495 | getCurrentWebView().loadUrl(urlConcat(item.title.toString())) 496 | } 497 | } 498 | 499 | private fun activityClickListener(item: MenuItem) { 500 | drawer_layout.closeDrawer(GravityCompat.START) 501 | getCurrentWebView().loadUrl(urlConcat(item.title.toString())) 502 | } 503 | 504 | private fun showSidebarResponse(stringList: List) { 505 | runOnUiThread { 506 | val menu: Menu = nav_view.menu 507 | val subMenu: SubMenu = menu.addSubMenu(1, 1, 0, "当前活动") 508 | for ((i, v) in stringList.iterator().withIndex()) { 509 | val subStringList = v.split(",") 510 | subMenu.add(1, i + 1, i + 1, subStringList[1]) 511 | recentActivityTitleUrlMap[subStringList[1]] = subStringList[0] 512 | } 513 | } 514 | } 515 | 516 | fun gotoUserPage(view: View) { 517 | if (user.getUserId().value == "") { 518 | getCurrentWebView().loadUrl("https://fgo.wiki/w/特殊:用户登录") 519 | } else { 520 | getCurrentWebView().loadUrl("https://fgo.wiki/w/用户:${user.getUserName().value}") 521 | } 522 | 523 | drawer_layout.closeDrawer(GravityCompat.START) 524 | } 525 | 526 | fun parseSidebarJsonWithJsonObject(jsonData: String) { 527 | try { 528 | val json: JsonElement = Gson().fromJson(jsonData) 529 | val sourceText = json["parse"]["text"]["*"].toString() 530 | var result: String? = null 531 | val pattern = "

(.*)

" 532 | val matcher: Matcher = Pattern.compile(pattern).matcher(sourceText) 533 | 534 | Log.e("debug", json["parse"]["text"]["*"].toString()) 535 | if (matcher.find()) { 536 | result = matcher.group(1)?.toString() 537 | } 538 | val resultArray = result?.replace("
\\n", "")?.split("
") 539 | if (resultArray != null) { 540 | return showSidebarResponse(resultArray) 541 | } 542 | } catch (ex: Exception) { 543 | ex.printStackTrace() 544 | } 545 | } 546 | 547 | fun parseUpdateJsonWithJsonObject(jsonData: String) { 548 | try { 549 | val json: JsonElement = Gson().fromJson(jsonData) 550 | Log.e("debug", json.toString()) 551 | 552 | val remoteVersionCode = json["remoteVersionCode"].toString().toInt() 553 | val localVersionCode = BuildConfig.VERSION_CODE 554 | val title = json["title"] 555 | val description = json["description"] 556 | 557 | if (remoteVersionCode > localVersionCode && !isFinishing) { 558 | runOnUiThread { 559 | AlertDialog.Builder(this) 560 | .setTitle(title.asString) 561 | .setMessage(description.asString) 562 | .setPositiveButton("去更新") { _, _ -> 563 | getCurrentWebView().loadUrl("https://fgo.wiki/w/Mooncell:Appclient") 564 | } 565 | .setNegativeButton("取消") { _, _ -> } 566 | .show() 567 | } 568 | } 569 | } catch (ex: Exception) { 570 | ex.printStackTrace() 571 | } 572 | } 573 | 574 | private fun sendRequestWithOkHttp(url: String, type: Int) { 575 | Thread(Runnable { 576 | try { 577 | HttpUtil.sendOkHttpRequest( 578 | url, 579 | object : Callback { 580 | 581 | override fun onResponse(call: Call?, response: Response?) { 582 | val responseData = response?.body()?.string() 583 | 584 | when (type) { 585 | 1 -> parseSidebarJsonWithJsonObject(responseData!!) 586 | 2 -> parseUpdateJsonWithJsonObject(responseData!!) 587 | } 588 | } 589 | 590 | override fun onFailure(call: Call?, e: IOException?) { 591 | e?.printStackTrace() 592 | } 593 | }) 594 | } catch (ex: Exception) { 595 | ex.printStackTrace() 596 | } 597 | }).start() 598 | } 599 | 600 | private fun getAppDetailSettingIntent(context: Context): Intent? { 601 | val localIntent = Intent() 602 | localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 603 | localIntent.action = "android.settings.APPLICATION_DETAILS_SETTINGS" 604 | localIntent.data = Uri.fromParts("package", context.packageName, null) 605 | return localIntent 606 | } 607 | 608 | private fun checkPermissions() { 609 | if (ContextCompat.checkSelfPermission( 610 | this@MainActivity, 611 | Manifest.permission.READ_EXTERNAL_STORAGE 612 | ) 613 | != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( 614 | this@MainActivity, 615 | Manifest.permission.READ_PHONE_STATE 616 | ) 617 | != PackageManager.PERMISSION_GRANTED 618 | ) { 619 | if (ActivityCompat.shouldShowRequestPermissionRationale( 620 | this@MainActivity, 621 | Manifest.permission.READ_EXTERNAL_STORAGE 622 | ) || ActivityCompat.shouldShowRequestPermissionRationale( 623 | this@MainActivity, 624 | Manifest.permission.READ_PHONE_STATE 625 | ) 626 | ) { 627 | AlertDialog.Builder(this) 628 | .setTitle("权限请求") 629 | .setMessage("由于Mooncell客户端已接入小米推送SDK,需要读取存储空间和电话权限才能正常运行。\n请前往设置进行授权。") 630 | .setPositiveButton("确定") { _, _ -> 631 | this.startActivity(getAppDetailSettingIntent(this)) 632 | } 633 | .setNegativeButton("取消") { _, _ -> } 634 | .show() 635 | } else { 636 | // No explanation needed, we can request the permission. 637 | ActivityCompat.requestPermissions( 638 | this@MainActivity, 639 | arrayOf( 640 | Manifest.permission.READ_EXTERNAL_STORAGE, 641 | Manifest.permission.READ_PHONE_STATE 642 | ), 643 | PERMISSIONS_MIPUSH_GROUP 644 | ) 645 | } 646 | } 647 | } 648 | 649 | private fun requestFloatPermission() { 650 | PermissionUtils.requestPermission(this, object : OnPermissionResult { 651 | override fun permissionResult(isOpen: Boolean) { 652 | Log.e("debug", isOpen.toString()) 653 | } 654 | }) 655 | } 656 | 657 | private fun readLogUserPreference() { 658 | sharedPref.getString("userId", "")?.let { user.userId(it) } 659 | sharedPref.getString("userName", "未登录")?.let { user.userName(it) } 660 | } 661 | 662 | private fun createFloatBall() { 663 | val floatBall = ImageView(applicationContext) 664 | floatBall.setImageResource(R.mipmap.ic_float) 665 | 666 | FloatWindow 667 | .with(applicationContext) 668 | .setView(floatBall) 669 | .setWidth(150) 670 | .setHeight(Screen.width, 0.2f) 671 | .setX(300) 672 | .setY(Screen.height, 0.3f) 673 | .setDesktopShow(true) 674 | .build() 675 | 676 | floatBall.setOnClickListener { 677 | EasyFloat.showAppFloat() 678 | FloatWindow.get().hide() 679 | } 680 | } 681 | } 682 | 683 | @SuppressLint("SetJavaScriptEnabled") 684 | private fun WebView.setFloatWebView() { 685 | val settingsFloat = float_webView.settings 686 | settingsFloat.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW 687 | settingsFloat.javaScriptEnabled = true 688 | settingsFloat.setAppCacheEnabled(true) 689 | settingsFloat.cacheMode = WebSettings.LOAD_DEFAULT 690 | settingsFloat.setSupportZoom(false) 691 | settingsFloat.builtInZoomControls = false 692 | settingsFloat.displayZoomControls = false 693 | settingsFloat.blockNetworkImage = false 694 | settingsFloat.loadsImagesAutomatically = true 695 | settingsFloat.userAgentString = 696 | settingsFloat.userAgentString + " mooncellApp/" + BuildConfig.VERSION_NAME 697 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 698 | settingsFloat.safeBrowsingEnabled = true 699 | } 700 | settingsFloat.useWideViewPort = true 701 | settingsFloat.loadWithOverviewMode = true 702 | settingsFloat.javaScriptCanOpenWindowsAutomatically = true 703 | settingsFloat.domStorageEnabled = true 704 | settingsFloat.setSupportMultipleWindows(true) 705 | settingsFloat.loadWithOverviewMode = true 706 | settingsFloat.setGeolocationEnabled(true) 707 | settingsFloat.allowFileAccess = true 708 | settingsFloat.javaScriptCanOpenWindowsAutomatically = true 709 | settingsFloat.setSupportMultipleWindows(true) 710 | float_webView.fitsSystemWindows = true 711 | 712 | float_webView.webViewClient = object : WebViewClient() { 713 | override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { 714 | if (Uri.parse(url).host == "fgo.wiki") { 715 | return false 716 | } 717 | Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply { 718 | Log.e("debug", "overwriteURL") 719 | } 720 | return true 721 | } 722 | } 723 | 724 | float_webView.webChromeClient = object : WebChromeClient() { 725 | override fun onCreateWindow( 726 | view: WebView, 727 | dialog: Boolean, 728 | userGesture: Boolean, 729 | resultMsg: Message? 730 | ): Boolean { 731 | val result: WebView.HitTestResult = view.hitTestResult 732 | val data: String? 733 | data = if (result.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { 734 | val handler = Handler() 735 | val message = handler.obtainMessage() 736 | 737 | float_webView.requestFocusNodeHref(message) 738 | message.data.getString("url") 739 | } else { 740 | result.extra 741 | } 742 | if (data != null) { 743 | val context = view.context 744 | val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(data)) 745 | context.startActivity(browserIntent) 746 | } 747 | return false 748 | 749 | } 750 | } 751 | } 752 | -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/ui/activity/StartActivity.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.ui.activity 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.os.Handler 6 | import android.os.HandlerThread 7 | import android.os.Message 8 | import androidx.appcompat.app.AppCompatActivity 9 | 10 | 11 | class StartActivity : AppCompatActivity() { 12 | private var mainHandler: Handler? = null 13 | private var workHandler: Handler? = null 14 | private var mHandlerThread: HandlerThread? = null 15 | private var time = 4 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | mainHandler = Handler() 19 | val intent = Intent( 20 | this@StartActivity, 21 | MainActivity::class.java 22 | ) 23 | //跳转主页 24 | startActivity(intent) 25 | finish() 26 | 27 | //创建后台计时线程 28 | initBackground() 29 | workHandler!!.sendEmptyMessage(0x121) 30 | } 31 | 32 | private fun initBackground() { 33 | mHandlerThread = HandlerThread("countTime") 34 | 35 | //启动新线程 36 | mHandlerThread!!.start() 37 | workHandler = object : Handler(mHandlerThread!!.looper) { 38 | //消息处理的操作 39 | override fun handleMessage(msg: Message) { 40 | //设置了两种消息处理操作,通过msg来进行识别 41 | when (msg.what) { 42 | 0x121 -> { 43 | if (time > 0) { 44 | 45 | //通过主线程Handler.post方法进行在主线程的UI更新操作 46 | //可能有人看到new了一个Runnable就以为是又开了一个新线程 47 | //事实上并没有开启任何新线程,只是使run()方法体的代码抛到与mHandler相关联的线程中执行,我们知道mainHandler是与主线程关联的,所以更新TextView组件依然发生在主线程 48 | mainHandler!!.post { 49 | time-- 50 | } 51 | val msg2 = 52 | workHandler!!.obtainMessage() 53 | msg2.what = 0x121 54 | workHandler!!.sendMessageDelayed(msg2, 1000) 55 | } 56 | if (time == 0) { 57 | val localIntent = Intent( 58 | this@StartActivity, 59 | MainActivity::class.java 60 | ) 61 | //跳转主页 62 | startActivity(localIntent) 63 | finish() 64 | return 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | override fun onDestroy() { 73 | super.onDestroy() 74 | workHandler!!.removeCallbacksAndMessages(null) 75 | mainHandler!!.removeCallbacksAndMessages(null) 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/ui/adapter/TabAdapter.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.ui.adapter 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentActivity 5 | import androidx.viewpager2.adapter.FragmentStateAdapter 6 | import wiki.fgo.app.ui.fragment.SwipeRefreshWebViewFragment 7 | 8 | class TabAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { 9 | val fragments = arrayOfNulls(4) 10 | override fun getItemCount(): Int = 4 11 | override fun createFragment(position: Int): Fragment { 12 | val f = SwipeRefreshWebViewFragment() 13 | fragments[position] = f 14 | return f 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/ui/fragment/MultiWebViewFragment.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.ui.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.viewpager2.widget.ViewPager2 9 | import com.google.android.material.tabs.TabLayout 10 | import com.google.android.material.tabs.TabLayoutMediator 11 | import wiki.fgo.app.R 12 | import wiki.fgo.app.ui.adapter.TabAdapter 13 | 14 | class MultiWebViewFragment() : Fragment() { 15 | private lateinit var pager: ViewPager2 16 | override fun onCreateView( 17 | inflater: LayoutInflater, 18 | container: ViewGroup?, 19 | savedInstanceState: Bundle? 20 | ): View? { 21 | val layout = inflater.inflate(R.layout.fragment_multi, container, false) 22 | val tabLayout = layout.findViewById(R.id.tab_layout) 23 | pager = layout.findViewById(R.id.vp_content) 24 | pager.adapter = TabAdapter(requireActivity()) 25 | pager.isUserInputEnabled = false 26 | pager.offscreenPageLimit = 4 27 | TabLayoutMediator(tabLayout, pager) { tab, position -> 28 | tab.text = "tab $position" 29 | }.attach() 30 | return layout 31 | } 32 | 33 | fun getCurrentWebViewFragment(): SwipeRefreshWebViewFragment? { 34 | return (pager.adapter as TabAdapter).fragments[pager.currentItem] 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/ui/fragment/SwipeRefreshWebViewFragment.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.ui.fragment 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Intent 5 | import android.graphics.Bitmap 6 | import android.net.Uri 7 | import android.net.Uri.decode 8 | import android.os.Bundle 9 | import android.os.Handler 10 | import android.os.Message 11 | import android.view.LayoutInflater 12 | import android.view.View 13 | import android.view.ViewGroup 14 | import android.webkit.CookieManager 15 | import android.webkit.WebChromeClient 16 | import android.webkit.WebView 17 | import android.webkit.WebView.HitTestResult 18 | import android.webkit.WebViewClient 19 | import androidx.core.view.isVisible 20 | import androidx.fragment.app.Fragment 21 | import androidx.fragment.app.activityViewModels 22 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout 23 | import wiki.fgo.app.R 24 | import wiki.fgo.app.model.UserViewModel 25 | import wiki.fgo.app.ui.webview.WebviewInit 26 | import wiki.fgo.app.utils.network.HttpUtil 27 | 28 | 29 | class SwipeRefreshWebViewFragment() : Fragment() { 30 | private val cssLayer: String = 31 | "javascript:var style = document.createElement(\"style\");style.type = \"text/css\";style.innerHTML=\"form.header{display:none}.footer-content{display:none}\";style.id=\"addStyle\";document.getElementsByTagName(\"HEAD\").item(0).appendChild(style);" 32 | private val user: UserViewModel by activityViewModels() 33 | private val mainUrl = "https://fgo.wiki/index.php?title=首页&mobileaction=toggle_view_mobile" 34 | lateinit var webView: WebView 35 | lateinit var swipeRefreshLayout: SwipeRefreshLayout 36 | 37 | @SuppressLint("SetJavaScriptEnabled") 38 | override fun onCreateView( 39 | inflater: LayoutInflater, 40 | container: ViewGroup?, 41 | savedInstanceState: Bundle? 42 | ): View { 43 | swipeRefreshLayout = 44 | inflater.inflate(R.layout.swipe_refresh_webview, container, false) as SwipeRefreshLayout 45 | swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary) 46 | webView = swipeRefreshLayout.findViewById(R.id.webView) 47 | WebviewInit.setWebView(webView, this.requireContext()) 48 | 49 | webView.webViewClient = object : WebViewClient() { 50 | override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { 51 | swipeRefreshLayout.setProgressViewEndTarget(false, 250) 52 | swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary) 53 | swipeRefreshLayout.isRefreshing = true 54 | super.onPageStarted(view, url, favicon) 55 | } 56 | 57 | override fun onPageFinished(view: WebView?, url: String?) { 58 | webView.loadUrl(cssLayer) 59 | swipeRefreshLayout.isRefreshing = false 60 | val cookieManager: CookieManager = CookieManager.getInstance() 61 | if (cookieManager.getCookie(url) == null) { 62 | println("cookie is null") 63 | } else { 64 | try { 65 | val cookieMap = mutableMapOf() 66 | val cookieStr: String = cookieManager.getCookie(url) 67 | val temp: List = cookieStr.split(";") 68 | for (ar1 in temp) { 69 | val temp1 = ar1.split("=").toTypedArray() 70 | cookieMap[temp1[0].replace(" ", "")] = temp1[1] 71 | } 72 | if (cookieMap["my_wiki_fateUserID"] != null && cookieMap["my_wiki_fateUserName"] != null) { 73 | if (user.getUserName().value != decode(cookieMap["my_wiki_fateUserName"])) { 74 | user.userName( 75 | decode(cookieMap["my_wiki_fateUserName"]).replace( 76 | "+", 77 | "" 78 | ) 79 | ) 80 | } 81 | if (user.getUserId().value != decode(cookieMap["my_wiki_fateUserID"])) { 82 | user.userId(decode(cookieMap["my_wiki_fateUserID"])) 83 | } 84 | } else { 85 | if (user.getUserId().value != "") user.userId("") 86 | if (user.getUserName().value != "未登录") user.userName("未登录") 87 | } 88 | } catch (e: IllegalStateException) { 89 | println("处理IllegalStateException") 90 | } 91 | } 92 | super.onPageFinished(view, url) 93 | } 94 | 95 | override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { 96 | if (Uri.parse(url).host == "fgo.wiki") { 97 | // This is my web site, so do not override; let my WebView load the page 98 | return false 99 | } 100 | // Otherwise, the link is not for a page on my site, so launch another Activity that handles URLs 101 | val uri = Uri.parse(url) 102 | val intent = Intent(Intent.ACTION_VIEW, uri) 103 | startActivity(intent, null) 104 | return true 105 | } 106 | } 107 | swipeRefreshLayout.setOnRefreshListener { 108 | webView.reload() 109 | } 110 | webView.webChromeClient = object : WebChromeClient() { 111 | override fun onCreateWindow( 112 | view: WebView, 113 | dialog: Boolean, 114 | userGesture: Boolean, 115 | resultMsg: Message? 116 | ): Boolean { 117 | val result: WebView.HitTestResult = view.hitTestResult 118 | val data: String? 119 | data = if (result.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { 120 | val handler = Handler() 121 | val message = handler.obtainMessage() 122 | 123 | webView.requestFocusNodeHref(message) 124 | message.data.getString("url") 125 | } else { 126 | result.extra 127 | } 128 | if (data != null) { 129 | val context = view.context 130 | val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(data)) 131 | context.startActivity(browserIntent) 132 | } 133 | return false 134 | 135 | } 136 | } 137 | webView.setOnLongClickListener { v: View? -> 138 | val hitTestResult = webView.hitTestResult 139 | // 如果是图片类型或者是带有图片链接的类型 140 | if (hitTestResult.type == HitTestResult.IMAGE_TYPE || 141 | hitTestResult.type == HitTestResult.SRC_IMAGE_ANCHOR_TYPE 142 | ) { 143 | val saveImgUrl: String? = hitTestResult.extra 144 | if (saveImgUrl != null) { 145 | HttpUtil.saveImageFromServer(saveImgUrl, v!!.context) 146 | } 147 | return@setOnLongClickListener true 148 | } 149 | false //保持长按可以复制文字 150 | } 151 | 152 | webView.loadUrl(mainUrl) 153 | 154 | return swipeRefreshLayout 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/ui/notification/MiPushBroadcast.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.ui.notification 2 | 3 | import android.content.Context 4 | import android.os.Message 5 | import com.xiaomi.mipush.sdk.* 6 | import wiki.fgo.app.ui.activity.BaseActivity 7 | 8 | class MiPushBroadcast : PushMessageReceiver() { 9 | //透传消息到达客户端时调用 10 | //作用:可通过参数message从而获得透传消息,具体请看官方SDK文档 11 | override fun onReceivePassThroughMessage( 12 | context: Context, 13 | message: MiPushMessage 14 | ) { 15 | 16 | //打印消息方便测试 17 | println("透传消息到达了") 18 | println("透传消息是$message") 19 | } 20 | 21 | //通知消息到达客户端时调用 22 | //注:应用在前台时不弹出通知的通知消息到达客户端时也会回调函数 23 | //作用:通过参数message从而获得通知消息,具体请看官方SDK文档 24 | override fun onNotificationMessageArrived( 25 | context: Context, 26 | message: MiPushMessage 27 | ) { 28 | //打印消息方便测试 29 | println("通知消息到达了") 30 | println("通知消息是$message") 31 | } 32 | 33 | //用户手动点击通知栏消息时调用 34 | //注:应用在前台时不弹出通知的通知消息到达客户端时也会回调函数 35 | //作用:1. 通过参数message从而获得通知消息,具体请看官方SDK文档 36 | //2. 设置用户点击消息后打开应用 or 网页 or 其他页面 37 | override fun onNotificationMessageClicked( 38 | context: Context, 39 | message: MiPushMessage 40 | ) { 41 | 42 | //打印消息方便测试 43 | println("用户点击了通知消息") 44 | println("通知消息是$message") 45 | println("点击后,会进入应用") 46 | } 47 | 48 | //用来接收客户端向服务器发送命令后的响应结果。 49 | override fun onCommandResult( 50 | context: Context, 51 | message: MiPushCommandMessage 52 | ) { 53 | val command = message.command 54 | // System.out.println(command ); 55 | if (MiPushClient.COMMAND_REGISTER == command) { 56 | if (message.resultCode == ErrorCode.SUCCESS.toLong()) { 57 | //打印信息便于测试注册成功与否 58 | // System.out.println("注册成功"); 59 | } else { 60 | // System.out.println("注册失败"); 61 | } 62 | } 63 | } 64 | 65 | //用于接收客户端向服务器发送注册命令后的响应结果。 66 | override fun onReceiveRegisterResult( 67 | context: Context, 68 | message: MiPushCommandMessage 69 | ) { 70 | val command = message.command 71 | println("开始注册$command") 72 | if (MiPushClient.COMMAND_REGISTER == command) { 73 | if (message.resultCode == ErrorCode.SUCCESS.toLong()) { 74 | 75 | //打印日志:注册成功 76 | println("推送服务注册成功") 77 | //广播是否已经注册了,2代表已注册 78 | if (BaseActivity.broadcastNet_State === 2) { 79 | 80 | //通过回调注销广播 81 | val msg: Message? = BaseActivity.AppHandler?.obtainMessage() 82 | if (msg != null) { 83 | msg.what = 3 84 | } 85 | BaseActivity.AppHandler?.sendMessage(msg) 86 | 87 | //设置状态量:3 = 推送服务注册成功;1 = 广播还未注册(已被注销) 88 | BaseActivity.pushState = 3 89 | BaseActivity.broadcastNet_State = 1 90 | } else { 91 | println("结束") 92 | BaseActivity.pushState = 3 93 | BaseActivity.broadcastNet_State = 1 94 | } 95 | } else { 96 | //打印日志:注册失败 97 | println("注册失败") 98 | BaseActivity.pushState = 2 99 | //通过回调延时注册广播 100 | println("回调延时注册广播") 101 | val msg: Message? = BaseActivity.AppHandler?.obtainMessage() 102 | if (msg != null) { 103 | msg.what = 1 104 | } 105 | BaseActivity.AppHandler?.sendMessage(msg) 106 | } 107 | } else { 108 | println("其他情况" + message.reason) 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/ui/notification/NetworkReceiver.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.ui.notification 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.ConnectivityManager 7 | import android.os.Build 8 | import wiki.fgo.app.ui.activity.BaseActivity 9 | 10 | class NetworkReceiver : BroadcastReceiver() { 11 | private val isConn = false 12 | override fun onReceive(context: Context, intent: Intent) { 13 | println("网络状态发生变化") 14 | 15 | //检测API是不是小于23,因为到了API23之后getNetworkInfo(int networkType)方法被弃用 16 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 17 | val connMgr = 18 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 19 | //获取WIFI连接的信息 20 | val wifiNetworkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI) 21 | //获取移动数据连接的信息 22 | val dataNetworkInfo = 23 | connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) 24 | if (wifiNetworkInfo.isConnected or dataNetworkInfo.isConnected) { 25 | println("有网了") 26 | 27 | //立即回调注册 28 | println("回调立即注册推送服务") 29 | val msg = BaseActivity.AppHandler!!.obtainMessage() 30 | msg.what = 0 31 | BaseActivity.AppHandler!!.sendMessage(msg) 32 | } else { 33 | println("没网了") 34 | BaseActivity.pushState = 1 35 | } 36 | 37 | //API大于23时使用下面的方式进行网络监听 38 | } else { 39 | println("API level 大于23") 40 | //获得ConnectivityManager对象 41 | val connMgr = 42 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 43 | 44 | //获取所有网络连接的信息 45 | val networks = connMgr.allNetworks 46 | //用于存放网络连接信息 47 | val sb = StringBuilder() 48 | //通过循环将网络信息逐个取出来 49 | for (i in networks.indices) { 50 | //获取ConnectivityManager对象对应的NetworkInfo对象 51 | val networkInfo = connMgr.getNetworkInfo(networks[i]) 52 | if (networkInfo.isConnected) { 53 | //立即回调注册 54 | println("回调立即注册推送服务") 55 | val msg = BaseActivity.AppHandler!!.obtainMessage() 56 | msg.what = 0 57 | BaseActivity.AppHandler!!.sendMessage(msg) 58 | break 59 | } else { 60 | println("没网了") 61 | //没网时设置推送服务状态量 62 | BaseActivity.pushState = 1 63 | } 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/ui/view/ScaleImage.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.ui.view 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.util.AttributeSet 6 | import android.view.MotionEvent 7 | 8 | class ScaleImage(context: Context, attrs: AttributeSet? = null) : androidx.appcompat.widget.AppCompatImageView(context, attrs) { 9 | 10 | private var touchDownX = 0f 11 | private var touchDownY = 0f 12 | 13 | var onScaledListener: OnScaledListener? = null 14 | 15 | interface OnScaledListener { 16 | fun onScaled(x: Float, y: Float, event: MotionEvent) 17 | } 18 | 19 | @SuppressLint("ClickableViewAccessibility") 20 | override fun onTouchEvent(event: MotionEvent?): Boolean { 21 | 22 | if (event == null) return super.onTouchEvent(event) 23 | 24 | // 屏蔽掉浮窗的事件拦截,仅由自身消费 25 | parent?.requestDisallowInterceptTouchEvent(true) 26 | 27 | when (event.action) { 28 | MotionEvent.ACTION_DOWN -> { 29 | touchDownX = event.x 30 | touchDownY = event.y 31 | } 32 | 33 | MotionEvent.ACTION_MOVE -> 34 | onScaledListener?.onScaled(event.x - touchDownX, event.y - touchDownY, event) 35 | 36 | } 37 | return true 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/ui/webview/WebViewInit.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.ui.webview 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.os.Build 6 | import android.webkit.WebSettings 7 | import android.webkit.WebView 8 | import wiki.fgo.app.BuildConfig 9 | 10 | 11 | class WebviewInit { 12 | companion object { 13 | @SuppressLint("SetJavaScriptEnabled") 14 | fun setWebView( 15 | webView: WebView, 16 | ctx: Context 17 | ) { 18 | // Get the web view settings instance 19 | val settings = webView.settings 20 | //5.0以上开启混合模式加载 21 | settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW 22 | settings.javaScriptEnabled = true 23 | // Enable and setup web view cache 24 | settings.setAppCacheEnabled(true) 25 | settings.cacheMode = WebSettings.LOAD_DEFAULT 26 | settings.setAppCachePath(ctx.cacheDir.path) 27 | settings.setSupportZoom(false) 28 | // Enable zooming in web view 29 | settings.builtInZoomControls = false 30 | settings.displayZoomControls = false 31 | // Enable disable images in web view 32 | settings.blockNetworkImage = false 33 | // Whether the WebView should load image resources 34 | settings.loadsImagesAutomatically = true 35 | //设置UA 36 | settings.userAgentString = 37 | settings.userAgentString + " mooncellApp/" + BuildConfig.VERSION_NAME 38 | // More web view settings 39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 40 | settings.safeBrowsingEnabled = true 41 | } 42 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 43 | settings.setForceDark(WebSettings.FORCE_DARK_AUTO) 44 | } 45 | settings.useWideViewPort = true 46 | settings.loadWithOverviewMode = true 47 | settings.javaScriptCanOpenWindowsAutomatically = true 48 | // More optional settings, you can enable it by yourself 49 | settings.domStorageEnabled = true 50 | settings.setSupportMultipleWindows(true) 51 | settings.loadWithOverviewMode = true 52 | settings.setGeolocationEnabled(true) 53 | settings.allowFileAccess = true 54 | settings.javaScriptCanOpenWindowsAutomatically = true 55 | settings.setSupportMultipleWindows(true) 56 | //webview setting 57 | webView.fitsSystemWindows = true 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/utils/io/MediaStoreHandler.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.utils.io 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.content.* 6 | import android.content.pm.PackageManager 7 | import android.graphics.Bitmap 8 | import android.net.Uri 9 | import android.os.Build 10 | import android.os.Environment 11 | import android.provider.MediaStore 12 | import android.util.Log 13 | import android.widget.Toast 14 | import androidx.annotation.Nullable 15 | import androidx.appcompat.app.AlertDialog 16 | import androidx.core.app.ActivityCompat 17 | import androidx.core.content.ContextCompat 18 | import java.io.* 19 | 20 | 21 | class MediaStoreHandler { 22 | companion object { 23 | private const val MY_PERMISSIONS_STORAGE_GROUP = 100 24 | 25 | fun addImageToGallery(saveImage: Bitmap, saveName: String, context: Context) { 26 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 27 | val resolver = context.contentResolver 28 | 29 | val imagesCollection = 30 | MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) 31 | 32 | val imagesDetail = ContentValues().apply { 33 | put(MediaStore.Images.Media.DISPLAY_NAME, saveName) 34 | put(MediaStore.Images.Media.MIME_TYPE, "image/png") 35 | put( 36 | MediaStore.Images.Media.RELATIVE_PATH, 37 | Environment.DIRECTORY_PICTURES + "/Mooncell" 38 | ) 39 | put(MediaStore.Images.Media.IS_PENDING, 1) 40 | } 41 | 42 | val imagesContentUri = resolver.insert(imagesCollection, imagesDetail) 43 | 44 | if (imagesContentUri != null) { 45 | resolver.openFileDescriptor(imagesContentUri, "w", null).use { pfd -> 46 | if (pfd != null) { 47 | writeImageData( 48 | pfd.fileDescriptor, 49 | saveImage 50 | ) 51 | } 52 | } 53 | } 54 | imagesDetail.clear() 55 | imagesDetail.put(MediaStore.Audio.Media.IS_PENDING, 0) 56 | if (imagesContentUri != null) { 57 | resolver.update(imagesContentUri, imagesDetail, null, null) 58 | } 59 | } else { 60 | //for api version <= 28 61 | val storedImagePath: File = 62 | generateImagePath( 63 | saveName 64 | ) 65 | 66 | if (!compressAndSaveImage( 67 | storedImagePath, 68 | saveImage 69 | ) 70 | ) { 71 | Log.e("error", "error occurred in save image") 72 | } 73 | 74 | addImageToGalleryLowOS( 75 | context.contentResolver, 76 | "png", 77 | storedImagePath 78 | ) 79 | 80 | //MediaStore will compress image to low quality before Android 10 81 | //MediaStore.Images.Media.insertImage(resolver, saveImage, saveName, "") 82 | } 83 | } 84 | 85 | private fun generateImagePath(title: String): File { 86 | return File(getImagesDirectory(), title) 87 | } 88 | 89 | private fun getImagesDirectory(): File? { 90 | val file = 91 | File( 92 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) 93 | .toString() + File.separator + "Mooncell" 94 | ) 95 | if (!file.mkdirs() && !file.isDirectory) { 96 | Log.e("mkdir", "Directory not created") 97 | } 98 | return file 99 | } 100 | 101 | private fun compressAndSaveImage(file: File?, bitmap: Bitmap): Boolean { 102 | var result = false 103 | try { 104 | val fos = FileOutputStream(file) 105 | if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos).also { result = it }) { 106 | Log.w("media store handler", "Compression success") 107 | } 108 | fos.close() 109 | } catch (e: IOException) { 110 | e.printStackTrace() 111 | } 112 | return result 113 | } 114 | 115 | private fun addImageToGalleryLowOS( 116 | cr: ContentResolver, 117 | imgType: String, 118 | filepath: File 119 | ): Uri? { 120 | val values = ContentValues() 121 | values.put(MediaStore.Images.Media.TITLE, "mooncell") 122 | values.put(MediaStore.Images.Media.DISPLAY_NAME, "mooncell") 123 | values.put(MediaStore.Images.Media.DESCRIPTION, "") 124 | values.put(MediaStore.Images.Media.MIME_TYPE, "image/$imgType") 125 | values.put(MediaStore.Images.Media.DATA, filepath.toString()) 126 | return cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) 127 | } 128 | 129 | fun checkPermissions(context: Context, activity: Activity): Boolean { 130 | var isGranted = false 131 | if (ContextCompat.checkSelfPermission( 132 | context, 133 | Manifest.permission.READ_EXTERNAL_STORAGE 134 | ) 135 | != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( 136 | context, 137 | Manifest.permission.WRITE_EXTERNAL_STORAGE 138 | ) 139 | != PackageManager.PERMISSION_GRANTED 140 | ) { 141 | if (ActivityCompat.shouldShowRequestPermissionRationale( 142 | activity, 143 | Manifest.permission.READ_EXTERNAL_STORAGE 144 | ) 145 | ) { 146 | AlertDialog.Builder(context) 147 | .setTitle("权限请求") 148 | .setMessage("保存图片操作需要存储空间权限才能正常运行。\n请前往设置进行授权。") 149 | .setPositiveButton("确定") { _, _ -> 150 | val localIntent = Intent() 151 | localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 152 | localIntent.action = "android.settings.APPLICATION_DETAILS_SETTINGS" 153 | localIntent.data = Uri.fromParts("package", context.packageName, null) 154 | context.startActivity(localIntent) 155 | } 156 | .setNegativeButton("取消") { _, _ -> } 157 | .show() 158 | } else { 159 | // No explanation needed, we can request the permission. 160 | ActivityCompat.requestPermissions( 161 | activity, 162 | arrayOf( 163 | Manifest.permission.READ_EXTERNAL_STORAGE, 164 | Manifest.permission.WRITE_EXTERNAL_STORAGE 165 | ), 166 | MY_PERMISSIONS_STORAGE_GROUP 167 | ) 168 | } 169 | } else { 170 | isGranted = true 171 | Toast.makeText(context, "图片已保存", Toast.LENGTH_SHORT).show() 172 | } 173 | return isGranted 174 | } 175 | 176 | @Nullable 177 | fun findActivity(context: Context): Activity? { 178 | if (context is Activity) { 179 | return context 180 | } 181 | return if (context is ContextWrapper) { 182 | findActivity( 183 | context.baseContext 184 | ) 185 | } else { 186 | null 187 | } 188 | } 189 | 190 | private fun writeImageData( 191 | fileDescriptor: FileDescriptor, 192 | writeImage: Bitmap 193 | ) { 194 | val fos = FileOutputStream(fileDescriptor) 195 | try { 196 | writeImage.compress(Bitmap.CompressFormat.PNG, 100, fos) 197 | fos.flush() 198 | } catch (e: IOException) { 199 | e.printStackTrace() 200 | } finally { 201 | fos.close() 202 | } 203 | } 204 | 205 | private fun readData(fileDescriptor: FileDescriptor): String? { 206 | val fis = FileInputStream(fileDescriptor) 207 | val b = ByteArray(1024) 208 | var read: Int 209 | var content: String? = null 210 | try { 211 | while (fis.read(b).also { read = it } != -1) { 212 | content = String(b, 0, read) 213 | } 214 | } catch (e: IOException) { 215 | e.printStackTrace() 216 | } finally { 217 | try { 218 | fis.close() 219 | } catch (e: IOException) { 220 | e.printStackTrace() 221 | } 222 | } 223 | return content 224 | } 225 | } 226 | } -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/utils/network/ContentHandler.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.utils.network 2 | 3 | import android.util.Log 4 | import org.xml.sax.Attributes 5 | import org.xml.sax.SAXException 6 | import org.xml.sax.helpers.DefaultHandler 7 | 8 | class ContentHandler : DefaultHandler() { 9 | 10 | var nodeName: String? = null 11 | var id: StringBuilder? = null 12 | var name: StringBuilder? = null 13 | var version: StringBuilder? = null 14 | 15 | 16 | @Throws(SAXException::class) 17 | override fun startDocument() { 18 | id = StringBuilder() 19 | name = StringBuilder() 20 | version = StringBuilder() 21 | } 22 | 23 | 24 | @Throws(SAXException::class) 25 | override fun endDocument() { 26 | super.endDocument() 27 | } 28 | 29 | 30 | @Throws(SAXException::class) 31 | override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) { 32 | nodeName = localName 33 | } 34 | 35 | 36 | @Throws(SAXException::class) 37 | override fun characters(ch: CharArray, start: Int, length: Int) { 38 | when (nodeName) { 39 | "id" -> { 40 | id?.append(ch,start,length) 41 | } 42 | "name" -> { 43 | name?.append(ch,start,length) 44 | } 45 | "version" -> { 46 | version?.append(ch, start, length) 47 | } 48 | } 49 | } 50 | 51 | @Throws(SAXException::class) 52 | override fun endElement(uri: String, localName: String, qName: String) { 53 | if("app" == localName) { 54 | Log.d("ContentHandler","id is :" + id?.toString()?.trim()) 55 | Log.d("ContentHandler","name is :" + name?.toString()?.trim()) 56 | Log.d("ContentHandler","version is :" + version?.toString()?.trim()) 57 | 58 | id?.setLength(0) 59 | name?.setLength(0) 60 | version?.setLength(0) 61 | } 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/utils/network/HttpCallback.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.utils.network 2 | 3 | interface HttpCallback { 4 | fun onFinish(responseData: String) 5 | fun onError(ex: Exception) 6 | } -------------------------------------------------------------------------------- /app/src/main/java/wiki/fgo/app/utils/network/HttpUtil.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app.utils.network 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.util.Log 6 | import com.bumptech.glide.Glide 7 | import com.bumptech.glide.request.target.Target 8 | import okhttp3.Callback 9 | import okhttp3.OkHttpClient 10 | import okhttp3.Request 11 | import wiki.fgo.app.utils.io.MediaStoreHandler 12 | import wiki.fgo.app.utils.io.MediaStoreHandler.Companion.addImageToGallery 13 | import wiki.fgo.app.utils.io.MediaStoreHandler.Companion.findActivity 14 | import java.io.BufferedReader 15 | import java.io.InputStreamReader 16 | import java.net.HttpURLConnection 17 | import java.net.URL 18 | import java.net.URLDecoder 19 | 20 | 21 | class HttpUtil { 22 | companion object { 23 | fun urlConcat(title: String): String { 24 | return "https://fgo.wiki/w/$title" 25 | } 26 | 27 | fun avatarUrlConcat(userId: String): String { 28 | return "http://avatar.mooncell.wiki/mc/$userId/original.png" 29 | } 30 | 31 | fun saveImageFromServer(imgUrl: String, context: Context) { 32 | var result: Boolean = false 33 | findActivity(context)?.let { 34 | result = MediaStoreHandler.checkPermissions(context, it) 35 | } 36 | if (result) { 37 | Thread { 38 | val bitmap: Bitmap = Glide.with(context) 39 | .asBitmap() 40 | .load(imgUrl) 41 | .submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) 42 | .get() 43 | val saveTo: String = URLDecoder.decode(imgUrl.split("/").last(), "UTF-8") 44 | addImageToGallery(bitmap, saveTo, context) 45 | }.start() 46 | } else { 47 | Log.w("media store handler", "permission denied") 48 | } 49 | } 50 | 51 | fun sendHttpRequest(address: String, callback: HttpCallback?) { 52 | Thread(Runnable { 53 | var connection: HttpURLConnection? = null 54 | try { 55 | val url = URL(address) 56 | connection = url.openConnection() as HttpURLConnection 57 | connection.requestMethod = "GET" 58 | connection.connectTimeout = 8000 59 | connection.readTimeout = 8000 60 | connection.doInput = true 61 | connection.doOutput = true 62 | val inStream = connection.inputStream 63 | val reader = BufferedReader(InputStreamReader(inStream)) 64 | val responseData = StringBuilder() 65 | val allText = reader.use(BufferedReader::readText) 66 | responseData.append(allText) 67 | callback?.onFinish(responseData.toString()) 68 | } catch (ex: Exception) { 69 | callback?.onError(ex) 70 | } finally { 71 | connection?.disconnect() 72 | } 73 | }).start() 74 | } 75 | 76 | 77 | fun sendOkHttpRequest(address: String, callback: Callback?) { 78 | var client = OkHttpClient() 79 | var request = Request.Builder().url(address).build() 80 | 81 | client.newCall(request).enqueue(callback) 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/about_page_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_favorite.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_favorite_empty.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_notice.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_reload.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_search.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_share.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_certificate.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_comment_slash.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cubes.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_diagnoses.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dice.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_download.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_expand.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fire.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gamepad.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_graduation_cap.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_hand_holding_heart.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info_black.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_map.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_gallery.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_slideshow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_microphone.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_music.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_o.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_paint_brush.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_scale.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_skull.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_street_view.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_tasks.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_tshirt.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_users.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_x.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | 15 | 24 | 25 | 34 | 35 | 39 | 40 | 44 | 45 | 50 | 51 | 52 | 53 | 62 | 63 | 64 | 65 | 70 | 71 | 75 | 83 | 84 | 89 | 90 | 98 | 99 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 19 | 20 | 24 | 25 | 28 | 29 | 41 | 42 | 45 | 46 | 55 | 56 | 57 | 58 | 59 | 60 | 64 | 65 | 66 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /app/src/main/res/layout/developers_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 19 | 20 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/float_window.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 26 | 27 | 35 | 36 | 45 | 46 | 53 | 54 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_multi.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_mutable.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/licenses_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 19 | 20 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/nav_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 25 | 26 | 33 | 34 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/swipe_refresh_webview.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/webview.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/menu/sidebar.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 10 | 14 | 18 | 22 | 26 | 30 | 34 | 38 | 42 | 46 | 50 | 54 | 58 | 62 | 66 | 70 | 74 | 78 | 82 | 86 | 90 | 91 | 92 | 93 | 94 | 98 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /app/src/main/res/menu/top_bar_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 27 | 28 | 31 | 32 | 35 | 36 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-hdpi/ic_float.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-mdpi/ic_float.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xhdpi/ic_float.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xxhdpi/ic_float.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xxxhdpi/ic_float.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #4487DF 4 | #4487DF 5 | #85C1F7 6 | #ffffff 7 | #000000 8 | #f1f1f1 9 | #858585 10 | #f1f1f1 11 | #757575 12 | #de000000 13 | @android:color/black 14 | #44000000 15 | #ffffff 16 | #33000000 17 | #eae9e9 18 | @color/about_page_card_text_color 19 | @color/about_page_hint 20 | @color/about_page_card_text_color 21 | #757575 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 8dp 6 | 176dp 7 | 16dp 8 | 212dp 9 | 38dp 10 | 13sp 11 | 48dp 12 | 48dp 13 | 16dp 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Mooncell 3 | 收藏 4 | 悬浮窗切换 5 | 搜索 6 | exampleActivity 7 | Open navigation drawer 8 | Close navigation drawer 9 | 未登录 10 | Ciao~ 11 | Navigation header 12 | 13 | 分享 14 | 通知 15 | 登录 16 | 关于Mooncell 17 | 关于客户端 18 | 常用 19 | 英灵图鉴 20 | 概念礼装图鉴 21 | 指令纹章图鉴 22 | 周常任务 23 | 新增卡牌 24 | 模拟抽卡 25 | 关卡配置 26 | 敌人一览 27 | 道具一览 28 | 技能一览 29 | 御主装备 30 | 灵衣一览 31 | 音乐鉴赏 32 | 声优一览 33 | 画师一览 34 | 日服客户端下载 35 | 支持我们 36 | 反馈与建议 37 | 评论须知 38 | wiki.fgo.app.PREFERENCE_FILE_KEY 39 | 40 | 41 | 退出 42 | 首页 43 | 是否可拖拽 44 | 设置 45 | tab1 46 | 开源项目鸣谢 47 | 关于开发者 48 | 开发者信息 49 | Back 50 | floatball mode 51 | 关于 52 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 20 | 21 | 26 | 27 | 30 | 31 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 53 | 54 | 58 | 59 | 65 | 66 | 70 | 71 | 80 | 81 | 88 | 89 | 100 | 101 | 105 | 106 | 116 | 117 | 127 | 128 | 134 | 135 | 140 | 141 | 146 | 147 | 152 | 153 | 159 | 160 | 168 | 169 | -------------------------------------------------------------------------------- /app/src/test/java/wiki/fgo/app/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package wiki.fgo.app 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.51' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.google.gms:google-services:4.3.4' 11 | classpath 'com.android.tools.build:gradle:4.1.2' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21" 13 | classpath 'com.google.firebase:perf-plugin:1.3.4' 14 | classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | maven { url 'https://jitpack.io' } 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | # Automatically convert third-party libraries to use AndroidX 24 | android.enableJetifier=true 25 | 26 | # Kotlin code style for this project: "official" or "obsolete": 27 | kotlin.code.style=official 28 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooncellWiki/mooncell-android-kotlin/d55489bad99846c3e82d6c20e1eeac05c2f59e24/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 28 18:07:23 CST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name='Mooncell' 3 | --------------------------------------------------------------------------------