├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_zh_rTW.md ├── app └── src │ └── main │ └── res │ ├── values-zh-rCN │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ └── values │ └── strings.xml ├── art └── screenshots │ ├── main_0_1_44.png │ ├── map_select_amap_0_7_300.png │ ├── per_app_settings_0_3_73.png │ ├── settings_0_1_44.png │ └── shortcut_list_0_7_300.png ├── mapsearchbar ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── rong │ │ └── library │ │ └── widget │ │ ├── HighlightTextView.java │ │ └── mapsearchbar │ │ ├── MapSearchBar.java │ │ ├── OnSearchResultItemClickListener.java │ │ ├── SearchQueryThread.java │ │ ├── SearchResult.java │ │ └── SearchResultAdapter.java │ └── res │ ├── drawable-hdpi │ ├── ic_arrow_back_white_24dp.png │ ├── ic_close_white_24dp.png │ ├── ic_history_white_24dp.png │ ├── ic_more_vert_white_24dp.png │ ├── ic_place_white_24dp.png │ └── ic_search_white_24dp.png │ ├── drawable-mdpi │ ├── ic_arrow_back_white_24dp.png │ ├── ic_close_white_24dp.png │ ├── ic_history_white_24dp.png │ ├── ic_more_vert_white_24dp.png │ ├── ic_place_white_24dp.png │ └── ic_search_white_24dp.png │ ├── drawable-xhdpi │ ├── ic_arrow_back_white_24dp.png │ ├── ic_close_white_24dp.png │ ├── ic_history_white_24dp.png │ ├── ic_more_vert_white_24dp.png │ ├── ic_place_white_24dp.png │ └── ic_search_white_24dp.png │ ├── drawable-xxhdpi │ ├── ic_arrow_back_white_24dp.png │ ├── ic_close_white_24dp.png │ ├── ic_history_white_24dp.png │ ├── ic_more_vert_white_24dp.png │ ├── ic_place_white_24dp.png │ └── ic_search_white_24dp.png │ ├── drawable-xxxhdpi │ ├── ic_arrow_back_white_24dp.png │ ├── ic_close_white_24dp.png │ ├── ic_history_white_24dp.png │ ├── ic_more_vert_white_24dp.png │ ├── ic_place_white_24dp.png │ └── ic_search_white_24dp.png │ ├── layout │ ├── item_search_result.xml │ └── search_bar.xml │ └── values │ ├── attr.xml │ ├── colors.xml │ ├── dimens.xml │ └── strings.xml └── markdownview ├── .gitignore ├── LICENSE ├── build.gradle └── src └── main ├── AndroidManifest.xml ├── assets └── html │ ├── css │ ├── bootstrap.css │ ├── github-gist.css │ └── github.css │ ├── js │ ├── highlight.pack.js │ ├── jquery-2.1.4.min.js │ ├── marked.js │ └── preview.js │ └── preview.html └── java └── com └── mukesh └── MarkdownView.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio Navigation editor temp files 29 | .navigation/ 30 | 31 | # Android Studio captures folder 32 | captures/ 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 更新日志 (Changelogs) 2 | 3 | ## 1.2 4 | - 准备对Android 7的支持(部分) 5 | - 应用设置页恢复“更新”按钮 6 | - 尝试修复部分GPS定位问题 7 | - 其他一些改进 8 |
9 | - Prepare Android 7 support (partial) 10 | - Add "Notify" button back in `Per-app Settings` page 11 | - Try to fix some issues regarding to GPS mocking 12 | - Other improvements 13 | 14 | 15 | ## 1.1.646 16 | - 地图添加搜索支持 17 | - 完善当前基站信息获取 18 | - 帮助中的页面链接现在使用浏览器打开 19 | - 打开地图默认定位到应用之前设置的位置 20 | - UI细节调整 21 | - 修复错误 22 |
23 | - Add map search 24 | - Improve 'Current Cell Info' 25 | - Links in help now use browser to open 26 | - Map's initial location default to app's current settings 27 | - UI adjustments 28 | - Bug fixes 29 | 30 | 31 | ## 1.0.561 32 | - 完善对Gps状态的处理 33 | - 悬浮窗及应用设置页新增“更新”按钮,单独使用GPS时应用出现“正在定位”、“无法定位”等可尝试点击 34 | - 改进内存不足时悬浮窗服务可能被系统关闭的问题 35 |
36 | - Improve Gps status handling 37 | - Add a "Notify" button (overlay and per-app settings), try it when you use GPS Mocking alone and get "Locating", "Unable to locate", etc. 38 | - Improve that overlay may be killed by the system when low on memory 39 | 40 | 41 | ## 1.0.527 42 | - 新增`模板`-`当前基站信息` 43 | - 修复错误 44 | - 易用性改进 45 |
46 | - Add `Template` - `Current Cell Info` 47 | - Bug fixes 48 | - Improve usability 49 | 50 | 51 | ## 0.9.486 52 | - 新增自我隐藏 53 | - 应用列表根据应用是否启用模拟排序 54 | - GMS支持使用新的实现(现在无需单独设置com.google.android.gms,直接设置对应APP即可) 55 | - 修复可能跳转回真实地址的错误 56 | - 兼容跳转至Material Xposed Installer模块设置页面 57 | - 更新帮助内容 58 | - 更新间隔设置 59 | - 修复错误 60 |
61 | - Hide module from selected app(s) 62 | - Sort apps list by mocking status and settings 63 | - New approach for GMS support (Make the gps settings in App's per-app settings page, no longer need to set com.google.android.gms separately) 64 | - Fix location jumping back to real location 65 | - Fix a problem with Material Xposed Installer 66 | - Update help 67 | - Settings for update interval 68 | - Bug fixes 69 | 70 | 71 | ## 0.8.400 72 | - 现在无需打开GPS即可实时模拟新的位置 73 | - 优化捐赠方式 74 | - 如微信无法打开,在设置中打开兼容模式 75 | - 新增摇杆悬浮窗,根据方向、力量值更新位置信息,使用时先在对应App设置页面点击“关联悬浮窗”按钮。 76 | - 添加首选地图设置 77 | - 修复部分手机上启用模块后仍然显示模块未启用的错误 78 | - 修复列表为空时点击搜索闪退的错误 79 | - 修复一些错误 80 | - 请注意:新版使用了新的设置文件和格式,更新后部分设置需重新设置 81 |
82 | - Now you don't even need to turn on GPS to update new mocking location 83 | - Optimize donation 84 | - Add joystick overlay, mocking location will be updated according to the bearing and strength. To use it, go to the app setting page and click "Connect" first. 85 | - Add preferred map setting 86 | - Fix a bug that the module keeps saying that it's not active even it was enabled in Xposed Installer on some devices 87 | - Fix a crash cause by clicking search when the list is still loading 88 | - Other bug fixes 89 | - PLEASE NOTE that this update uses a new setting file and new format, some old settings will be lost after the update. 90 | 91 | 92 | ## 0.7.306 93 | - 修复点击应用列表闪退的错误 94 | - 修复加载应用列表可能导致的OOM错误 95 |
96 | - Fix a crash when selecting app from the list 97 | - Fix an OOM crash when loading a long list of apps 98 | 99 | 100 | ## 0.7.300 101 | - 自定义GPS状态 102 | - 新增地图选择 103 | - 地图选择历史(最近列表) 104 | - 修复一些错误 105 | - 开启“即时更新”后,地图点击、选择(点击)标记、使用最近列表中的地点将直接写入设置中,无需返回应用列表。 106 |
107 | - Custom GPS status support 108 | - Select GPS coordinates from map (play service 7.0.0+ is required) 109 | - Map select history (recent list) 110 | - Bug fixes 111 | - When "Instant Update" enabled, location settings made by map click, marker click, recent list selection will be saved immediately without going back to app list. 112 | 113 | 114 | ## 0.7.291 115 | - 测试版本 (Internal release) 116 | 117 | 118 | ## 0.6.195 119 | - 支持Android 4.3 120 | - 基站模拟增强 121 | - 完善部分函数的处理逻辑 122 | - 新增对Google Play services Location API的支持 123 | - 新增对腾讯定位SDK的支持 124 |
125 | - Support Android 4.3 126 | - Enhance cell location mocking 127 | - Improve the handling logic of several functions 128 | - Add support for the Location API of Google Play services 129 | - Add support for the Tencent Location SDK 130 | 131 | 132 | ## 0.5.161 133 | - 新增模板 134 | - 坐标偏移修正 135 | - 基站模拟增强 136 | - 修复一个可能导致崩溃的错误 137 | - 修复部分设置不能生效的错误 138 | - 新增的WRITE_EXTERNAL_STORAGE权限用于记录崩溃日志 139 |
140 | - Add template settings 141 | - Offset correction (coordinates in China) 142 | - Enhance cell location mocking 143 | - Fix a bug that may cause FC 144 | - Fix a bug that new settings won't take effect 145 | - New permission WRITE_EXTERNAL_STORAGE is used for saving crash logs 146 | 147 | 148 | ## 0.4.127 149 | - 新增应用搜索 150 | - 新增快速设置(最近列表) 151 | - 修复一些错误 152 | - 一些性能优化 153 |
154 | - Add app search 155 | - Add setting shortcut (recent list) 156 | - Bug fixes 157 | - Performance optimizations 158 | 159 | 160 | ## 0.3.78 161 | - 新增基站模拟 162 | - 新增繁体中文 (thanks to iamernie8199) 163 |
164 | - Add cell location mocking 165 | - Add Tradictional Chinese translation (thanks to iamernie8199) 166 | 167 | 168 | ## 0.2.48 169 | - 添加对4.4系统的支持 170 |
171 | - Add support for Android 4.4 172 | 173 | 174 | ## 0.1.44 175 | - 初始版本 176 |
177 | - First release 178 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 模拟位置 (FakeLocation) 2 | 3 | - English description please refer to [here](https://github.com/j2rong/FakeLocation#english). 4 | - 繁體中文描述請參照[這裡](https://github.com/j2rong/FakeLocation/blob/master/README_zh_rTW.md) 5 | - 这是一个 ***Xposed*** 模块,用于模拟地理位置,世界在手天下我有。 6 | - 无需模拟位置权限 7 | 8 |
9 | 10 | ## 屏幕截图 (Screenshots) 11 | 12 |

13 | 14 | 15 | 16 |

17 |

18 | 19 | 20 |

21 |
22 | 23 | ## 更新日志 24 | - [**CHANGELOG**](https://github.com/j2rong/FakeLocation/blob/master/CHANGELOG.md) 25 |
26 | 27 | ## 问题 28 | 29 | - 如使用过程中出现问题或功能建议,请至[此处](https://github.com/j2rong/FakeLocation/issues/new)提交。 30 | - 如位置设置无效,请在模块中打开日志,并将Xposed Installer中的日志提交至[此处](https://github.com/j2rong/FakeLocation/issues/new)。 31 | 32 |
33 | 34 | ## 致谢 (Credits) 35 | 36 | - [**Xposed Framework**](https://github.com/rovo89/Xposed) 37 | 38 | Original work Copyright (c) 2005-2008, The Android Open Source Project 39 | Modified work Copyright (c) 2013, rovo89 and Tungstwenty 40 | 41 | Licensed under the Apache License, Version 2.0 (the "License"); 42 | you may not use this file except in compliance with the License. 43 | 44 | Unless required by applicable law or agreed to in writing, software 45 | distributed under the License is distributed on an "AS IS" BASIS, 46 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 47 | See the License for the specific language governing permissions and 48 | limitations under the License. 49 | 50 |
51 | 52 | ## English 53 | 54 | - This is an ***Xposed*** module for mocking locations per app 55 | 56 | - No need to turn on "Mock locations" permission 57 | 58 | - **Problems** 59 | 60 | Feel free to [open an issue](https://github.com/j2rong/FakeLocation/issues/new) if there is any problem or suggestion. 61 | If fake location don't work, you can turn on the logs and submit (you can find it in Xposed Installer) to help identify the issue. 62 | 63 | - [**Changelogs**](https://github.com/j2rong/FakeLocation/blob/master/CHANGELOG.md) 64 | -------------------------------------------------------------------------------- /README_zh_rTW.md: -------------------------------------------------------------------------------- 1 | # 模擬位置 (FakeLocation) 2 | 3 | - English description please refer to [here](https://github.com/j2rong/FakeLocation#english). 4 | - 简体中文描述请参见[此处](https://github.com/j2rong/FakeLocation) 5 | - 這是一個 ***Xposed*** 模組,用於模擬地理位置,世界在手天下我有。 6 | - 無需模擬位置權限 7 | 8 |
9 | 10 | ## 螢幕截圖 11 | 12 |

13 | 14 | 15 | 16 |

17 |

18 | 19 | 20 |

21 |
22 | 23 | ## 更新記錄檔 24 | - [**CHANGELOG**](https://github.com/j2rong/FakeLocation/blob/master/CHANGELOG.md) 25 |
26 | 27 | ## 問題 28 | 29 | - 若使用過程中出現問題或功能建議,請至[這裡](https://github.com/j2rong/FakeLocation/issues/new)送出。 30 | - 若位置設定無效,請在模組中啟用記錄檔,並把 Xposed Installer 裡的記錄檔送出至[這裡](https://github.com/j2rong/FakeLocation/issues/new)。 31 | 32 |
33 | 34 | ## 參與名單 35 | 36 | - [**Xposed Framework**](https://github.com/rovo89/Xposed) 37 | 38 | Original work Copyright (c) 2005-2008, The Android Open Source Project 39 | Modified work Copyright (c) 2013, rovo89 and Tungstwenty 40 | 41 | Licensed under the Apache License, Version 2.0 (the "License"); 42 | you may not use this file except in compliance with the License. 43 | 44 | Unless required by applicable law or agreed to in writing, software 45 | distributed under the License is distributed on an "AS IS" BASIS, 46 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 47 | See the License for the specific language governing permissions and 48 | limitations under the License. 49 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 模拟位置 4 | 模拟我的位置 5 | 6 | 位置设置 7 | 设置 8 | 捐赠 9 | 模板 10 | 关于 11 | 选择地点 12 | \"%1$s\"的搜索结果 13 | 14 | 15 | 16 | 设置 17 | 模板 18 | 搜索 19 | 捐赠 20 | 如何使用 21 | 关于 22 | 悬浮窗开启/关闭 23 | 24 | 模块“%1$s”尚未启用!首次使用请先在Xposed Installer中启用并重启。 25 | 我知道了 26 | 未知版本的Xposed Installer,请手动启用 27 | 28 | 本次更新中的部分新功能需要重启才能正常使用。 29 | 我知道了 30 | 31 | 搜索应用名或包名 32 | [隐藏] 33 | [关联] 34 | 35 | 36 | 37 | 很抱歉,%1$s已停止运行,崩溃日志已复制到剪贴板,请尝试重新启动或至GitHub反馈该问题。 38 | 调用堆栈: 39 | 崩溃日志已保存至%1$s 40 | 崩溃日志保存失败,保存日志需开启“写入外部存储 (WRITE_EXTERNAL_STORAGE)”权限 41 | 确定 42 | 提交问题 43 | 44 | 45 | 46 | 确定 47 | 不再显示 48 | 如果要模拟位置的App使用了谷歌服务来获取GPS信息,只需在设置中打开“谷歌服务API支持”并在对应App页面中设置即可,无需在此设置。 49 | 悬浮窗 50 | 关联一个应用以使悬浮窗正常工作,查看帮助获取更详细说明。(应用设置页面-GPS模拟-关联悬浮窗) 51 | 52 | 53 | 54 | 为保证下列功能正常运行: 55 | 保存自定义设置及崩溃日志 56 | 显示地图用于选择模拟的位置 57 | 显示悬浮窗 58 | 应用需要获取 %1$s、%2$s 和 %3$s 权限 59 | 在部分ROM上应用需要获取 %1$s 权限 60 | 写入外部存储 (WRITE_EXTERNAL_STORAGE) 61 | 读取手机状态 (READ_PHONE_STATE) 62 | 获取粗略位置 (ACCESS_COARSE_LOCATION) 63 | 显示系统窗口 (SYSTEM_ALERT_WINDOW) 64 | 65 | 以后再说 66 | 去设置 67 | 允许 68 | 拒绝 69 | 我知道了 70 | 71 | 72 | 73 | 搜索地点 74 | 历史记录 75 | 手气不错 76 | 切换地图 77 | 78 | 选择了 (%1$.6f, %2$.6f) 79 | 坐标获取失败 80 | 当前位置 81 | 正在获取地址… 82 | 选择地点 83 | (空) 84 | 85 | 86 | 87 | 没有找到与“%1$s”相关的地点 88 | 正在搜索 89 | 重试 90 | 正在加载 91 | 无法获取该地址对应坐标 92 | 93 | 94 | 95 | 最近 96 | 模板 97 | 应用包名称获取失败,请重试! 98 | 我知道了 99 | 模块已更新,该功能需重启后才能使用。 100 | 设置已保存 101 | 已更新 102 | 发生错误:%1$s 103 | 104 | 对该应用隐藏本模块 105 | 106 | 使用GPS模拟 107 | 不同应用的处理逻辑可能不同,如模拟的位置没有更新,使用悬浮窗手动更新 108 | 109 | 经度范围只能在-180与180之间 110 | 纬度范围只能在-90与90之间 111 | 纬度 112 | 经度 113 | 114 | 选择 115 | 关联悬浮窗 116 | 断开关联 117 | 更新 118 | 119 | 使用基站信息模拟 120 | GPS模拟失败时可尝试使用基站模拟 121 | 122 | 基站类型 123 | 最好与手机类型相同 124 | 125 | 126 | 127 | 新的设置尚未被保存,是否保存? 128 | 取消 129 | 130 | 保存 131 | 设置已保存 132 | 设置未被修改 133 | 设置未保存 134 | 135 | 136 | 137 | 快速填充 138 | 模板 139 | 最近 140 | (未设置) 141 | (未知) 142 | (空) 143 | 144 | 当前基站信息 145 | 使用手机当前所连接的基站的信息 146 | 自动获取基站信息失败 :( 147 | GPS模板 148 | 基站模板 149 | (无) 150 | 类型 151 | 152 | 153 | 154 | 155 | 通用 156 | 157 | 系统应用 158 | 显示 159 | 不显示 160 | 161 | 应用列表排序 162 | 根据应用设置及模拟状态排序(更耗时) 163 | 默认 164 | 165 | 166 | 地图 167 | 168 | 首选地图类型 169 | 170 | 自动(默认) 171 | 高德地图 172 | 谷歌地图 173 | 174 | 搜索范围(谷歌地图) 175 | 176 | 只搜索本地区 177 | 全球 178 | 179 | 180 | 181 | 定位 182 | 183 | 修正地图偏移 184 | 对中国地区坐标进行偏移修正,当定位失败、位置偏移时可尝试打开此项 185 | 不修正 186 | 187 | 谷歌服务API支持 188 | 189 | 190 | 191 | 反馈 192 | 193 | 输出日志 194 | 开启 195 | 提交问题前请先开启日志输出,并尝试重现该问题! 196 | 197 | 提交问题 198 | 请尽可能详细的描述该问题,并将Xposed Installer中的日志一起提交 199 | 200 | 201 | 202 | 高级 203 | 204 | 高级设置 205 | 如果你不能完全确定所进行的操作,建议不要更改这些设置,因为这可能会导致未知错误。 206 | 207 | GPS状态 208 | 默认为空,数字以逗号分隔 209 | 210 | 信噪比 211 | 卫星高度 212 | 卫星方位角 213 | 214 | 215 | 216 | 实验性功能 217 | 开启(需重启) 218 | 关闭(需重启) 219 | 220 | Tencent Location SDK 支持 221 | 222 | 223 | 224 | 悬浮窗 225 | 226 | 悬浮窗设置 227 | 相关设置 228 | 229 | 服务 230 | 231 | 自动停止 232 | 当主页面关闭时,如果悬浮窗未显示,停止后台服务 233 | 不自动停止 234 | 235 | 前台服务 236 | 启用以避免内存不足时系统自动停止服务 237 | 238 | 239 | 行为 240 | 241 | 切换间隔 242 | 在设置的时间内无操作时关闭摇杆返回小窗口(毫秒) 243 | 244 | 记住上次位置 245 | 悬浮窗将在上次关闭时的位置显示 246 | 悬浮窗将在默认位置显示 247 | 248 | 控制参数 249 | 250 | GPS坐标更新速度 251 | 更新间隔(毫秒) 252 | GPS坐标将根据摇杆所确定的方向和强度进行更新。 253 | 该值设置过小可能会影响设备性能,且如果设置的间隔小于关联应用所请求的位置更新间隔,新位置可能不被应用使用。 254 | 255 | 最小移动速度 256 | 摇杆强度值为0时使用该值(米/秒) 257 | 258 | 最大移动速度 259 | 摇杆强度最大时使用该值(米/秒) 260 | 261 | 262 | 263 | 格式错误,多个数字请用逗号分隔 264 | (默认) 265 | %1$d 毫秒 266 | 不能为空或小于%1$d 267 | 该数字必须是100的整数倍 268 | 速度值不能为空 269 | 格式错误,必须为一个数值 270 | 271 | 272 | 273 | 开始/停止更新 274 | 更新一次 275 | 276 | 277 | 278 | 开源项目 279 | 翻译 280 | 其他模块 281 | 禁用悬挂式(Heads-up)通知 282 | 283 | 如果喜欢,点击此处%1$s支持 284 | 捐赠 285 | 286 | 287 | 288 | 请注意 289 | 修改系统应用的位置设置可能会导致未知错误! 290 | 我知道了 291 | 不再提醒 292 | 293 | 294 | 295 | 测试版本仅支持设置最多8个应用 296 | 好的 297 | 取消 298 | 299 | 300 | 301 | 302 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | FakeLocation 4 | 模擬目前的位置 5 | 6 | 位置設定 7 | 設定 8 | 捐贈 9 | 範本 10 | 關於 11 | 選擇一個位置 12 | \"%1$s\" 的搜尋結果 13 | 14 | 15 | 16 | 設定 17 | 範本 18 | 搜尋 19 | 捐贈 20 | 如何使用 21 | 關於 22 | 切換 Overlay 23 | 24 | "%1$s" 模組並未啟用!首次使用請先在 Xposed Installer 裡啟用並重新啟動。 25 | 我懂了 26 | 未知的 Xposed Installer,請手動啟用 27 | 28 | 此次更新包含一些新功能需要重新啟動才能正常運作。 29 | 我懂了 30 | 31 | 搜尋程式名稱或套件名稱 32 | [隱藏] 33 | [已連結] 34 | 35 | 36 | 37 | 很遺憾, "%1$s" 已停止執行,損毀記錄檔已複製至剪貼簿,請嘗試重新啟動此應用程式或在 GitHub 新開一個 issue。 38 | 追蹤堆疊: 39 | 損毀記錄檔已儲存至 %1$s 40 | 損毀記錄檔儲存失敗,儲存記錄檔需要「寫入外部儲存空間 (WRITE_EXTERNAL_STORAGE) 」權限 41 | 確定 42 | 在 GitHub 新開 issue 43 | 44 | 45 | 46 | 確定 47 | 不要再顯示 48 | 若您要模擬的應用程式使用 Google 服務取得GPS 位置,您應該在設定裡開啟「Google 服務 API 支援」並且在程式的每個程式設定頁面設定,而不是在此設定。 49 | Overlay 50 | 為了使 Overlay 能夠正常運作,您應該連結一個程式。(程式設定頁面- GPS 模擬-連結按鈕)參閱說明取得更多詳細資料。 51 | 52 | 53 | 54 | 為了使下列功能正常運作: 55 | 儲存損毀記錄檔與使用者設定 56 | 顯示地圖便於選擇位置 57 | 顯示 Overlay 58 | 需要 %1$s 與 %2$s 以及 %3$s 的權限 59 | 某些 ROM 需要 %1$s 權限 60 | 寫入外部儲存空間 (WRITE_EXTERNAL_STORAGE) 61 | 讀取電話狀態 (READ_PHONE_STATE) 62 | 存取約略位置 (ACCESS_COARSE_LOCATION) 63 | 系統警示視窗 (SYSTEM_ALERT_WINDOW) 64 | 65 | 稍後 66 | 前往設定 67 | 允許 68 | 拒絕 69 | 我懂了 70 | 71 | 72 | 73 | 搜尋地點 74 | 歷史 75 | 好手氣 76 | 切換地圖 77 | 78 | 已選擇位置 (%1$.6f, %2$.6f) 79 | 座標取得失敗 80 | 目前位置 81 | 擷取地址中… 82 | 選擇一個位置 83 | (空白) 84 | 85 | 86 | 87 | 找不到 "%1$s" 的結果 88 | 搜尋中… 89 | 重新嘗試 90 | 載入更多 91 | 找不到此地址的座標 92 | 93 | 94 | 95 | 最近 96 | 範本 97 | 取得套件名稱失敗,請再試一次! 98 | 我懂了 99 | 模組已更新,此功能需要重新啟動方能運作。 100 | 設定已儲存 101 | 已更新 102 | 發生錯誤:%1$s 103 | 104 | 對此程式隱藏此模組 105 | 106 | GPS 模擬 107 | 程式的位置處理可能不同,若此位置未更新,使用 Overlay 手動通知它 108 | 109 | 經度應大於 -180 小於 180 110 | 緯度應大於 -90 小於 90 111 | 緯度 112 | 經度 113 | 114 | 選擇 115 | 連結 116 | 中斷連結 117 | 通知 118 | 119 | 蜂窩資訊模擬 120 | 若 GPS 失敗,則嘗試使用蜂窩資訊 121 | 122 | 蜂窩類型 123 | 最好與手機類型一致 124 | 125 | 126 | 127 | 新設定未被儲存,是否先儲存之? 128 | 取消 129 | 捨棄 130 | 儲存 131 | 設定已儲存 132 | 未變更設定 133 | 設定已捨棄 134 | 135 | 136 | 137 | 捷徑 138 | 範本 139 | 最近 140 | (未設定) 141 | (不明) 142 | (空白) 143 | 144 | 目前的蜂窩資訊 145 | 使用手機目前的蜂窩資訊 146 | 不能自動擷取目前的蜂窩資訊:( 147 | GPS 範本 148 | 蜂窩範本 149 | (Null) 150 | 類型 151 | 152 | 153 | 154 | 155 | 一般 156 | 157 | 系統應用程式 158 | 顯示 159 | 不顯示 160 | 161 | 程式清單排序 162 | 根據程式的模擬狀態與設定排序(更耗時) 163 | 預設 164 | 165 | 166 | 地圖 167 | 168 | 偏好的地圖類型 169 | 170 | 自動(預設) 171 | 高德地圖 172 | Google 地圖 173 | 174 | 搜尋區域(Google 地圖) 175 | 176 | 僅目前地區 177 | 全球 178 | 179 | 180 | 181 | 定位 182 | 183 | 位移修正 184 | 嘗試修正位移(僅針對中國大陸座標) 185 | 不做任何事 186 | 187 | Google 服務 API 支援 188 | 189 | 190 | 191 | 回饋 192 | 193 | 詳細資訊記錄檔 194 | 啟用 195 | 在送出一條 issue 之前請先啟用此功能並且嘗試重現此 issue ! 196 | 197 | Issue 198 | 請盡可能詳細描述此 issue,並且把 Xposed Installer 裡的記錄檔同時送出 199 | 200 | 201 | 202 | 進階 203 | 204 | 進階設定 205 | 若您無法完全確定進行的操作,建議不要變更這些設定,因為它或許會引起意外行為。 206 | 207 | GPS 狀態 208 | 預設為空白,數字用逗點分隔 209 | 210 | 訊號雜訊比 211 | 衛星高度 212 | 衛星方位 213 | 214 | 215 | 216 | 實驗性 217 | 啟用(需要重新啟動) 218 | 停用(需要重新啟動) 219 | 220 | 騰訊位置 SDK 支援 221 | 222 | 223 | 224 | Overlay 225 | 226 | Overlay 設定 227 | 相關設定 228 | 229 | 服務 230 | 231 | 自動停止 232 | 在主頁面關閉時,若 Overlay 未顯示,則停止服務 233 | 什麼都不做 234 | 235 | 前景服務 236 | 若開啟,在記憶體不足時,服務不會被系統終止 237 | 238 | 239 | 行為 240 | 241 | 切換間隔 242 | 在時間間隔內無操作時關閉搖桿視圖(毫秒) 243 | 244 | 記住上一次位置 245 | Overlay 視圖會在上一次關閉的位置顯示 246 | Overlay 視圖會在預設位置顯示 247 | 248 | 控制 249 | 250 | GPS 位置更新間隔 251 | 更新間隔(毫秒) 252 | GPS 位置會根據搖桿選擇的角度與強度更新。 253 | 若更新間隔太小,裝置效能或許會受到影響。 254 | 另外,若間隔小於已連結的程式要求的位置接收間隔,更新或許會被程式捨棄。 255 | 256 | 最小移動速度 257 | 在搖桿強度設定為零時使用此數值(公尺/秒) 258 | 259 | 最大移動速度 260 | 在搖桿強度設定為最大時使用此數值(公尺/秒) 261 | 262 | 263 | 264 | 輸入錯誤,多個數字用逗點分隔 265 | (預設) 266 | %1$d 毫秒 267 | 不可以空白或小於 %1$d 268 | 此數字只能是 100 的整數倍數 269 | 速度值不可以空白 270 | 速度值無效,應該為一個浮點數 271 | 272 | 273 | 274 | 開始/停止更新 275 | 通知一次 276 | 277 | 278 | 279 | 開放原始碼計畫 280 | 翻譯 281 | 其他模組 282 | 停用 heads-up 通知 283 | 284 | 覺得此程式好用嗎?\n透過 %1$s 來支持我們。 285 | 捐贈 286 | 287 | 288 | 289 | 注意事項 290 | 變更系統應用程式的位置設定可能會引起未知錯誤! 291 | 我懂了 292 | 不要再顯示 293 | 294 | 295 | 296 | 測試版本僅支援設定最多 8 個程式 297 | 好的 298 | 取消 299 | 300 | 301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FakeLocation 3 | Mock current location 4 | 5 | 6 | Location Settings 7 | Settings 8 | Donate 9 | Template 10 | About 11 | Select a location 12 | Results for \"%1$s\" 13 | 14 | 15 | Unfortunately, %1$s has stopped, crash log has been copied to the clipboard. Please try to restart the app or open an issue on GitHub. 16 | Stacktrace: 17 | Crash log has been saved to %1$s 18 | Failed to save the crash log, it requires \"Write External Storage\" permission. 19 | OK 20 | Open Issue 21 | 22 | 23 | 24 | Settings 25 | Template 26 | Search 27 | Donate 28 | How to Use 29 | About 30 | Toggle Overlay 31 | 32 | %1$s is not active! Please enable it first in Xposed Installer and restart the phone. 33 | OK 34 | Unknown Xposed Installer, please enable it manually. 35 | 36 | This update contains some new features that requires a reboot to work properly. 37 | OK 38 | 39 | Search app name or package name 40 | [Hidden] 41 | [Connected] 42 | 43 | 44 | 45 | OK 46 | Never Show Again 47 | If the app you want to mock uses Google service to obtain gps locations, you should turn on \'Google service API support\' in Settings and set in app\'s per-app settings page instead of setting here. 48 | Overlay 49 | In order for the overlay to work, you should connect an App to it (Per-app settings page, GPS Mocking, Connect button). See help for more details. 50 | 51 | 52 | 53 | In order for the following features to work properly: 54 | Save crash log and user settings 55 | Display map for location selection 56 | Display overlay 57 | Permissions %1$s, %2$s and %3$s are needed. 58 | Permission %1$s is needed on some ROMs. 59 | WRITE_EXTERNAL_STORAGE 60 | READ_PHONE_STATE 61 | ACCESS_COARSE_LOCATION 62 | SYSTEM_ALERT_WINDOW 63 | 64 | Later 65 | To Set 66 | Allow 67 | Deny 68 | GOT IT 69 | 70 | 71 | 72 | Search places 73 | History 74 | Feeling Lucky 75 | Switch Map 76 | 77 | Location (%1$.6f, %2$.6f) selected 78 | Failed to get coordinate 79 | Current location 80 | Retrieving address… 81 | Select a location 82 | (Empty) 83 | 84 | 85 | 86 | No results found for \"%1$s\" 87 | Searching 88 | Retry 89 | Loading more 90 | No coordinate found for this address 91 | 92 | 93 | 94 | Recent 95 | Templates 96 | Failed to get package name, please try again! 97 | GOT IT 98 | This feature requires a reboot to work since the module has been updated 99 | Settings saved 100 | Updated 101 | Error occurred: %1$s 102 | 103 | Hide Module from the App 104 | 105 | 106 | GPS Mocking 107 | App\'s location handling may be different. If the location is not updated, use overlay to notify it manually 108 | 109 | Latitude 110 | Longitude 111 | Longitude should be greater than -180 and less than 180 112 | Latitude should be greater than -90 and less than 90 113 | 114 | Select 115 | Connect 116 | Disconnect 117 | Notify 118 | 119 | 120 | 121 | Cell info Mocking 122 | Try use cell info if GPS failed 123 | 124 | Cell type 125 | preferably the same as phone type 126 | 127 | 128 | 129 | Settings have been changed, do you want to save it first? 130 | Cancel 131 | Discard 132 | Save 133 | Settings saved 134 | No settings changed 135 | Settings discarded 136 | 137 | 138 | 139 | Shortcut 140 | Template 141 | Recent 142 | (Not set) 143 | (Unknown) 144 | (Empty) 145 | 146 | Current Cell Info 147 | The phone currently using 148 | Cannot retrieve current cell info automatically :( 149 | GPS Template 150 | Cell Template 151 | (Null) 152 | Type 153 | 154 | 155 | 156 | 157 | General 158 | 159 | System apps 160 | Show 161 | Don\'t show 162 | 163 | App list sorting 164 | Sort according to app\'s mocking status and settings (more time-consuming) 165 | Default 166 | 167 | 168 | Map 169 | 170 | Preferred map type 171 | 172 | Auto (Default) 173 | AutoNavi Map 174 | Google Map 175 | 176 | Search area (Google Map) 177 | 178 | Only current locale 179 | Global 180 | 181 | 182 | 183 | Locate 184 | 185 | Offset correction 186 | Try to correct offset (only coordinates in China) 187 | Don\'t do anything 188 | 189 | Google Service API support 190 | 191 | 192 | 193 | Feedback 194 | 195 | Verbose log 196 | Enabled 197 | Before submitting an issue please enable this and try to reproduce the issue! 198 | 199 | Issue 200 | Please describe the issue as detailed as possible, and submit with the log in Xposed Installer together 201 | 202 | 203 | 204 | 205 | Advanced 206 | 207 | Advanced settings 208 | It is not advisable to change these settings if you are not completely sure of what you are doing, as it could cause unexpected behavior. 209 | 210 | GPS Status 211 | Default is empty, numbers separated by commas 212 | 213 | Signal to Noise Ratio 214 | Satellite Elevation 215 | Satellite Azimuths 216 | 217 | 218 | 219 | Experimental 220 | Enabled (Reboot required) 221 | Disabled (Reboot required) 222 | 223 | Tencent Location SDK support 224 | 225 | 226 | 227 | Overlay 228 | 229 | Overlay settings 230 | Related settings 231 | 232 | Service 233 | 234 | Auto stop 235 | Stop service when closing main page if overlay is not showing 236 | Do nothing 237 | 238 | Foreground service 239 | If on, the service will unlikely be killed by the system when low on memory. 240 | 241 | Behavior 242 | 243 | Switching interval 244 | Close joystick view when there is no operation during the time interval (ms) 245 | 246 | Remember last position 247 | Overlay view will be shown at last closed position 248 | Overlay view will be shown at default position 249 | 250 | Controls 251 | 252 | GPS location update interval 253 | Update Interval (ms) 254 | GPS location will be updated according to the angle and strength selected from the overlay joystick. 255 | Device performance may be affected if update interval is too small. 256 | Also the update may be discarded by the connected app if the interval is less than the location receiving interval that the app requested. 257 | 258 | Minimum moving speed 259 | The value is used when strength of the overlay joystick is set to 0 (meter/s) 260 | 261 | Maximum moving speed 262 | The value is used when strength of the overlay joystick is set to maximum (meter/s) 263 | 264 | 265 | 266 | Input error, numbers separated by commas 267 | (Default) 268 | %1$d ms 269 | Can not be empty or less than %1$d 270 | The number can only be a integer multiple of 100 271 | Speed can not be empty 272 | Invalid speed value, should be a float number 273 | 274 | 275 | 276 | Start/Stop Updates 277 | Notify Once 278 | 279 | 280 | 281 | Find this app useful?\nSupport by %1$s. 282 | making a donation 283 | 284 | Other modules 285 | Disable heads-up notifications 286 | Translations 287 | Open source projects 288 | 289 | 290 | 291 | Attention 292 | Change the location settings of system apps may cause unexpected behavior! 293 | GOT IT 294 | DON\'T SHOW AGAIN 295 | 296 | 297 | 298 | Only supports 8 apps in beta version. 299 | OK 300 | Cancel 301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /art/screenshots/main_0_1_44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/art/screenshots/main_0_1_44.png -------------------------------------------------------------------------------- /art/screenshots/map_select_amap_0_7_300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/art/screenshots/map_select_amap_0_7_300.png -------------------------------------------------------------------------------- /art/screenshots/per_app_settings_0_3_73.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/art/screenshots/per_app_settings_0_3_73.png -------------------------------------------------------------------------------- /art/screenshots/settings_0_1_44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/art/screenshots/settings_0_1_44.png -------------------------------------------------------------------------------- /art/screenshots/shortcut_list_0_7_300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/art/screenshots/shortcut_list_0_7_300.png -------------------------------------------------------------------------------- /mapsearchbar/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mapsearchbar/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 17 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile 'com.android.support:appcompat-v7:23.4.0' 24 | compile 'com.android.support:design:23.4.0' 25 | compile 'com.android.support:recyclerview-v7:23.4.0' 26 | compile 'com.android.support:cardview-v7:23.4.0' 27 | 28 | } 29 | -------------------------------------------------------------------------------- /mapsearchbar/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Develop\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/java/com/rong/library/widget/HighlightTextView.java: -------------------------------------------------------------------------------- 1 | package com.rong.library.widget; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Color; 7 | import android.graphics.Typeface; 8 | import android.os.Build; 9 | import android.support.annotation.ColorInt; 10 | import android.text.Spannable; 11 | import android.text.SpannableString; 12 | import android.text.Spanned; 13 | import android.text.TextUtils; 14 | import android.text.style.BackgroundColorSpan; 15 | import android.text.style.ForegroundColorSpan; 16 | import android.text.style.StyleSpan; 17 | import android.util.AttributeSet; 18 | import android.widget.TextView; 19 | 20 | import com.rong.library.widget.mapsearchbar.R; 21 | 22 | /** 23 | * Copyright (c) 2016-2017, j2Rong 24 | * 25 | * Licensed under the Apache License, Version 2.0 (the "License"); 26 | * you may not use this file except in compliance with the License. 27 | * You may obtain a copy of the License at 28 | * 29 | * http://www.apache.org/licenses/LICENSE-2.0 30 | * 31 | * Unless required by applicable law or agreed to in writing, software 32 | * distributed under the License is distributed on an "AS IS" BASIS, 33 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | * See the License for the specific language governing permissions and 35 | * limitations under the License. 36 | */ 37 | public class HighlightTextView extends android.support.v7.widget.AppCompatTextView { 38 | 39 | public HighlightTextView(Context context) { 40 | super(context); 41 | init(null, 0); 42 | } 43 | 44 | public HighlightTextView(Context context, AttributeSet attrs) { 45 | super(context, attrs); 46 | init(attrs, 0); 47 | } 48 | 49 | public HighlightTextView(Context context, AttributeSet attrs, int defStyle) { 50 | super(context, attrs, defStyle); 51 | init(attrs, defStyle); 52 | } 53 | 54 | 55 | private int mHighlightTextColor = Color.BLACK; 56 | private int mHighlightBackgroundColor = Color.TRANSPARENT; 57 | private boolean mCaseSensitive = false; 58 | private boolean mHighlightAllOccurrences = true; 59 | private boolean mHighlightTextBold = false; 60 | 61 | private void init(AttributeSet attrs, int defStyle) { 62 | final TypedArray a = getContext().obtainStyledAttributes( 63 | attrs, R.styleable.HighlightTextView, defStyle, 0); 64 | 65 | mHighlightTextColor = a.getColor(R.styleable.HighlightTextView_htv_textHighlightColor, getCurrentTextColor()); 66 | mHighlightBackgroundColor = a.getColor(R.styleable.HighlightTextView_htv_textHighlightBackgroundColor, Color.TRANSPARENT); 67 | mCaseSensitive = a.getBoolean(R.styleable.HighlightTextView_htv_caseSensitive, false); 68 | mHighlightAllOccurrences = a.getBoolean(R.styleable.HighlightTextView_htv_highlightAllOccurrences, true); 69 | mHighlightTextBold = a.getBoolean(R.styleable.HighlightTextView_htv_highlightTextBold, false); 70 | 71 | a.recycle(); 72 | } 73 | 74 | public void setHighlightTextColor(@ColorInt int color) { 75 | this.mHighlightTextColor = color; 76 | } 77 | 78 | public int getHighlightTextColor() { 79 | return mHighlightTextColor; 80 | } 81 | 82 | public void setHighlightBackgroundColor(@ColorInt int color) { 83 | this.mHighlightBackgroundColor = color; 84 | } 85 | 86 | public int getHighlightBackgroundColor() { 87 | return mHighlightBackgroundColor; 88 | } 89 | 90 | public boolean isCaseSensitive() { 91 | return mCaseSensitive; 92 | } 93 | 94 | public void setCaseSensitive(boolean mCaseSensitive) { 95 | this.mCaseSensitive = mCaseSensitive; 96 | } 97 | 98 | public boolean isHighlightAllOccurrences() { 99 | return mHighlightAllOccurrences; 100 | } 101 | 102 | public void setHighlightAllOccurrences(boolean mHighlightAllOccurrences) { 103 | this.mHighlightAllOccurrences = mHighlightAllOccurrences; 104 | } 105 | 106 | public void highlight(String textToHighlight) { 107 | if (textToHighlight == null) { 108 | // clear highlight 109 | String text = getText().toString(); 110 | setText(text); 111 | return; 112 | } 113 | 114 | if (!TextUtils.isEmpty(getText()) && !textToHighlight.isEmpty()) { 115 | final String text = getText().toString(); 116 | final Spannable spannableString = new SpannableString(text); 117 | 118 | String textCopy; 119 | String textHighlightCopy; 120 | if (!mCaseSensitive) { 121 | textCopy = text.toLowerCase(); 122 | textHighlightCopy = textToHighlight.toLowerCase(); 123 | } else { 124 | textCopy = text; 125 | textHighlightCopy = textToHighlight; 126 | } 127 | 128 | int matchStart = textCopy.indexOf(textHighlightCopy, 0); 129 | if (matchStart == -1) { 130 | // no match , return 131 | return; 132 | } 133 | 134 | int searchStart; 135 | do { 136 | searchStart = matchStart + textHighlightCopy.length(); 137 | 138 | spannableString.setSpan(new ForegroundColorSpan(mHighlightTextColor), 139 | matchStart, searchStart, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 140 | spannableString.setSpan(new BackgroundColorSpan(mHighlightBackgroundColor), 141 | matchStart, searchStart, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 142 | if (mHighlightTextBold) 143 | spannableString.setSpan(new StyleSpan(Typeface.BOLD), 144 | matchStart, searchStart, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 145 | 146 | if (!mHighlightAllOccurrences) { 147 | break; 148 | } 149 | } while ((matchStart = textCopy.indexOf(textHighlightCopy, searchStart)) != -1); 150 | 151 | setText(spannableString); 152 | } 153 | } 154 | 155 | } -------------------------------------------------------------------------------- /mapsearchbar/src/main/java/com/rong/library/widget/mapsearchbar/MapSearchBar.java: -------------------------------------------------------------------------------- 1 | package com.rong.library.widget.mapsearchbar; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.annotation.TargetApi; 5 | import android.app.Activity; 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.graphics.drawable.Drawable; 9 | import android.os.Build; 10 | import android.support.annotation.MenuRes; 11 | import android.support.v4.content.ContextCompat; 12 | import android.support.v7.widget.AppCompatEditText; 13 | import android.support.v7.widget.AppCompatImageView; 14 | import android.support.v7.widget.CardView; 15 | import android.support.v7.widget.LinearLayoutManager; 16 | import android.support.v7.widget.PopupMenu; 17 | import android.support.v7.widget.RecyclerView; 18 | import android.text.Editable; 19 | import android.text.TextUtils; 20 | import android.text.TextWatcher; 21 | import android.util.AttributeSet; 22 | import android.util.DisplayMetrics; 23 | import android.view.Gravity; 24 | import android.view.KeyEvent; 25 | import android.view.MenuItem; 26 | import android.view.View; 27 | import android.view.ViewGroup; 28 | import android.view.inputmethod.EditorInfo; 29 | import android.view.inputmethod.InputMethodManager; 30 | import android.widget.FrameLayout; 31 | import android.widget.RelativeLayout; 32 | import android.widget.TextView; 33 | 34 | import java.util.List; 35 | 36 | /** 37 | * Copyright (c) 2016-2017, j2Rong 38 | * 39 | * Licensed under the Apache License, Version 2.0 (the "License"); 40 | * you may not use this file except in compliance with the License. 41 | * You may obtain a copy of the License at 42 | * 43 | * http://www.apache.org/licenses/LICENSE-2.0 44 | * 45 | * Unless required by applicable law or agreed to in writing, software 46 | * distributed under the License is distributed on an "AS IS" BASIS, 47 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 48 | * See the License for the specific language governing permissions and 49 | * limitations under the License. 50 | */ 51 | public class MapSearchBar extends FrameLayout implements 52 | View.OnClickListener, PopupMenu.OnMenuItemClickListener, 53 | TextView.OnEditorActionListener, View.OnFocusChangeListener, 54 | SearchResultAdapter.OnSuggestionItemClickListener { 55 | 56 | public MapSearchBar(Context context, AttributeSet attrs) { 57 | super(context, attrs); 58 | init(attrs); 59 | } 60 | 61 | public MapSearchBar(Context context, AttributeSet attrs, int defStyleAttr) { 62 | super(context, attrs, defStyleAttr); 63 | init(attrs); 64 | } 65 | 66 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 67 | public MapSearchBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 68 | super(context, attrs, defStyleAttr, defStyleRes); 69 | init(attrs); 70 | } 71 | 72 | public interface MapSearchActionListener { 73 | boolean onSearchBarMenuItemClick(MenuItem item); 74 | void onNavigationClick(View v); 75 | void onHistoryClick(View v); 76 | void onSearchQuerySuggestionClick(SearchResult data); 77 | 78 | void onSearchConfirmedAsync(String query); 79 | void onSearchQueryChangedAsync(String query); 80 | } 81 | 82 | 83 | public static final int NAV_MAP_AUTO_NAV = 0; 84 | public static final int NAV_MAP_GOOGLE = 1; 85 | 86 | 87 | private CardView cardView = null; 88 | private AppCompatImageView btnMenu = null; 89 | private AppCompatImageView btnNavigation = null; 90 | private AppCompatImageView btnHistory = null; 91 | private AppCompatEditText searchEdit = null; 92 | private AppCompatImageView btnClear = null; 93 | private RelativeLayout containerSuggestion = null; 94 | private RecyclerView suggestionRecycler = null; 95 | private PopupMenu popupMenu = null; 96 | 97 | private SearchResultAdapter adapter; 98 | private MapSearchActionListener mListener = null; 99 | private SearchQueryThread queryThread = null; 100 | 101 | private int screenHeight = 0; 102 | private boolean isSearchEditFocused = false; 103 | private boolean focusCameFromHistory = false; 104 | 105 | private boolean suggestionsVisible; 106 | 107 | 108 | private String hint; 109 | private String emptyHistory; 110 | private int elevation; 111 | private int margin; 112 | private int navType; 113 | 114 | 115 | private void init(AttributeSet attrs) { 116 | inflate(getContext(), R.layout.search_bar, this); 117 | 118 | TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MapSearchBar); 119 | hint = a.getString(R.styleable.MapSearchBar_ms_searchHint); 120 | emptyHistory = a.getString(R.styleable.MapSearchBar_ms_defaultHistoryEmptyString); 121 | elevation = a.getDimensionPixelSize(R.styleable.MapSearchBar_ms_elevation, -1); 122 | margin = a.getDimensionPixelSize(R.styleable.MapSearchBar_ms_margin, -1); 123 | navType = a.getInt(R.styleable.MapSearchBar_ms_mapType, -1); 124 | 125 | a.recycle(); 126 | 127 | // default settings 128 | if (hint == null) hint = getContext().getString(R.string.ms_default_edit_hint); 129 | if (emptyHistory == null) emptyHistory = ""; 130 | if (elevation == -1) elevation = getResources().getDimensionPixelSize(R.dimen.ms_default_search_bar_elevation); 131 | if (margin == -1) margin = getResources().getDimensionPixelSize(R.dimen.ms_default_search_bar_margin); 132 | if (navType == -1) navType = NAV_MAP_AUTO_NAV; 133 | 134 | 135 | findViews(); 136 | setupViews(); 137 | 138 | DisplayMetrics displaymetrics = new DisplayMetrics(); 139 | ((Activity)getContext()).getWindowManager().getDefaultDisplay().getMetrics(displaymetrics); 140 | screenHeight = displaymetrics.heightPixels; 141 | } 142 | 143 | private void findViews() { 144 | cardView = findViewById(R.id.card); 145 | btnMenu = findViewById(R.id.btn_menu); 146 | btnNavigation = findViewById(R.id.btn_nav); 147 | btnHistory = findViewById(R.id.btn_history); 148 | searchEdit = findViewById(R.id.edit); 149 | btnClear = findViewById(R.id.btn_clear); 150 | containerSuggestion = findViewById(R.id.list_container); 151 | suggestionRecycler = findViewById(R.id.recycler); 152 | } 153 | 154 | private void setupViews() { 155 | cardView.setCardElevation(elevation); 156 | 157 | FrameLayout.LayoutParams lpCard = (FrameLayout.LayoutParams) cardView.getLayoutParams(); 158 | lpCard.setMargins(margin, margin, margin, margin); 159 | cardView.setLayoutParams(lpCard); 160 | 161 | if (popupMenu == null) 162 | btnMenu.setVisibility(GONE); 163 | 164 | btnHistory.setOnClickListener(this); 165 | 166 | int navResId = (navType == NAV_MAP_AUTO_NAV) ? R.drawable.a_90 : R.drawable.gmap_24; 167 | Drawable d = ContextCompat.getDrawable(getContext(), navResId); 168 | btnNavigation.setImageDrawable(d); 169 | btnNavigation.setOnClickListener(this); 170 | 171 | searchEdit.setHint(hint); 172 | searchEdit.setOnEditorActionListener(this); 173 | searchEdit.setOnFocusChangeListener(this); 174 | searchEdit.addTextChangedListener(new SearchEditTextWatcher()); 175 | 176 | btnClear.setVisibility(GONE); 177 | btnClear.setOnClickListener(this); 178 | 179 | adapter = new SearchResultAdapter(getContext(), this, emptyHistory); 180 | suggestionRecycler.setLayoutManager(new LinearLayoutManager(getContext())); 181 | suggestionRecycler.setAdapter(adapter); 182 | } 183 | 184 | 185 | // 186 | // animation 187 | // 188 | private int getSuggestionHeight() { 189 | // returns pixels 190 | int h = (int)(screenHeight * 0.45); 191 | return adapter.getSuggestedHeight(suggestionRecycler, h); 192 | } 193 | 194 | private void animateSuggestionList(int from, int to) { 195 | suggestionsVisible = (to > 0); 196 | 197 | final ViewGroup.LayoutParams lp = containerSuggestion.getLayoutParams(); 198 | if (to == 0 && lp.height == 0) 199 | return; // already collapsed 200 | 201 | if (to == 0) 202 | from = lp.height; 203 | 204 | ValueAnimator animator = ValueAnimator.ofInt(from, to); 205 | animator.setDuration(200); 206 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 207 | @Override 208 | public void onAnimationUpdate(ValueAnimator animation) { 209 | lp.height = (int) animation.getAnimatedValue(); 210 | containerSuggestion.setLayoutParams(lp); 211 | } 212 | }); 213 | 214 | // if (adapter.getItemCount() > 0) 215 | animator.start(); 216 | } 217 | 218 | 219 | 220 | @Override 221 | public void onClick(View v) { 222 | int id = v.getId(); // id == getId() 223 | 224 | if (id == R.id.btn_menu) { 225 | if (popupMenu != null) { 226 | popupMenu.show(); 227 | searchEdit.clearFocus(); // clear focus and hide suggestion list 228 | } 229 | } else if (id == R.id.btn_clear) { 230 | searchEdit.setText(""); // auto trigger onTextChanged 231 | //searchEdit.requestFocus(); // hide 232 | } else if (id == R.id.btn_nav) { 233 | if (mListener != null) 234 | mListener.onNavigationClick(btnNavigation); 235 | } else if (id == R.id.btn_history) { 236 | if (mListener != null) { 237 | mListener.onHistoryClick(btnHistory); 238 | focusCameFromHistory = true; 239 | searchEdit.setText(""); 240 | searchEdit.requestFocus(); 241 | } 242 | } 243 | } 244 | 245 | @Override 246 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 247 | if (actionId == EditorInfo.IME_ACTION_SEARCH) { 248 | 249 | String q = searchEdit.getText().toString(); 250 | 251 | // clear and hide suggestions 252 | searchEdit.setText(""); 253 | searchEdit.clearFocus(); 254 | 255 | if (mListener != null) { 256 | if (!TextUtils.isEmpty(q.trim())) 257 | mListener.onSearchConfirmedAsync(q); 258 | } 259 | 260 | return true; 261 | } 262 | 263 | return false; 264 | } 265 | 266 | private class SearchEditTextWatcher implements TextWatcher { 267 | @Override 268 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 269 | 270 | @Override 271 | public void afterTextChanged(Editable s) {} 272 | 273 | @Override 274 | public void onTextChanged(CharSequence s, int start, int before, int count) { 275 | if (TextUtils.isEmpty(s.toString())) { 276 | btnClear.setVisibility(GONE); 277 | } else { 278 | btnClear.setVisibility(VISIBLE); 279 | } 280 | 281 | if (queryThread != null) { 282 | queryThread.pushQuery(s.toString()); 283 | } 284 | } 285 | } 286 | 287 | @Override 288 | public void onFocusChange(View v, boolean hasFocus) { 289 | InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 290 | 291 | if (hasFocus) { 292 | if (!focusCameFromHistory && imm != null) 293 | imm.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT); 294 | focusCameFromHistory = false; 295 | 296 | isSearchEditFocused = true; 297 | if (adapter.getItemCount() > 0 && !suggestionsVisible) 298 | animateSuggestionList(0, getSuggestionHeight()); 299 | 300 | } else { 301 | if (imm != null) 302 | imm.hideSoftInputFromWindow(v.getWindowToken(), 0); 303 | 304 | isSearchEditFocused = false; 305 | if (suggestionsVisible) 306 | animateSuggestionList(-1, 0); 307 | } 308 | } 309 | 310 | @SuppressWarnings("SimplifiableIfStatement") 311 | @Override 312 | public boolean onMenuItemClick(MenuItem item) { 313 | if (mListener != null) 314 | return mListener.onSearchBarMenuItemClick(item); 315 | else 316 | return false; 317 | } 318 | 319 | @Override 320 | public void onSuggestionItemClick(int position, View v, SearchResult data) { 321 | if (mListener != null) 322 | mListener.onSearchQuerySuggestionClick(data); 323 | 324 | searchEdit.setText(""); 325 | searchEdit.clearFocus(); 326 | } 327 | 328 | 329 | /** 330 | * update search result list 331 | * @param list new search result list, null if only content changed 332 | * @param theQuery query of this search 333 | * 334 | */ 335 | public void updateSearchResult(List list, String theQuery) { 336 | int previous, current; 337 | 338 | previous = getSuggestionHeight(); 339 | 340 | if (list != null) 341 | adapter.setList(list, theQuery); 342 | 343 | adapter.notifyDataSetChanged(); 344 | current = getSuggestionHeight(); 345 | 346 | if (isSearchEditFocused) 347 | animateSuggestionList(previous, current); 348 | } 349 | 350 | 351 | public void inflateMenu(@MenuRes int menuRes) { 352 | btnMenu.setVisibility(VISIBLE); 353 | btnMenu.setOnClickListener(this); 354 | 355 | popupMenu = new PopupMenu(getContext(), btnMenu); 356 | popupMenu.setOnMenuItemClickListener(this); 357 | popupMenu.inflate(menuRes); 358 | popupMenu.setGravity(Gravity.END); 359 | } 360 | 361 | public void setMapSearchActionListener(MapSearchActionListener l) { 362 | mListener = l; 363 | if (queryThread != null) { 364 | queryThread.updateListener(mListener); 365 | } 366 | } 367 | 368 | public void startQueryThread() { 369 | if (queryThread == null) { 370 | queryThread = new SearchQueryThread(); 371 | queryThread.start(); 372 | } 373 | } 374 | 375 | public void stopQueryThread() { 376 | if (queryThread != null) { 377 | queryThread.quit(); 378 | queryThread = null; 379 | } 380 | } 381 | 382 | public void setNavigationType(int type) { 383 | navType = type; 384 | if (type != NAV_MAP_AUTO_NAV && type != NAV_MAP_GOOGLE) 385 | navType = NAV_MAP_AUTO_NAV; 386 | 387 | int navResId = (navType == NAV_MAP_AUTO_NAV) ? R.drawable.a_90 : R.drawable.gmap_24; 388 | Drawable d = ContextCompat.getDrawable(getContext(), navResId); 389 | btnNavigation.setImageDrawable(d); 390 | } 391 | 392 | 393 | 394 | 395 | } 396 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/java/com/rong/library/widget/mapsearchbar/OnSearchResultItemClickListener.java: -------------------------------------------------------------------------------- 1 | package com.rong.library.widget.mapsearchbar; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Copyright (c) 2016-2017, j2Rong 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | public interface OnSearchResultItemClickListener { 21 | void onItemViewClick(int position, View v); 22 | } 23 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/java/com/rong/library/widget/mapsearchbar/SearchQueryThread.java: -------------------------------------------------------------------------------- 1 | package com.rong.library.widget.mapsearchbar; 2 | 3 | import java.lang.ref.WeakReference; 4 | 5 | /** 6 | * Copyright (c) 2016-2017, j2Rong 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | class SearchQueryThread extends Thread { 21 | 22 | private volatile boolean bStopping = false; 23 | 24 | private String cachedQuery = null; 25 | private String strQuerying = ""; 26 | private WeakReference listener = null; 27 | 28 | void pushQuery(String query) { 29 | strQuerying = query; 30 | } 31 | 32 | void updateListener(MapSearchBar.MapSearchActionListener l) { 33 | if (l != null) { 34 | listener = new WeakReference<>(l); 35 | } else { 36 | listener = null; 37 | } 38 | } 39 | 40 | public void quit() { 41 | if (!bStopping) { 42 | bStopping = true; 43 | this.interrupt(); 44 | } 45 | } 46 | 47 | @Override 48 | public void run() { 49 | while (!bStopping) { 50 | if (strQuerying != null) { 51 | if (!strQuerying.equals(cachedQuery)) { 52 | cachedQuery = strQuerying; 53 | 54 | if (listener != null) { 55 | MapSearchBar.MapSearchActionListener l = listener.get(); 56 | if (l != null) 57 | l.onSearchQueryChangedAsync(cachedQuery); 58 | } 59 | } 60 | } 61 | 62 | try { 63 | Thread.sleep(500); 64 | } catch (InterruptedException ignored) { 65 | ignored.printStackTrace(); 66 | } 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/java/com/rong/library/widget/mapsearchbar/SearchResult.java: -------------------------------------------------------------------------------- 1 | package com.rong.library.widget.mapsearchbar; 2 | 3 | /** 4 | * Copyright (c) 2016-2017, j2Rong 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | public class SearchResult { 19 | 20 | public Object real; 21 | public int type; 22 | 23 | public String displayFirst; 24 | public String displaySecond; 25 | 26 | public static final int TYPE_LOCATION_RECENT = 0; 27 | public static final int TYPE_MAP_SEARCH_PLACE = 1; 28 | public static final int TYPE_MAP_SEARCH_WITHOUT_LOCATION = 2; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/java/com/rong/library/widget/mapsearchbar/SearchResultAdapter.java: -------------------------------------------------------------------------------- 1 | package com.rong.library.widget.mapsearchbar; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | import android.support.v4.content.ContextCompat; 6 | import android.support.v7.widget.AppCompatImageView; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.text.TextUtils; 9 | import android.util.DisplayMetrics; 10 | import android.util.TypedValue; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | import com.rong.library.widget.HighlightTextView; 16 | 17 | import java.lang.ref.WeakReference; 18 | import java.util.List; 19 | 20 | /** 21 | * Copyright (c) 2016-2017, j2Rong 22 | * 23 | * Licensed under the Apache License, Version 2.0 (the "License"); 24 | * you may not use this file except in compliance with the License. 25 | * You may obtain a copy of the License at 26 | * 27 | * http://www.apache.org/licenses/LICENSE-2.0 28 | * 29 | * Unless required by applicable law or agreed to in writing, software 30 | * distributed under the License is distributed on an "AS IS" BASIS, 31 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | * See the License for the specific language governing permissions and 33 | * limitations under the License. 34 | */ 35 | class SearchResultAdapter extends RecyclerView.Adapter implements 36 | OnSearchResultItemClickListener { 37 | 38 | interface OnSuggestionItemClickListener { 39 | void onSuggestionItemClick(int pos, View v, SearchResult data); 40 | } 41 | 42 | private List mList = null; 43 | private String currentQuery; 44 | 45 | private WeakReference mListener; 46 | private Context mContext; 47 | private View calcView = null; 48 | 49 | private String defEmptyStr; 50 | private Drawable iconHistory; 51 | private Drawable iconSearch; 52 | private Drawable iconPlace; 53 | 54 | SearchResultAdapter(Context c, OnSuggestionItemClickListener listener, String empty) { 55 | mContext = c; 56 | mListener = new WeakReference<>(listener); 57 | defEmptyStr = empty; 58 | 59 | iconHistory = ContextCompat.getDrawable(c, R.drawable.ic_history_white_24dp); 60 | iconSearch = ContextCompat.getDrawable(c, R.drawable.ic_search_white_24dp); 61 | iconPlace = ContextCompat.getDrawable(c, R.drawable.ic_place_white_24dp); 62 | } 63 | 64 | 65 | @Override 66 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 67 | View v = LayoutInflater.from(mContext).inflate(R.layout.item_search_result, parent, false); 68 | return new ViewHolder(v, this); 69 | } 70 | 71 | @Override 72 | public void onBindViewHolder(ViewHolder holder, int position) { 73 | SearchResult r = getItem(position); 74 | 75 | if (r != null) { 76 | holder.txt1st.setText(r.displayFirst); 77 | holder.txt2nd.setText(r.displaySecond); 78 | 79 | // highlight 80 | if (!TextUtils.isEmpty(currentQuery)) { 81 | holder.txt1st.highlight(currentQuery); 82 | holder.txt2nd.highlight(currentQuery); 83 | } 84 | 85 | switch (r.type) { 86 | case SearchResult.TYPE_LOCATION_RECENT: { 87 | if (TextUtils.isEmpty(r.displayFirst)) { 88 | holder.txt1st.setText(defEmptyStr); 89 | } 90 | 91 | holder.icon.setImageDrawable(iconHistory); 92 | break; 93 | } 94 | case SearchResult.TYPE_MAP_SEARCH_PLACE: { 95 | holder.icon.setImageDrawable(iconPlace); 96 | break; 97 | } 98 | case SearchResult.TYPE_MAP_SEARCH_WITHOUT_LOCATION: { 99 | holder.icon.setImageDrawable(iconSearch); 100 | } 101 | } 102 | } 103 | } 104 | 105 | private SearchResult getItem(int position) { 106 | if (mList == null || mList.size() == 0) 107 | return null; 108 | else 109 | return mList.get(position); 110 | } 111 | 112 | @Override 113 | public int getItemCount() { 114 | return (mList == null) ? 0 : mList.size(); 115 | } 116 | 117 | void setList(List list, String query) { 118 | mList = list; 119 | currentQuery = query; 120 | } 121 | 122 | int getSuggestedHeight(RecyclerView parent, int max) { 123 | if (getItemCount() == 0) 124 | return 0; 125 | 126 | if (calcView == null) 127 | calcView = LayoutInflater.from(mContext).inflate(R.layout.item_search_result, parent, false); 128 | 129 | HighlightTextView txt1st = calcView.findViewById(R.id.txt_1st); 130 | HighlightTextView txt2nd = calcView.findViewById(R.id.txt_2nd); 131 | 132 | int total = 0; 133 | 134 | // 1dp separator 135 | DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); 136 | total += ((int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, metrics))); 137 | 138 | for (int i = 0; i < getItemCount(); i++) { 139 | SearchResult r = getItem(i); 140 | if (r != null) { 141 | txt1st.setText(r.displayFirst); 142 | txt2nd.setText(r.displaySecond); 143 | 144 | calcView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.EXACTLY), 145 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); 146 | total += calcView.getMeasuredHeight(); 147 | 148 | if (total > max) { 149 | total = max; 150 | break; 151 | } 152 | } 153 | } 154 | 155 | return total; 156 | } 157 | 158 | @Override 159 | public void onItemViewClick(int position, View v) { 160 | if (mListener != null) { 161 | if (mListener.get() != null) { 162 | mListener.get().onSuggestionItemClick(position, v, getItem(position)); 163 | } 164 | } 165 | } 166 | 167 | public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 168 | 169 | AppCompatImageView icon; 170 | HighlightTextView txt1st; 171 | HighlightTextView txt2nd; 172 | 173 | private WeakReference listener; 174 | 175 | ViewHolder(View itemView, OnSearchResultItemClickListener l) { 176 | super(itemView); 177 | 178 | icon = itemView.findViewById(R.id.image); 179 | txt1st = itemView.findViewById(R.id.txt_1st); 180 | txt2nd = itemView.findViewById(R.id.txt_2nd); 181 | 182 | listener = new WeakReference<>(l); 183 | itemView.setOnClickListener(this); 184 | } 185 | 186 | @Override 187 | public void onClick(View v) { 188 | if (listener != null) { 189 | if (listener.get() != null) 190 | listener.get().onItemViewClick(getAdapterPosition(), v); 191 | } 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-hdpi/ic_arrow_back_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-hdpi/ic_arrow_back_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-hdpi/ic_close_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-hdpi/ic_close_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-hdpi/ic_history_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-hdpi/ic_history_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-hdpi/ic_place_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-hdpi/ic_place_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-hdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-hdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-mdpi/ic_arrow_back_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-mdpi/ic_arrow_back_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-mdpi/ic_close_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-mdpi/ic_close_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-mdpi/ic_history_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-mdpi/ic_history_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-mdpi/ic_place_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-mdpi/ic_place_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-mdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-mdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xhdpi/ic_arrow_back_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xhdpi/ic_arrow_back_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xhdpi/ic_close_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xhdpi/ic_close_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xhdpi/ic_history_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xhdpi/ic_history_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xhdpi/ic_place_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xhdpi/ic_place_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxhdpi/ic_arrow_back_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxhdpi/ic_arrow_back_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxhdpi/ic_history_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxhdpi/ic_history_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxhdpi/ic_place_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxhdpi/ic_place_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxxhdpi/ic_arrow_back_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxxhdpi/ic_arrow_back_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxxhdpi/ic_history_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxxhdpi/ic_history_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxxhdpi/ic_place_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxxhdpi/ic_place_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j2rong/FakeLocation/d8a2cb2d52ecee4cbd1b908e3d712029a6b8eee9/mapsearchbar/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/layout/item_search_result.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 22 | 23 | 28 | 29 | 39 | 40 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/layout/search_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 14 | 15 | 19 | 20 | 34 | 35 | 48 | 49 | 62 | 63 | 75 | 76 | 77 | 81 | 82 | 85 | 86 | 100 | 101 | 113 | 114 | 115 | 116 | 123 | 124 | 128 | 129 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/values/attr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #E6FFFFFF 5 | @color/ms_material_grey_600 6 | #9C27B0 7 | 8 | #ffe0e0e0 9 | #ff757575 10 | 11 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4dp 5 | 8dp 6 | 7 | 8dp 8 | 9 | -------------------------------------------------------------------------------- /mapsearchbar/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Search 4 | 5 | 6 | -------------------------------------------------------------------------------- /markdownview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /markdownview/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /markdownview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt') 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | //provided 'com.android.support:appcompat-v7:23.4.0' 24 | } 25 | -------------------------------------------------------------------------------- /markdownview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /markdownview/src/main/assets/html/css/github-gist.css: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub Gist Theme 3 | * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | background: white; 9 | padding: 0.5em; 10 | color: #333333; 11 | overflow-x: auto; 12 | -webkit-text-size-adjust: none; 13 | } 14 | 15 | .hljs-comment, 16 | .bash .hljs-shebang, 17 | .java .hljs-javadoc, 18 | .javascript .hljs-javadoc, 19 | .rust .hljs-preprocessor { 20 | color: #969896; 21 | } 22 | 23 | .hljs-string, 24 | .apache .hljs-sqbracket, 25 | .coffeescript .hljs-subst, 26 | .coffeescript .hljs-regexp, 27 | .cpp .hljs-preprocessor, 28 | .c .hljs-preprocessor, 29 | .javascript .hljs-regexp, 30 | .json .hljs-attribute, 31 | .makefile .hljs-variable, 32 | .markdown .hljs-value, 33 | .markdown .hljs-link_label, 34 | .markdown .hljs-strong, 35 | .markdown .hljs-emphasis, 36 | .markdown .hljs-blockquote, 37 | .nginx .hljs-regexp, 38 | .nginx .hljs-number, 39 | .objectivec .hljs-preprocessor .hljs-title, 40 | .perl .hljs-regexp, 41 | .php .hljs-regexp, 42 | .xml .hljs-value, 43 | .less .hljs-built_in, 44 | .scss .hljs-built_in { 45 | color: #df5000; 46 | } 47 | 48 | .hljs-keyword, 49 | .css .hljs-at_rule, 50 | .css .hljs-important, 51 | .http .hljs-request, 52 | .ini .hljs-setting, 53 | .haskell .hljs-type, 54 | .java .hljs-javadoctag, 55 | .javascript .hljs-tag, 56 | .javascript .hljs-javadoctag, 57 | .nginx .hljs-title, 58 | .objectivec .hljs-preprocessor, 59 | .php .hljs-phpdoc, 60 | .sql .hljs-built_in, 61 | .less .hljs-tag, 62 | .less .hljs-at_rule, 63 | .scss .hljs-tag, 64 | .scss .hljs-at_rule, 65 | .scss .hljs-important, 66 | .stylus .hljs-at_rule, 67 | .go .hljs-typename, 68 | .swift .hljs-preprocessor { 69 | color: #a71d5d; 70 | } 71 | 72 | .apache .hljs-common, 73 | .apache .hljs-cbracket, 74 | .apache .hljs-keyword, 75 | .bash .hljs-literal, 76 | .bash .hljs-built_in, 77 | .coffeescript .hljs-literal, 78 | .coffeescript .hljs-built_in, 79 | .coffeescript .hljs-number, 80 | .cpp .hljs-number, 81 | .cpp .hljs-built_in, 82 | .c .hljs-number, 83 | .c .hljs-built_in, 84 | .cs .hljs-number, 85 | .cs .hljs-built_in, 86 | .css .hljs-attribute, 87 | .css .hljs-hexcolor, 88 | .css .hljs-number, 89 | .css .hljs-function, 90 | .haskell .hljs-number, 91 | .http .hljs-literal, 92 | .http .hljs-attribute, 93 | .java .hljs-number, 94 | .javascript .hljs-built_in, 95 | .javascript .hljs-literal, 96 | .javascript .hljs-number, 97 | .json .hljs-number, 98 | .makefile .hljs-keyword, 99 | .markdown .hljs-link_reference, 100 | .nginx .hljs-built_in, 101 | .objectivec .hljs-literal, 102 | .objectivec .hljs-number, 103 | .objectivec .hljs-built_in, 104 | .php .hljs-literal, 105 | .php .hljs-number, 106 | .python .hljs-number, 107 | .ruby .hljs-prompt, 108 | .ruby .hljs-constant, 109 | .ruby .hljs-number, 110 | .ruby .hljs-subst .hljs-keyword, 111 | .ruby .hljs-symbol, 112 | .rust .hljs-number, 113 | .sql .hljs-number, 114 | .puppet .hljs-function, 115 | .less .hljs-number, 116 | .less .hljs-hexcolor, 117 | .less .hljs-function, 118 | .less .hljs-attribute, 119 | .scss .hljs-preprocessor, 120 | .scss .hljs-number, 121 | .scss .hljs-hexcolor, 122 | .scss .hljs-function, 123 | .scss .hljs-attribute, 124 | .stylus .hljs-number, 125 | .stylus .hljs-hexcolor, 126 | .stylus .hljs-attribute, 127 | .stylus .hljs-params, 128 | .go .hljs-built_in, 129 | .go .hljs-constant, 130 | .swift .hljs-built_in, 131 | .swift .hljs-number { 132 | color: #0086b3; 133 | } 134 | 135 | .apache .hljs-tag, 136 | .cs .hljs-xmlDocTag, 137 | .css .hljs-tag, 138 | .xml .hljs-title, 139 | .stylus .hljs-tag { 140 | color: #63a35c; 141 | } 142 | 143 | .bash .hljs-variable, 144 | .cs .hljs-preprocessor, 145 | .cs .hljs-preprocessor .hljs-keyword, 146 | .css .hljs-attr_selector, 147 | .css .hljs-value, 148 | .ini .hljs-value, 149 | .ini .hljs-keyword, 150 | .javascript .hljs-tag .hljs-title, 151 | .makefile .hljs-constant, 152 | .nginx .hljs-variable, 153 | .xml .hljs-tag, 154 | .scss .hljs-variable { 155 | color: #333333; 156 | } 157 | 158 | .bash .hljs-title, 159 | .coffeescript .hljs-title, 160 | .cpp .hljs-title, 161 | .c .hljs-title, 162 | .cs .hljs-title, 163 | .css .hljs-id, 164 | .css .hljs-class, 165 | .css .hljs-pseudo, 166 | .ini .hljs-title, 167 | .haskell .hljs-title, 168 | .haskell .hljs-pragma, 169 | .java .hljs-title, 170 | .javascript .hljs-title, 171 | .makefile .hljs-title, 172 | .objectivec .hljs-title, 173 | .perl .hljs-sub, 174 | .php .hljs-title, 175 | .python .hljs-decorator, 176 | .python .hljs-title, 177 | .ruby .hljs-parent, 178 | .ruby .hljs-title, 179 | .rust .hljs-title, 180 | .xml .hljs-attribute, 181 | .puppet .hljs-title, 182 | .less .hljs-id, 183 | .less .hljs-pseudo, 184 | .less .hljs-class, 185 | .scss .hljs-id, 186 | .scss .hljs-pseudo, 187 | .scss .hljs-class, 188 | .stylus .hljs-class, 189 | .stylus .hljs-id, 190 | .stylus .hljs-pseudo, 191 | .stylus .hljs-title, 192 | .swift .hljs-title, 193 | .diff .hljs-chunk { 194 | color: #795da3; 195 | } 196 | 197 | .coffeescript .hljs-reserved, 198 | .coffeescript .hljs-attribute { 199 | color: #1d3e81; 200 | } 201 | 202 | .diff .hljs-chunk { 203 | font-weight: bold; 204 | } 205 | 206 | .diff .hljs-addition { 207 | color: #55a532; 208 | background-color: #eaffea; 209 | } 210 | 211 | .diff .hljs-deletion { 212 | color: #bd2c00; 213 | background-color: #ffecec; 214 | } 215 | 216 | .markdown .hljs-link_url { 217 | text-decoration: underline; 218 | } 219 | -------------------------------------------------------------------------------- /markdownview/src/main/assets/html/css/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | github.com style (c) Vasily Polovnyov 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | color: #333; 10 | background: #f8f8f8; 11 | -webkit-text-size-adjust: none; 12 | } 13 | 14 | .hljs-comment, 15 | .diff .hljs-header { 16 | color: #998; 17 | font-style: italic; 18 | } 19 | 20 | .hljs-keyword, 21 | .css .rule .hljs-keyword, 22 | .hljs-winutils, 23 | .nginx .hljs-title, 24 | .hljs-subst, 25 | .hljs-request, 26 | .hljs-status { 27 | color: #333; 28 | font-weight: bold; 29 | } 30 | 31 | .hljs-number, 32 | .hljs-hexcolor, 33 | .ruby .hljs-constant { 34 | color: #008080; 35 | } 36 | 37 | .hljs-string, 38 | .hljs-tag .hljs-value, 39 | .hljs-doctag, 40 | .tex .hljs-formula { 41 | color: #d14; 42 | } 43 | 44 | .hljs-title, 45 | .hljs-id, 46 | .scss .hljs-preprocessor { 47 | color: #900; 48 | font-weight: bold; 49 | } 50 | 51 | .hljs-list .hljs-keyword, 52 | .hljs-subst { 53 | font-weight: normal; 54 | } 55 | 56 | .hljs-class .hljs-title, 57 | .hljs-type, 58 | .vhdl .hljs-literal, 59 | .tex .hljs-command { 60 | color: #458; 61 | font-weight: bold; 62 | } 63 | 64 | .hljs-tag, 65 | .hljs-tag .hljs-title, 66 | .hljs-rule .hljs-property, 67 | .django .hljs-tag .hljs-keyword { 68 | color: #000080; 69 | font-weight: normal; 70 | } 71 | 72 | .hljs-attribute, 73 | .hljs-variable, 74 | .lisp .hljs-body, 75 | .hljs-name { 76 | color: #008080; 77 | } 78 | 79 | .hljs-regexp { 80 | color: #009926; 81 | } 82 | 83 | .hljs-symbol, 84 | .ruby .hljs-symbol .hljs-string, 85 | .lisp .hljs-keyword, 86 | .clojure .hljs-keyword, 87 | .scheme .hljs-keyword, 88 | .tex .hljs-special, 89 | .hljs-prompt { 90 | color: #990073; 91 | } 92 | 93 | .hljs-built_in { 94 | color: #0086b3; 95 | } 96 | 97 | .hljs-preprocessor, 98 | .hljs-pragma, 99 | .hljs-pi, 100 | .hljs-doctype, 101 | .hljs-shebang, 102 | .hljs-cdata { 103 | color: #999; 104 | font-weight: bold; 105 | } 106 | 107 | .hljs-deletion { 108 | background: #fdd; 109 | } 110 | 111 | .hljs-addition { 112 | background: #dfd; 113 | } 114 | 115 | .diff .hljs-change { 116 | background: #0086b3; 117 | } 118 | 119 | .hljs-chunk { 120 | color: #aaa; 121 | } 122 | -------------------------------------------------------------------------------- /markdownview/src/main/assets/html/js/marked.js: -------------------------------------------------------------------------------- 1 | /** 2 | * marked - a markdown parser 3 | * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/chjj/marked 5 | */ 6 | 7 | ;(function() { 8 | 9 | /** 10 | * Block-Level Grammar 11 | */ 12 | 13 | var block = { 14 | newline: /^\n+/, 15 | code: /^( {4}[^\n]+\n*)+/, 16 | fences: noop, 17 | hr: /^( *[-*_]){3,} *(?:\n+|$)/, 18 | heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, 19 | nptable: noop, 20 | lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, 21 | blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, 22 | list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, 23 | html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/, 24 | def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, 25 | table: noop, 26 | paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, 27 | text: /^[^\n]+/ 28 | }; 29 | 30 | block.bullet = /(?:[*+-]|\d+\.)/; 31 | block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; 32 | block.item = replace(block.item, 'gm') 33 | (/bull/g, block.bullet) 34 | (); 35 | 36 | block.list = replace(block.list) 37 | (/bull/g, block.bullet) 38 | ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))') 39 | ('def', '\\n+(?=' + block.def.source + ')') 40 | (); 41 | 42 | block.blockquote = replace(block.blockquote) 43 | ('def', block.def) 44 | (); 45 | 46 | block._tag = '(?!(?:' 47 | + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' 48 | + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' 49 | + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b'; 50 | 51 | block.html = replace(block.html) 52 | ('comment', //) 53 | ('closed', /<(tag)[\s\S]+?<\/\1>/) 54 | ('closing', /])*?>/) 55 | (/tag/g, block._tag) 56 | (); 57 | 58 | block.paragraph = replace(block.paragraph) 59 | ('hr', block.hr) 60 | ('heading', block.heading) 61 | ('lheading', block.lheading) 62 | ('blockquote', block.blockquote) 63 | ('tag', '<' + block._tag) 64 | ('def', block.def) 65 | (); 66 | 67 | /** 68 | * Normal Block Grammar 69 | */ 70 | 71 | block.normal = merge({}, block); 72 | 73 | /** 74 | * GFM Block Grammar 75 | */ 76 | 77 | block.gfm = merge({}, block.normal, { 78 | fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/, 79 | paragraph: /^/, 80 | heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ 81 | }); 82 | 83 | block.gfm.paragraph = replace(block.paragraph) 84 | ('(?!', '(?!' 85 | + block.gfm.fences.source.replace('\\1', '\\2') + '|' 86 | + block.list.source.replace('\\1', '\\3') + '|') 87 | (); 88 | 89 | /** 90 | * GFM + Tables Block Grammar 91 | */ 92 | 93 | block.tables = merge({}, block.gfm, { 94 | nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, 95 | table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ 96 | }); 97 | 98 | /** 99 | * Block Lexer 100 | */ 101 | 102 | function Lexer(options) { 103 | this.tokens = []; 104 | this.tokens.links = {}; 105 | this.options = options || marked.defaults; 106 | this.rules = block.normal; 107 | 108 | if (this.options.gfm) { 109 | if (this.options.tables) { 110 | this.rules = block.tables; 111 | } else { 112 | this.rules = block.gfm; 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * Expose Block Rules 119 | */ 120 | 121 | Lexer.rules = block; 122 | 123 | /** 124 | * Static Lex Method 125 | */ 126 | 127 | Lexer.lex = function(src, options) { 128 | var lexer = new Lexer(options); 129 | return lexer.lex(src); 130 | }; 131 | 132 | /** 133 | * Preprocessing 134 | */ 135 | 136 | Lexer.prototype.lex = function(src) { 137 | src = src 138 | .replace(/\r\n|\r/g, '\n') 139 | .replace(/\t/g, ' ') 140 | .replace(/\u00a0/g, ' ') 141 | .replace(/\u2424/g, '\n'); 142 | 143 | return this.token(src, true); 144 | }; 145 | 146 | /** 147 | * Lexing 148 | */ 149 | 150 | Lexer.prototype.token = function(src, top, bq) { 151 | var src = src.replace(/^ +$/gm, '') 152 | , next 153 | , loose 154 | , cap 155 | , bull 156 | , b 157 | , item 158 | , space 159 | , i 160 | , l; 161 | 162 | while (src) { 163 | // newline 164 | if (cap = this.rules.newline.exec(src)) { 165 | src = src.substring(cap[0].length); 166 | if (cap[0].length > 1) { 167 | this.tokens.push({ 168 | type: 'space' 169 | }); 170 | } 171 | } 172 | 173 | // code 174 | if (cap = this.rules.code.exec(src)) { 175 | src = src.substring(cap[0].length); 176 | cap = cap[0].replace(/^ {4}/gm, ''); 177 | this.tokens.push({ 178 | type: 'code', 179 | text: !this.options.pedantic 180 | ? cap.replace(/\n+$/, '') 181 | : cap 182 | }); 183 | continue; 184 | } 185 | 186 | // fences (gfm) 187 | if (cap = this.rules.fences.exec(src)) { 188 | src = src.substring(cap[0].length); 189 | this.tokens.push({ 190 | type: 'code', 191 | lang: cap[2], 192 | text: cap[3] || '' 193 | }); 194 | continue; 195 | } 196 | 197 | // heading 198 | if (cap = this.rules.heading.exec(src)) { 199 | src = src.substring(cap[0].length); 200 | this.tokens.push({ 201 | type: 'heading', 202 | depth: cap[1].length, 203 | text: cap[2] 204 | }); 205 | continue; 206 | } 207 | 208 | // table no leading pipe (gfm) 209 | if (top && (cap = this.rules.nptable.exec(src))) { 210 | src = src.substring(cap[0].length); 211 | 212 | item = { 213 | type: 'table', 214 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 215 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 216 | cells: cap[3].replace(/\n$/, '').split('\n') 217 | }; 218 | 219 | for (i = 0; i < item.align.length; i++) { 220 | if (/^ *-+: *$/.test(item.align[i])) { 221 | item.align[i] = 'right'; 222 | } else if (/^ *:-+: *$/.test(item.align[i])) { 223 | item.align[i] = 'center'; 224 | } else if (/^ *:-+ *$/.test(item.align[i])) { 225 | item.align[i] = 'left'; 226 | } else { 227 | item.align[i] = null; 228 | } 229 | } 230 | 231 | for (i = 0; i < item.cells.length; i++) { 232 | item.cells[i] = item.cells[i].split(/ *\| */); 233 | } 234 | 235 | this.tokens.push(item); 236 | 237 | continue; 238 | } 239 | 240 | // lheading 241 | if (cap = this.rules.lheading.exec(src)) { 242 | src = src.substring(cap[0].length); 243 | this.tokens.push({ 244 | type: 'heading', 245 | depth: cap[2] === '=' ? 1 : 2, 246 | text: cap[1] 247 | }); 248 | continue; 249 | } 250 | 251 | // hr 252 | if (cap = this.rules.hr.exec(src)) { 253 | src = src.substring(cap[0].length); 254 | this.tokens.push({ 255 | type: 'hr' 256 | }); 257 | continue; 258 | } 259 | 260 | // blockquote 261 | if (cap = this.rules.blockquote.exec(src)) { 262 | src = src.substring(cap[0].length); 263 | 264 | this.tokens.push({ 265 | type: 'blockquote_start' 266 | }); 267 | 268 | cap = cap[0].replace(/^ *> ?/gm, ''); 269 | 270 | // Pass `top` to keep the current 271 | // "toplevel" state. This is exactly 272 | // how markdown.pl works. 273 | this.token(cap, top, true); 274 | 275 | this.tokens.push({ 276 | type: 'blockquote_end' 277 | }); 278 | 279 | continue; 280 | } 281 | 282 | // list 283 | if (cap = this.rules.list.exec(src)) { 284 | src = src.substring(cap[0].length); 285 | bull = cap[2]; 286 | 287 | this.tokens.push({ 288 | type: 'list_start', 289 | ordered: bull.length > 1 290 | }); 291 | 292 | // Get each top-level item. 293 | cap = cap[0].match(this.rules.item); 294 | 295 | next = false; 296 | l = cap.length; 297 | i = 0; 298 | 299 | for (; i < l; i++) { 300 | item = cap[i]; 301 | 302 | // Remove the list item's bullet 303 | // so it is seen as the next token. 304 | space = item.length; 305 | item = item.replace(/^ *([*+-]|\d+\.) +/, ''); 306 | 307 | // Outdent whatever the 308 | // list item contains. Hacky. 309 | if (~item.indexOf('\n ')) { 310 | space -= item.length; 311 | item = !this.options.pedantic 312 | ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') 313 | : item.replace(/^ {1,4}/gm, ''); 314 | } 315 | 316 | // Determine whether the next list item belongs here. 317 | // Backpedal if it does not belong in this list. 318 | if (this.options.smartLists && i !== l - 1) { 319 | b = block.bullet.exec(cap[i + 1])[0]; 320 | if (bull !== b && !(bull.length > 1 && b.length > 1)) { 321 | src = cap.slice(i + 1).join('\n') + src; 322 | i = l - 1; 323 | } 324 | } 325 | 326 | // Determine whether item is loose or not. 327 | // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ 328 | // for discount behavior. 329 | loose = next || /\n\n(?!\s*$)/.test(item); 330 | if (i !== l - 1) { 331 | next = item.charAt(item.length - 1) === '\n'; 332 | if (!loose) loose = next; 333 | } 334 | 335 | this.tokens.push({ 336 | type: loose 337 | ? 'loose_item_start' 338 | : 'list_item_start' 339 | }); 340 | 341 | // Recurse. 342 | this.token(item, false, bq); 343 | 344 | this.tokens.push({ 345 | type: 'list_item_end' 346 | }); 347 | } 348 | 349 | this.tokens.push({ 350 | type: 'list_end' 351 | }); 352 | 353 | continue; 354 | } 355 | 356 | // html 357 | if (cap = this.rules.html.exec(src)) { 358 | src = src.substring(cap[0].length); 359 | this.tokens.push({ 360 | type: this.options.sanitize 361 | ? 'paragraph' 362 | : 'html', 363 | pre: !this.options.sanitizer 364 | && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), 365 | text: cap[0] 366 | }); 367 | continue; 368 | } 369 | 370 | // def 371 | if ((!bq && top) && (cap = this.rules.def.exec(src))) { 372 | src = src.substring(cap[0].length); 373 | this.tokens.links[cap[1].toLowerCase()] = { 374 | href: cap[2], 375 | title: cap[3] 376 | }; 377 | continue; 378 | } 379 | 380 | // table (gfm) 381 | if (top && (cap = this.rules.table.exec(src))) { 382 | src = src.substring(cap[0].length); 383 | 384 | item = { 385 | type: 'table', 386 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 387 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 388 | cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') 389 | }; 390 | 391 | for (i = 0; i < item.align.length; i++) { 392 | if (/^ *-+: *$/.test(item.align[i])) { 393 | item.align[i] = 'right'; 394 | } else if (/^ *:-+: *$/.test(item.align[i])) { 395 | item.align[i] = 'center'; 396 | } else if (/^ *:-+ *$/.test(item.align[i])) { 397 | item.align[i] = 'left'; 398 | } else { 399 | item.align[i] = null; 400 | } 401 | } 402 | 403 | for (i = 0; i < item.cells.length; i++) { 404 | item.cells[i] = item.cells[i] 405 | .replace(/^ *\| *| *\| *$/g, '') 406 | .split(/ *\| */); 407 | } 408 | 409 | this.tokens.push(item); 410 | 411 | continue; 412 | } 413 | 414 | // top-level paragraph 415 | if (top && (cap = this.rules.paragraph.exec(src))) { 416 | src = src.substring(cap[0].length); 417 | this.tokens.push({ 418 | type: 'paragraph', 419 | text: cap[1].charAt(cap[1].length - 1) === '\n' 420 | ? cap[1].slice(0, -1) 421 | : cap[1] 422 | }); 423 | continue; 424 | } 425 | 426 | // text 427 | if (cap = this.rules.text.exec(src)) { 428 | // Top-level should never reach here. 429 | src = src.substring(cap[0].length); 430 | this.tokens.push({ 431 | type: 'text', 432 | text: cap[0] 433 | }); 434 | continue; 435 | } 436 | 437 | if (src) { 438 | throw new 439 | Error('Infinite loop on byte: ' + src.charCodeAt(0)); 440 | } 441 | } 442 | 443 | return this.tokens; 444 | }; 445 | 446 | /** 447 | * Inline-Level Grammar 448 | */ 449 | 450 | var inline = { 451 | escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, 452 | autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, 453 | url: noop, 454 | tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, 455 | link: /^!?\[(inside)\]\(href\)/, 456 | reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, 457 | nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, 458 | strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, 459 | em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, 460 | code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, 461 | br: /^ {2,}\n(?!\s*$)/, 462 | del: noop, 463 | text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; 468 | 469 | inline.link = replace(inline.link) 470 | ('inside', inline._inside) 471 | ('href', inline._href) 472 | (); 473 | 474 | inline.reflink = replace(inline.reflink) 475 | ('inside', inline._inside) 476 | (); 477 | 478 | /** 479 | * Normal Inline Grammar 480 | */ 481 | 482 | inline.normal = merge({}, inline); 483 | 484 | /** 485 | * Pedantic Inline Grammar 486 | */ 487 | 488 | inline.pedantic = merge({}, inline.normal, { 489 | strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, 490 | em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ 491 | }); 492 | 493 | /** 494 | * GFM Inline Grammar 495 | */ 496 | 497 | inline.gfm = merge({}, inline.normal, { 498 | escape: replace(inline.escape)('])', '~|])')(), 499 | url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, 500 | del: /^~~(?=\S)([\s\S]*?\S)~~/, 501 | text: replace(inline.text) 502 | (']|', '~]|') 503 | ('|', '|https?://|') 504 | () 505 | }); 506 | 507 | /** 508 | * GFM + Line Breaks Inline Grammar 509 | */ 510 | 511 | inline.breaks = merge({}, inline.gfm, { 512 | br: replace(inline.br)('{2,}', '*')(), 513 | text: replace(inline.gfm.text)('{2,}', '*')() 514 | }); 515 | 516 | /** 517 | * Inline Lexer & Compiler 518 | */ 519 | 520 | function InlineLexer(links, options) { 521 | this.options = options || marked.defaults; 522 | this.links = links; 523 | this.rules = inline.normal; 524 | this.renderer = this.options.renderer || new Renderer; 525 | this.renderer.options = this.options; 526 | 527 | if (!this.links) { 528 | throw new 529 | Error('Tokens array requires a `links` property.'); 530 | } 531 | 532 | if (this.options.gfm) { 533 | if (this.options.breaks) { 534 | this.rules = inline.breaks; 535 | } else { 536 | this.rules = inline.gfm; 537 | } 538 | } else if (this.options.pedantic) { 539 | this.rules = inline.pedantic; 540 | } 541 | } 542 | 543 | /** 544 | * Expose Inline Rules 545 | */ 546 | 547 | InlineLexer.rules = inline; 548 | 549 | /** 550 | * Static Lexing/Compiling Method 551 | */ 552 | 553 | InlineLexer.output = function(src, links, options) { 554 | var inline = new InlineLexer(links, options); 555 | return inline.output(src); 556 | }; 557 | 558 | /** 559 | * Lexing/Compiling 560 | */ 561 | 562 | InlineLexer.prototype.output = function(src) { 563 | var out = '' 564 | , link 565 | , text 566 | , href 567 | , cap; 568 | 569 | while (src) { 570 | // escape 571 | if (cap = this.rules.escape.exec(src)) { 572 | src = src.substring(cap[0].length); 573 | out += cap[1]; 574 | continue; 575 | } 576 | 577 | // autolink 578 | if (cap = this.rules.autolink.exec(src)) { 579 | src = src.substring(cap[0].length); 580 | if (cap[2] === '@') { 581 | text = cap[1].charAt(6) === ':' 582 | ? this.mangle(cap[1].substring(7)) 583 | : this.mangle(cap[1]); 584 | href = this.mangle('mailto:') + text; 585 | } else { 586 | text = escape(cap[1]); 587 | href = text; 588 | } 589 | out += this.renderer.link(href, null, text); 590 | continue; 591 | } 592 | 593 | // url (gfm) 594 | if (!this.inLink && (cap = this.rules.url.exec(src))) { 595 | src = src.substring(cap[0].length); 596 | text = escape(cap[1]); 597 | href = text; 598 | out += this.renderer.link(href, null, text); 599 | continue; 600 | } 601 | 602 | // tag 603 | if (cap = this.rules.tag.exec(src)) { 604 | if (!this.inLink && /^/i.test(cap[0])) { 607 | this.inLink = false; 608 | } 609 | src = src.substring(cap[0].length); 610 | out += this.options.sanitize 611 | ? this.options.sanitizer 612 | ? this.options.sanitizer(cap[0]) 613 | : escape(cap[0]) 614 | : cap[0] 615 | continue; 616 | } 617 | 618 | // link 619 | if (cap = this.rules.link.exec(src)) { 620 | src = src.substring(cap[0].length); 621 | this.inLink = true; 622 | out += this.outputLink(cap, { 623 | href: cap[2], 624 | title: cap[3] 625 | }); 626 | this.inLink = false; 627 | continue; 628 | } 629 | 630 | // reflink, nolink 631 | if ((cap = this.rules.reflink.exec(src)) 632 | || (cap = this.rules.nolink.exec(src))) { 633 | src = src.substring(cap[0].length); 634 | link = (cap[2] || cap[1]).replace(/\s+/g, ' '); 635 | link = this.links[link.toLowerCase()]; 636 | if (!link || !link.href) { 637 | out += cap[0].charAt(0); 638 | src = cap[0].substring(1) + src; 639 | continue; 640 | } 641 | this.inLink = true; 642 | out += this.outputLink(cap, link); 643 | this.inLink = false; 644 | continue; 645 | } 646 | 647 | // strong 648 | if (cap = this.rules.strong.exec(src)) { 649 | src = src.substring(cap[0].length); 650 | out += this.renderer.strong(this.output(cap[2] || cap[1])); 651 | continue; 652 | } 653 | 654 | // em 655 | if (cap = this.rules.em.exec(src)) { 656 | src = src.substring(cap[0].length); 657 | out += this.renderer.em(this.output(cap[2] || cap[1])); 658 | continue; 659 | } 660 | 661 | // code 662 | if (cap = this.rules.code.exec(src)) { 663 | src = src.substring(cap[0].length); 664 | out += this.renderer.codespan(escape(cap[2], true)); 665 | continue; 666 | } 667 | 668 | // br 669 | if (cap = this.rules.br.exec(src)) { 670 | src = src.substring(cap[0].length); 671 | out += this.renderer.br(); 672 | continue; 673 | } 674 | 675 | // del (gfm) 676 | if (cap = this.rules.del.exec(src)) { 677 | src = src.substring(cap[0].length); 678 | out += this.renderer.del(this.output(cap[1])); 679 | continue; 680 | } 681 | 682 | // text 683 | if (cap = this.rules.text.exec(src)) { 684 | src = src.substring(cap[0].length); 685 | out += this.renderer.text(escape(this.smartypants(cap[0]))); 686 | continue; 687 | } 688 | 689 | if (src) { 690 | throw new 691 | Error('Infinite loop on byte: ' + src.charCodeAt(0)); 692 | } 693 | } 694 | 695 | return out; 696 | }; 697 | 698 | /** 699 | * Compile Link 700 | */ 701 | 702 | InlineLexer.prototype.outputLink = function(cap, link) { 703 | var href = escape(link.href) 704 | , title = link.title ? escape(link.title) : null; 705 | 706 | return cap[0].charAt(0) !== '!' 707 | ? this.renderer.link(href, title, this.output(cap[1])) 708 | : this.renderer.image(href, title, escape(cap[1])); 709 | }; 710 | 711 | /** 712 | * Smartypants Transformations 713 | */ 714 | 715 | InlineLexer.prototype.smartypants = function(text) { 716 | if (!this.options.smartypants) return text; 717 | return text 718 | // em-dashes 719 | .replace(/---/g, '\u2014') 720 | // en-dashes 721 | .replace(/--/g, '\u2013') 722 | // opening singles 723 | .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') 724 | // closing singles & apostrophes 725 | .replace(/'/g, '\u2019') 726 | // opening doubles 727 | .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') 728 | // closing doubles 729 | .replace(/"/g, '\u201d') 730 | // ellipses 731 | .replace(/\.{3}/g, '\u2026'); 732 | }; 733 | 734 | /** 735 | * Mangle Links 736 | */ 737 | 738 | InlineLexer.prototype.mangle = function(text) { 739 | if (!this.options.mangle) return text; 740 | var out = '' 741 | , l = text.length 742 | , i = 0 743 | , ch; 744 | 745 | for (; i < l; i++) { 746 | ch = text.charCodeAt(i); 747 | if (Math.random() > 0.5) { 748 | ch = 'x' + ch.toString(16); 749 | } 750 | out += '&#' + ch + ';'; 751 | } 752 | 753 | return out; 754 | }; 755 | 756 | /** 757 | * Renderer 758 | */ 759 | 760 | function Renderer(options) { 761 | this.options = options || {}; 762 | } 763 | 764 | Renderer.prototype.code = function(code, lang, escaped) { 765 | if (this.options.highlight) { 766 | var out = this.options.highlight(code, lang); 767 | if (out != null && out !== code) { 768 | escaped = true; 769 | code = out; 770 | } 771 | } 772 | 773 | if (!lang) { 774 | return '
'
 775 |       + (escaped ? code : escape(code, true))
 776 |       + '\n
'; 777 | } 778 | 779 | return '
'
 783 |     + (escaped ? code : escape(code, true))
 784 |     + '\n
\n'; 785 | }; 786 | 787 | Renderer.prototype.blockquote = function(quote) { 788 | return '
\n' + quote + '
\n'; 789 | }; 790 | 791 | Renderer.prototype.html = function(html) { 792 | return html; 793 | }; 794 | 795 | Renderer.prototype.heading = function(text, level, raw) { 796 | return '' 802 | + text 803 | + '\n'; 806 | }; 807 | 808 | Renderer.prototype.hr = function() { 809 | return this.options.xhtml ? '
\n' : '
\n'; 810 | }; 811 | 812 | Renderer.prototype.list = function(body, ordered) { 813 | var type = ordered ? 'ol' : 'ul'; 814 | return '<' + type + '>\n' + body + '\n'; 815 | }; 816 | 817 | Renderer.prototype.listitem = function(text) { 818 | return '
  • ' + text + '
  • \n'; 819 | }; 820 | 821 | Renderer.prototype.paragraph = function(text) { 822 | return '

    ' + text + '

    \n'; 823 | }; 824 | 825 | Renderer.prototype.table = function(header, body) { 826 | return '\n' 827 | + '\n' 828 | + header 829 | + '\n' 830 | + '\n' 831 | + body 832 | + '\n' 833 | + '
    \n'; 834 | }; 835 | 836 | Renderer.prototype.tablerow = function(content) { 837 | return '\n' + content + '\n'; 838 | }; 839 | 840 | Renderer.prototype.tablecell = function(content, flags) { 841 | var type = flags.header ? 'th' : 'td'; 842 | var tag = flags.align 843 | ? '<' + type + ' style="text-align:' + flags.align + '">' 844 | : '<' + type + '>'; 845 | return tag + content + '\n'; 846 | }; 847 | 848 | // span level renderer 849 | Renderer.prototype.strong = function(text) { 850 | return '' + text + ''; 851 | }; 852 | 853 | Renderer.prototype.em = function(text) { 854 | return '' + text + ''; 855 | }; 856 | 857 | Renderer.prototype.codespan = function(text) { 858 | return '' + text + ''; 859 | }; 860 | 861 | Renderer.prototype.br = function() { 862 | return this.options.xhtml ? '
    ' : '
    '; 863 | }; 864 | 865 | Renderer.prototype.del = function(text) { 866 | return '' + text + ''; 867 | }; 868 | 869 | Renderer.prototype.link = function(href, title, text) { 870 | if (this.options.sanitize) { 871 | try { 872 | var prot = decodeURIComponent(unescape(href)) 873 | .replace(/[^\w:]/g, '') 874 | .toLowerCase(); 875 | } catch (e) { 876 | return ''; 877 | } 878 | if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) { 879 | return ''; 880 | } 881 | } 882 | var out = '
    '; 887 | return out; 888 | }; 889 | 890 | Renderer.prototype.image = function(href, title, text) { 891 | var out = '' + text + '' : '>'; 896 | return out; 897 | }; 898 | 899 | Renderer.prototype.text = function(text) { 900 | return text; 901 | }; 902 | 903 | /** 904 | * Parsing & Compiling 905 | */ 906 | 907 | function Parser(options) { 908 | this.tokens = []; 909 | this.token = null; 910 | this.options = options || marked.defaults; 911 | this.options.renderer = this.options.renderer || new Renderer; 912 | this.renderer = this.options.renderer; 913 | this.renderer.options = this.options; 914 | } 915 | 916 | /** 917 | * Static Parse Method 918 | */ 919 | 920 | Parser.parse = function(src, options, renderer) { 921 | var parser = new Parser(options, renderer); 922 | return parser.parse(src); 923 | }; 924 | 925 | /** 926 | * Parse Loop 927 | */ 928 | 929 | Parser.prototype.parse = function(src) { 930 | this.inline = new InlineLexer(src.links, this.options, this.renderer); 931 | this.tokens = src.reverse(); 932 | 933 | var out = ''; 934 | while (this.next()) { 935 | out += this.tok(); 936 | } 937 | 938 | return out; 939 | }; 940 | 941 | /** 942 | * Next Token 943 | */ 944 | 945 | Parser.prototype.next = function() { 946 | return this.token = this.tokens.pop(); 947 | }; 948 | 949 | /** 950 | * Preview Next Token 951 | */ 952 | 953 | Parser.prototype.peek = function() { 954 | return this.tokens[this.tokens.length - 1] || 0; 955 | }; 956 | 957 | /** 958 | * Parse Text Tokens 959 | */ 960 | 961 | Parser.prototype.parseText = function() { 962 | var body = this.token.text; 963 | 964 | while (this.peek().type === 'text') { 965 | body += '\n' + this.next().text; 966 | } 967 | 968 | return this.inline.output(body); 969 | }; 970 | 971 | /** 972 | * Parse Current Token 973 | */ 974 | 975 | Parser.prototype.tok = function() { 976 | switch (this.token.type) { 977 | case 'space': { 978 | return ''; 979 | } 980 | case 'hr': { 981 | return this.renderer.hr(); 982 | } 983 | case 'heading': { 984 | return this.renderer.heading( 985 | this.inline.output(this.token.text), 986 | this.token.depth, 987 | this.token.text); 988 | } 989 | case 'code': { 990 | return this.renderer.code(this.token.text, 991 | this.token.lang, 992 | this.token.escaped); 993 | } 994 | case 'table': { 995 | var header = '' 996 | , body = '' 997 | , i 998 | , row 999 | , cell 1000 | , flags 1001 | , j; 1002 | 1003 | // header 1004 | cell = ''; 1005 | for (i = 0; i < this.token.header.length; i++) { 1006 | flags = { header: true, align: this.token.align[i] }; 1007 | cell += this.renderer.tablecell( 1008 | this.inline.output(this.token.header[i]), 1009 | { header: true, align: this.token.align[i] } 1010 | ); 1011 | } 1012 | header += this.renderer.tablerow(cell); 1013 | 1014 | for (i = 0; i < this.token.cells.length; i++) { 1015 | row = this.token.cells[i]; 1016 | 1017 | cell = ''; 1018 | for (j = 0; j < row.length; j++) { 1019 | cell += this.renderer.tablecell( 1020 | this.inline.output(row[j]), 1021 | { header: false, align: this.token.align[j] } 1022 | ); 1023 | } 1024 | 1025 | body += this.renderer.tablerow(cell); 1026 | } 1027 | return this.renderer.table(header, body); 1028 | } 1029 | case 'blockquote_start': { 1030 | var body = ''; 1031 | 1032 | while (this.next().type !== 'blockquote_end') { 1033 | body += this.tok(); 1034 | } 1035 | 1036 | return this.renderer.blockquote(body); 1037 | } 1038 | case 'list_start': { 1039 | var body = '' 1040 | , ordered = this.token.ordered; 1041 | 1042 | while (this.next().type !== 'list_end') { 1043 | body += this.tok(); 1044 | } 1045 | 1046 | return this.renderer.list(body, ordered); 1047 | } 1048 | case 'list_item_start': { 1049 | var body = ''; 1050 | 1051 | while (this.next().type !== 'list_item_end') { 1052 | body += this.token.type === 'text' 1053 | ? this.parseText() 1054 | : this.tok(); 1055 | } 1056 | 1057 | return this.renderer.listitem(body); 1058 | } 1059 | case 'loose_item_start': { 1060 | var body = ''; 1061 | 1062 | while (this.next().type !== 'list_item_end') { 1063 | body += this.tok(); 1064 | } 1065 | 1066 | return this.renderer.listitem(body); 1067 | } 1068 | case 'html': { 1069 | var html = !this.token.pre && !this.options.pedantic 1070 | ? this.inline.output(this.token.text) 1071 | : this.token.text; 1072 | return this.renderer.html(html); 1073 | } 1074 | case 'paragraph': { 1075 | return this.renderer.paragraph(this.inline.output(this.token.text)); 1076 | } 1077 | case 'text': { 1078 | return this.renderer.paragraph(this.parseText()); 1079 | } 1080 | } 1081 | }; 1082 | 1083 | /** 1084 | * Helpers 1085 | */ 1086 | 1087 | function escape(html, encode) { 1088 | return html 1089 | .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') 1090 | .replace(//g, '>') 1092 | .replace(/"/g, '"') 1093 | .replace(/'/g, '''); 1094 | } 1095 | 1096 | function unescape(html) { 1097 | return html.replace(/&([#\w]+);/g, function(_, n) { 1098 | n = n.toLowerCase(); 1099 | if (n === 'colon') return ':'; 1100 | if (n.charAt(0) === '#') { 1101 | return n.charAt(1) === 'x' 1102 | ? String.fromCharCode(parseInt(n.substring(2), 16)) 1103 | : String.fromCharCode(+n.substring(1)); 1104 | } 1105 | return ''; 1106 | }); 1107 | } 1108 | 1109 | function replace(regex, opt) { 1110 | regex = regex.source; 1111 | opt = opt || ''; 1112 | return function self(name, val) { 1113 | if (!name) return new RegExp(regex, opt); 1114 | val = val.source || val; 1115 | val = val.replace(/(^|[^\[])\^/g, '$1'); 1116 | regex = regex.replace(name, val); 1117 | return self; 1118 | }; 1119 | } 1120 | 1121 | function noop() {} 1122 | noop.exec = noop; 1123 | 1124 | function merge(obj) { 1125 | var i = 1 1126 | , target 1127 | , key; 1128 | 1129 | for (; i < arguments.length; i++) { 1130 | target = arguments[i]; 1131 | for (key in target) { 1132 | if (Object.prototype.hasOwnProperty.call(target, key)) { 1133 | obj[key] = target[key]; 1134 | } 1135 | } 1136 | } 1137 | 1138 | return obj; 1139 | } 1140 | 1141 | 1142 | /** 1143 | * Marked 1144 | */ 1145 | 1146 | function marked(src, opt, callback) { 1147 | if (callback || typeof opt === 'function') { 1148 | if (!callback) { 1149 | callback = opt; 1150 | opt = null; 1151 | } 1152 | 1153 | opt = merge({}, marked.defaults, opt || {}); 1154 | 1155 | var highlight = opt.highlight 1156 | , tokens 1157 | , pending 1158 | , i = 0; 1159 | 1160 | try { 1161 | tokens = Lexer.lex(src, opt) 1162 | } catch (e) { 1163 | return callback(e); 1164 | } 1165 | 1166 | pending = tokens.length; 1167 | 1168 | var done = function(err) { 1169 | if (err) { 1170 | opt.highlight = highlight; 1171 | return callback(err); 1172 | } 1173 | 1174 | var out; 1175 | 1176 | try { 1177 | out = Parser.parse(tokens, opt); 1178 | } catch (e) { 1179 | err = e; 1180 | } 1181 | 1182 | opt.highlight = highlight; 1183 | 1184 | return err 1185 | ? callback(err) 1186 | : callback(null, out); 1187 | }; 1188 | 1189 | if (!highlight || highlight.length < 3) { 1190 | return done(); 1191 | } 1192 | 1193 | delete opt.highlight; 1194 | 1195 | if (!pending) return done(); 1196 | 1197 | for (; i < tokens.length; i++) { 1198 | (function(token) { 1199 | if (token.type !== 'code') { 1200 | return --pending || done(); 1201 | } 1202 | return highlight(token.text, token.lang, function(err, code) { 1203 | if (err) return done(err); 1204 | if (code == null || code === token.text) { 1205 | return --pending || done(); 1206 | } 1207 | token.text = code; 1208 | token.escaped = true; 1209 | --pending || done(); 1210 | }); 1211 | })(tokens[i]); 1212 | } 1213 | 1214 | return; 1215 | } 1216 | try { 1217 | if (opt) opt = merge({}, marked.defaults, opt); 1218 | return Parser.parse(Lexer.lex(src, opt), opt); 1219 | } catch (e) { 1220 | e.message += '\nPlease report this to https://github.com/chjj/marked.'; 1221 | if ((opt || marked.defaults).silent) { 1222 | return '

    An error occured:

    '
    1223 |         + escape(e.message + '', true)
    1224 |         + '
    '; 1225 | } 1226 | throw e; 1227 | } 1228 | } 1229 | 1230 | /** 1231 | * Options 1232 | */ 1233 | 1234 | marked.options = 1235 | marked.setOptions = function(opt) { 1236 | merge(marked.defaults, opt); 1237 | return marked; 1238 | }; 1239 | 1240 | marked.defaults = { 1241 | gfm: true, 1242 | tables: true, 1243 | breaks: false, 1244 | pedantic: false, 1245 | sanitize: false, 1246 | sanitizer: null, 1247 | mangle: true, 1248 | smartLists: false, 1249 | silent: false, 1250 | highlight: null, 1251 | langPrefix: 'lang-', 1252 | smartypants: false, 1253 | headerPrefix: '', 1254 | renderer: new Renderer, 1255 | xhtml: false 1256 | }; 1257 | 1258 | /** 1259 | * Expose 1260 | */ 1261 | 1262 | marked.Parser = Parser; 1263 | marked.parser = Parser.parse; 1264 | 1265 | marked.Renderer = Renderer; 1266 | 1267 | marked.Lexer = Lexer; 1268 | marked.lexer = Lexer.lex; 1269 | 1270 | marked.InlineLexer = InlineLexer; 1271 | marked.inlineLexer = InlineLexer.output; 1272 | 1273 | marked.parse = marked; 1274 | 1275 | if (typeof module !== 'undefined' && typeof exports === 'object') { 1276 | module.exports = marked; 1277 | } else if (typeof define === 'function' && define.amd) { 1278 | define(function() { return marked; }); 1279 | } else { 1280 | this.marked = marked; 1281 | } 1282 | 1283 | }).call(function() { 1284 | return this || (typeof window !== 'undefined' ? window : global); 1285 | }()); 1286 | -------------------------------------------------------------------------------- /markdownview/src/main/assets/html/js/preview.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | marked.setOptions({ 3 | langPrefix: '' 4 | }); 5 | preview = function setMarkdown(md_text){ 6 | if(md_text == "") return false; 7 | md_text = md_text.replace(/\\n/g, "\n"); 8 | var md_html = marked(md_text); 9 | $('#preview').html(md_html); 10 | $('pre code').each(function(i, block) { 11 | hljs.highlightBlock(block); 12 | }); 13 | }; 14 | }); -------------------------------------------------------------------------------- /markdownview/src/main/assets/html/preview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 |
    19 | 20 | 21 | -------------------------------------------------------------------------------- /markdownview/src/main/java/com/mukesh/MarkdownView.java: -------------------------------------------------------------------------------- 1 | package com.mukesh; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.util.AttributeSet; 9 | import android.util.Base64; 10 | import android.util.Log; 11 | import android.webkit.WebSettings; 12 | import android.webkit.WebView; 13 | import android.webkit.WebViewClient; 14 | import java.io.BufferedInputStream; 15 | import java.io.BufferedReader; 16 | import java.io.File; 17 | import java.io.FileInputStream; 18 | import java.io.FileNotFoundException; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.io.InputStreamReader; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | 26 | public class MarkdownView extends WebView { 27 | 28 | private static final String TAG = MarkdownView.class.getSimpleName(); 29 | private static final String IMAGE_PATTERN = "!\\[(.*)\\]\\((.*)\\)"; 30 | 31 | private String mPreviewText; 32 | 33 | public MarkdownView(Context context) { 34 | this(context, null); 35 | } 36 | 37 | public MarkdownView(Context context, AttributeSet attrs) { 38 | this(context, attrs, 0); 39 | } 40 | 41 | public MarkdownView(Context context, AttributeSet attrs, int defStyleAttr) { 42 | super(context, attrs, defStyleAttr); 43 | initialize(); 44 | } 45 | 46 | @SuppressLint("SetJavaScriptEnabled") private void initialize() { 47 | setWebViewClient(new WebViewClient() { 48 | public void onPageFinished(WebView view, String url) { 49 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 50 | loadUrl(mPreviewText); 51 | } else { 52 | evaluateJavascript(mPreviewText, null); 53 | } 54 | } 55 | 56 | @Override 57 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 58 | if (url != null && url.startsWith("http")) { 59 | openWebPage(view.getContext(), url); 60 | return true; 61 | } else { 62 | return super.shouldOverrideUrlLoading(view, url); 63 | } 64 | } 65 | }); 66 | loadUrl("file:///android_asset/html/preview.html"); 67 | getSettings().setJavaScriptEnabled(true); 68 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 69 | getSettings().setAllowUniversalAccessFromFileURLs(true); 70 | } 71 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 72 | getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); 73 | } 74 | } 75 | 76 | public void loadMarkdownFromFile(File markdownFile) { 77 | String mdText = ""; 78 | try { 79 | FileInputStream fileInputStream = new FileInputStream(markdownFile); 80 | InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream); 81 | BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 82 | String readText; 83 | StringBuilder stringBuilder = new StringBuilder(); 84 | while ((readText = bufferedReader.readLine()) != null) { 85 | stringBuilder.append(readText); 86 | stringBuilder.append("\n"); 87 | } 88 | fileInputStream.close(); 89 | mdText = stringBuilder.toString(); 90 | } catch (FileNotFoundException e) { 91 | Log.e(TAG, "FileNotFoundException:" + e); 92 | } catch (IOException e) { 93 | Log.e(TAG, "IOException:" + e); 94 | } 95 | setMarkDownText(mdText); 96 | } 97 | 98 | public void loadMarkdownFromAssets(String assetsFilePath) { 99 | try { 100 | StringBuilder buf = new StringBuilder(); 101 | InputStream json = getContext().getAssets().open(assetsFilePath); 102 | BufferedReader in = new BufferedReader(new InputStreamReader(json, "UTF-8")); 103 | String str; 104 | while ((str = in.readLine()) != null) { 105 | buf.append(str).append("\n"); 106 | } 107 | in.close(); 108 | setMarkDownText(buf.toString()); 109 | } catch (IOException e) { 110 | e.printStackTrace(); 111 | } 112 | } 113 | 114 | public void setMarkDownText(String markdownText) { 115 | String bs64MdText = imgToBase64(markdownText); 116 | String escMdText = escapeForText(bs64MdText); 117 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 118 | mPreviewText = String.format("javascript:preview('%s')", escMdText); 119 | } else { 120 | mPreviewText = String.format("preview('%s')", escMdText); 121 | } 122 | initialize(); 123 | } 124 | 125 | private String escapeForText(String mdText) { 126 | String escText = mdText.replace("\n", "\\\\n"); 127 | escText = escText.replace("'", "\\\'"); 128 | escText = escText.replace("\r", ""); 129 | return escText; 130 | } 131 | 132 | private String imgToBase64(String mdText) { 133 | Pattern ptn = Pattern.compile(IMAGE_PATTERN); 134 | Matcher matcher = ptn.matcher(mdText); 135 | if (!matcher.find()) { 136 | return mdText; 137 | } 138 | String imgPath = matcher.group(2); 139 | if (isUrlPrefix(imgPath) || !isPathExCheck(imgPath)) { 140 | return mdText; 141 | } 142 | String baseType = imgEx2BaseType(imgPath); 143 | if (baseType.equals("")) { 144 | return mdText; 145 | } 146 | File file = new File(imgPath); 147 | byte[] bytes = new byte[(int) file.length()]; 148 | try { 149 | BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file)); 150 | buf.read(bytes, 0, bytes.length); 151 | buf.close(); 152 | } catch (FileNotFoundException e) { 153 | Log.e(TAG, "FileNotFoundException:" + e); 154 | } catch (IOException e) { 155 | Log.e(TAG, "IOException:" + e); 156 | } 157 | String base64Img = baseType + Base64.encodeToString(bytes, Base64.NO_WRAP); 158 | return mdText.replace(imgPath, base64Img); 159 | } 160 | 161 | private boolean isUrlPrefix(String text) { 162 | return text.startsWith("http://") || text.startsWith("https://"); 163 | } 164 | 165 | private boolean isPathExCheck(String text) { 166 | return text.endsWith(".png") 167 | || text.endsWith(".jpg") 168 | || text.endsWith(".jpeg") 169 | || text.endsWith(".gif"); 170 | } 171 | 172 | private String imgEx2BaseType(String text) { 173 | if (text.endsWith(".png")) { 174 | return "data:image/png;base64,"; 175 | } else if (text.endsWith(".jpg") || text.endsWith(".jpeg")) { 176 | return "data:image/jpg;base64,"; 177 | } else if (text.endsWith(".gif")) { 178 | return "data:image/gif;base64,"; 179 | } else { 180 | return ""; 181 | } 182 | } 183 | 184 | // 185 | public void openWebPage(Context context, String url) { 186 | Uri webPage = Uri.parse(url); 187 | openWebPage(context, webPage); 188 | } 189 | 190 | public void openWebPage(Context context, Uri uri) { 191 | Intent intent = new Intent(Intent.ACTION_VIEW, uri); 192 | 193 | if (intent.resolveActivity(context.getPackageManager()) != null) { 194 | context.startActivity(intent); 195 | } 196 | } 197 | 198 | } 199 | --------------------------------------------------------------------------------