├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── refactoring.md ├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── icons │ │ ├── arrow.svg │ │ └── compass.svg │ └── themes │ │ ├── classic │ │ ├── patterns │ │ │ └── kraft.png │ │ ├── shields │ │ │ ├── motorway │ │ │ │ ├── 1.svg │ │ │ │ ├── 10.svg │ │ │ │ ├── 2.svg │ │ │ │ ├── 3.svg │ │ │ │ ├── 4.svg │ │ │ │ ├── 5.svg │ │ │ │ ├── 6.svg │ │ │ │ ├── 8.svg │ │ │ │ └── branches │ │ │ │ │ └── 3A.svg │ │ │ ├── primary │ │ │ │ ├── 1.svg │ │ │ │ ├── 10.svg │ │ │ │ ├── 11.svg │ │ │ │ ├── 12.svg │ │ │ │ ├── 13.svg │ │ │ │ ├── 14.svg │ │ │ │ ├── 15.svg │ │ │ │ ├── 16.svg │ │ │ │ ├── 17.svg │ │ │ │ ├── 18.svg │ │ │ │ ├── 19.svg │ │ │ │ ├── 2.svg │ │ │ │ ├── 20.svg │ │ │ │ ├── 21.svg │ │ │ │ ├── 22.svg │ │ │ │ ├── 23.svg │ │ │ │ ├── 24.svg │ │ │ │ ├── 25.svg │ │ │ │ ├── 26.svg │ │ │ │ ├── 27.svg │ │ │ │ ├── 28.svg │ │ │ │ ├── 29.svg │ │ │ │ ├── 3.svg │ │ │ │ ├── 30.svg │ │ │ │ ├── 31.svg │ │ │ │ ├── 35.svg │ │ │ │ ├── 37.svg │ │ │ │ ├── 39.svg │ │ │ │ ├── 4.svg │ │ │ │ ├── 5.svg │ │ │ │ ├── 6.svg │ │ │ │ ├── 63.svg │ │ │ │ ├── 7.svg │ │ │ │ ├── 8.svg │ │ │ │ ├── 9.svg │ │ │ │ └── branches │ │ │ │ │ ├── 10B.svg │ │ │ │ │ ├── 11A.svg │ │ │ │ │ ├── 11B.svg │ │ │ │ │ ├── 11C.svg │ │ │ │ │ ├── 13A.svg │ │ │ │ │ ├── 14A.svg │ │ │ │ │ ├── 14B.svg │ │ │ │ │ ├── 14C.svg │ │ │ │ │ ├── 14D.svg │ │ │ │ │ ├── 15A.svg │ │ │ │ │ ├── 17A.svg │ │ │ │ │ ├── 17B.svg │ │ │ │ │ ├── 19A.svg │ │ │ │ │ ├── 1A.svg │ │ │ │ │ ├── 1B.svg │ │ │ │ │ ├── 1C.svg │ │ │ │ │ ├── 1D.svg │ │ │ │ │ ├── 1E.svg │ │ │ │ │ ├── 1F.svg │ │ │ │ │ ├── 20A.svg │ │ │ │ │ ├── 20B.svg │ │ │ │ │ ├── 21A.svg │ │ │ │ │ ├── 27A.svg │ │ │ │ │ ├── 2A.svg │ │ │ │ │ ├── 2B.svg │ │ │ │ │ ├── 2C.svg │ │ │ │ │ ├── 2D.svg │ │ │ │ │ ├── 2E.svg │ │ │ │ │ ├── 2F.svg │ │ │ │ │ ├── 2G.svg │ │ │ │ │ ├── 3A.svg │ │ │ │ │ ├── 3B.svg │ │ │ │ │ ├── 3C.svg │ │ │ │ │ ├── 5A.svg │ │ │ │ │ ├── 5B.svg │ │ │ │ │ ├── 63A.svg │ │ │ │ │ ├── 68A.svg │ │ │ │ │ ├── 74A.svg │ │ │ │ │ ├── 7A.svg │ │ │ │ │ ├── 7B.svg │ │ │ │ │ ├── 7C.svg │ │ │ │ │ ├── 7D.svg │ │ │ │ │ ├── 8A.svg │ │ │ │ │ ├── 9A.svg │ │ │ │ │ ├── 9B.svg │ │ │ │ │ └── 9C.svg │ │ │ └── trunk │ │ │ │ ├── 61.svg │ │ │ │ ├── 62.svg │ │ │ │ ├── 64.svg │ │ │ │ ├── 65.svg │ │ │ │ ├── 66.svg │ │ │ │ ├── 68.svg │ │ │ │ ├── 72.svg │ │ │ │ ├── 74.svg │ │ │ │ ├── 76.svg │ │ │ │ ├── 78.svg │ │ │ │ ├── 82.svg │ │ │ │ ├── 84.svg │ │ │ │ ├── 86.svg │ │ │ │ ├── 88.svg │ │ │ │ └── branches │ │ │ │ ├── 61A.svg │ │ │ │ └── 61B.svg │ │ ├── symbols │ │ │ ├── hsr.svg │ │ │ ├── metro_ks.svg │ │ │ ├── metro_tp.svg │ │ │ ├── metro_ty.svg │ │ │ └── tra.svg │ │ └── theme.xml │ │ └── default │ │ ├── shields │ │ ├── motorway │ │ │ ├── 1.svg │ │ │ ├── 10.svg │ │ │ ├── 2.svg │ │ │ ├── 3.svg │ │ │ ├── 4.svg │ │ │ ├── 5.svg │ │ │ ├── 6.svg │ │ │ ├── 8.svg │ │ │ └── branches │ │ │ │ ├── 2A.svg │ │ │ │ └── 3A.svg │ │ ├── primary │ │ │ ├── 1.svg │ │ │ ├── 10.svg │ │ │ ├── 11.svg │ │ │ ├── 12.svg │ │ │ ├── 13.svg │ │ │ ├── 14.svg │ │ │ ├── 15.svg │ │ │ ├── 16.svg │ │ │ ├── 17.svg │ │ │ ├── 18.svg │ │ │ ├── 19.svg │ │ │ ├── 2.svg │ │ │ ├── 20.svg │ │ │ ├── 21.svg │ │ │ ├── 22.svg │ │ │ ├── 23.svg │ │ │ ├── 24.svg │ │ │ ├── 25.svg │ │ │ ├── 26.svg │ │ │ ├── 27.svg │ │ │ ├── 28.svg │ │ │ ├── 29.svg │ │ │ ├── 3.svg │ │ │ ├── 30.svg │ │ │ ├── 31.svg │ │ │ ├── 35.svg │ │ │ ├── 37.svg │ │ │ ├── 39.svg │ │ │ ├── 4.svg │ │ │ ├── 5.svg │ │ │ ├── 6.svg │ │ │ ├── 63.svg │ │ │ ├── 7.svg │ │ │ ├── 8.svg │ │ │ ├── 9.svg │ │ │ └── branches │ │ │ │ ├── 10B.svg │ │ │ │ ├── 11A.svg │ │ │ │ ├── 11B.svg │ │ │ │ ├── 11C.svg │ │ │ │ ├── 13A.svg │ │ │ │ ├── 14A.svg │ │ │ │ ├── 14B.svg │ │ │ │ ├── 14C.svg │ │ │ │ ├── 14D.svg │ │ │ │ ├── 15A.svg │ │ │ │ ├── 17A.svg │ │ │ │ ├── 17B.svg │ │ │ │ ├── 19A.svg │ │ │ │ ├── 1A.svg │ │ │ │ ├── 1B.svg │ │ │ │ ├── 1C.svg │ │ │ │ ├── 1D.svg │ │ │ │ ├── 1E.svg │ │ │ │ ├── 1F.svg │ │ │ │ ├── 20A.svg │ │ │ │ ├── 20B.svg │ │ │ │ ├── 21A.svg │ │ │ │ ├── 27A.svg │ │ │ │ ├── 2A.svg │ │ │ │ ├── 2B.svg │ │ │ │ ├── 2C.svg │ │ │ │ ├── 2D.svg │ │ │ │ ├── 2E.svg │ │ │ │ ├── 2F.svg │ │ │ │ ├── 2G.svg │ │ │ │ ├── 3A.svg │ │ │ │ ├── 3B.svg │ │ │ │ ├── 3C.svg │ │ │ │ ├── 5A.svg │ │ │ │ ├── 5B.svg │ │ │ │ ├── 63A.svg │ │ │ │ ├── 68A.svg │ │ │ │ ├── 74A.svg │ │ │ │ ├── 7A.svg │ │ │ │ ├── 7B.svg │ │ │ │ ├── 7C.svg │ │ │ │ ├── 7D.svg │ │ │ │ ├── 8A.svg │ │ │ │ ├── 9A.svg │ │ │ │ ├── 9B.svg │ │ │ │ └── 9C.svg │ │ └── trunk │ │ │ ├── 61.svg │ │ │ ├── 62.svg │ │ │ ├── 64.svg │ │ │ ├── 65.svg │ │ │ ├── 66.svg │ │ │ ├── 68.svg │ │ │ ├── 72.svg │ │ │ ├── 74.svg │ │ │ ├── 76.svg │ │ │ ├── 78.svg │ │ │ ├── 82.svg │ │ │ ├── 84.svg │ │ │ ├── 86.svg │ │ │ ├── 88.svg │ │ │ └── branches │ │ │ ├── 61A.svg │ │ │ └── 61B.svg │ │ ├── symbols │ │ ├── hsr.svg │ │ ├── metro_ks.svg │ │ ├── metro_tp.svg │ │ ├── metro_ty.svg │ │ └── tra.svg │ │ └── theme.xml │ ├── java │ └── tacoball │ │ └── com │ │ └── geomancer │ │ ├── MainActivity.java │ │ ├── MainUtils.java │ │ ├── MapViewFragment.java │ │ ├── PermissionUtils.java │ │ ├── SettingsFragment.java │ │ ├── SimpleFragment.java │ │ ├── UpdateToolFragment.java │ │ ├── checkupdate │ │ ├── AutoUpdateAdapter.java │ │ ├── AutoUpdateManager.java │ │ └── CheckUpdateAdapter.java │ │ ├── map │ │ ├── FixedFpsSurfaceView.java │ │ ├── Pin.java │ │ ├── PinGroup.java │ │ ├── SurfaceMapView.java │ │ └── TaiwanMapView.java │ │ └── view │ │ ├── CircleButton.java │ │ └── LinkView.java │ └── res │ ├── drawable │ ├── contrib96.png │ ├── debug_border.xml │ ├── g0v_dark.png │ ├── geomancer_info_border.xml │ ├── license96.png │ ├── measure_result_background.xml │ ├── more96.png │ └── settings96.png │ ├── layout │ ├── activity_main.xml │ ├── fragment_contributors.xml │ ├── fragment_license.xml │ ├── fragment_main.xml │ └── fragment_updater.xml │ ├── mipmap-xhdpi │ └── geomancer.png │ ├── values-w820dp │ └── dimens.xml │ ├── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── settings.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 蟲 6 | assignees: virus-warnning 7 | 8 | --- 9 | 10 | **問題描述** 11 | A clear and concise description of what the bug is. 12 | 13 | **重現步驟** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **預期行為** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **線索** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **環境資訊** 27 | - 機型: [e.g. ZenFone 3] 28 | - Android 版本: [e.g. 6.0] 29 | - App 版本: [e.g. 0.1.6] 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 新功能 6 | assignees: virus-warnning 7 | 8 | --- 9 | 10 | **願望** 11 | A clear and concise description of what you want to happen. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/refactoring.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Refactoring 3 | about: 重構應用程式 4 | title: '' 5 | labels: 重構 6 | assignees: virus-warnning 7 | 8 | --- 9 | 10 | **重構原因** 11 | 12 | **套件異動** 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | 45 | # Keystore files 46 | # Uncomment the following line if you do not want to check your keystore files in. 47 | #*.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | 60 | # fastlane 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | fastlane/readme.md 66 | 67 | # OSX Thumbnails 68 | .DS_Store 69 | 70 | # Bundle 71 | app/release/ 72 | 73 | # Crashlytics 74 | google-services.json 75 | 76 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 鄉民風水師 2 | [Google Play 下載](https://play.google.com/store/apps/details?id=tacoball.com.geomancer) 3 | 4 | 這是一個利用資料視覺化的方式設計的人文地理工具,目前可查詢: 5 | * 全台灣凶宅地點 6 | * 台北市勞工局違反勞基法事業地點 7 | 8 | ### 閃退紀錄 9 | [Crashlytics 統計](https://fabric.io/taco-studio/android/apps/tacoball.com.geomancer/issues) 10 | 11 | ### 更新檔 12 | 包含離線地圖、凶宅資料庫、違反勞基法資料庫: 13 | [OSSPlanet](http://mirror.ossplanet.net/geomancer/0.1.0) 14 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.google.gms.google-services' 3 | 4 | android { 5 | // 配合 appcompat-v7 6 | compileSdkVersion 28 7 | buildToolsVersion '28.0.3' 8 | defaultConfig { 9 | // 名稱與版本 10 | applicationId "tacoball.com.geomancer" 11 | versionCode 106 12 | versionName "0.1.6" 13 | 14 | // Android 6.0+ 15 | minSdkVersion 23 16 | targetSdkVersion 28 17 | } 18 | } 19 | 20 | project.ext { 21 | supportVersion = "28.0.0" 22 | mapsforgeVersion = "0.8.0" 23 | } 24 | 25 | dependencies { 26 | // mapsforge 27 | implementation "org.mapsforge:mapsforge-map-android:${project.mapsforgeVersion}" 28 | implementation "org.mapsforge:mapsforge-themes:${project.mapsforgeVersion}" 29 | /* 30 | compile("org.mapsforge:mapsforge-map-android-extras:${project.mapsforgeVersion}") { 31 | transitive = false 32 | } 33 | */ 34 | 35 | // Android support library 36 | // See: https://developer.android.com/topic/libraries/support-library/packages.html 37 | implementation "com.android.support:support-v4:${project.supportVersion}" 38 | implementation "com.android.support:appcompat-v7:${project.supportVersion}" 39 | implementation "com.android.support:preference-v7:${project.supportVersion}" 40 | 41 | // Others 42 | implementation 'commons-io:commons-io:2.5' 43 | implementation 'com.google.code.gson:gson:2.8.2' 44 | 45 | // Crashlytics 46 | implementation 'com.google.firebase:firebase-core:16.0.6' 47 | 48 | // Unit test 49 | testImplementation 'junit:junit:4.12' 50 | } 51 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By bright, the flags in this file are appended to flags specified 3 | # in /Users/raymond/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 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/assets/icons/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/assets/icons/compass.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 西 34 | 東北 35 | 東南 36 | 西南 37 | 西北 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/patterns/kraft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OsmHackTW/GeomancerAndroid/f134ee8751a0abe85325c3fd9707cd0f84f7a9c7/app/src/main/assets/themes/classic/patterns/kraft.png -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/motorway/1.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 13 | 14 | 1 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/motorway/10.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 13 | 14 | 10 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/motorway/2.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 13 | 14 | 2 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/motorway/3.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 13 | 14 | 3 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/motorway/4.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 13 | 14 | 4 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/motorway/5.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 13 | 14 | 5 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/motorway/6.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 13 | 14 | 6 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/motorway/8.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 13 | 14 | 8 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/motorway/branches/3A.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 13 | 14 | 3甲 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/primary/1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/primary/35.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 33 | 50 | 68 | 69 | 79 | 86 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/primary/7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/primary/branches/20B.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/shields/primary/branches/7D.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/symbols/hsr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/symbols/metro_ks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 18 | 21 | 22 | 23 | 25 | 26 | 28 | image/svg+xml 29 | 31 | 32 | 33 | 34 | 35 | 38 | 41 | 44 | 48 | 49 | 52 | 56 | 57 | 60 | 64 | 65 | 68 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/symbols/metro_tp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 20 | 23 | 24 | 25 | 29 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/symbols/metro_ty.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 41 | 43 | Created by potrace 1.13, written by Peter Selinger 2001-2015, and reduced by Raymond Wu. 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 54 | 55 | 58 | 62 | 63 | 69 | 70 | 74 | 75 | 79 | 83 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/classic/symbols/tra.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/default/shields/primary/1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/default/shields/primary/7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/default/shields/primary/branches/20B.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/default/shields/primary/branches/7D.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/default/symbols/hsr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 20 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 63 | 67 | 71 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/default/symbols/metro_ks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 18 | 21 | 22 | 23 | 25 | 26 | 28 | image/svg+xml 29 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 42 | 45 | 49 | 50 | 53 | 57 | 58 | 61 | 65 | 66 | 69 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/default/symbols/metro_tp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 20 | 21 | 24 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/default/symbols/metro_ty.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 41 | 43 | Created by potrace 1.13, written by Peter Selinger 2001-2015, and reduced by Raymond Wu. 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 54 | 55 | 58 | 62 | 63 | 69 | 70 | 74 | 75 | 79 | 83 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /app/src/main/assets/themes/default/symbols/tra.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/MainActivity.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer; 2 | 3 | 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.os.Bundle; 9 | import android.support.annotation.NonNull; 10 | import android.support.annotation.Nullable; 11 | import android.support.v4.app.Fragment; 12 | import android.support.v4.app.FragmentManager; 13 | import android.support.v7.app.AppCompatActivity; 14 | import android.util.Log; 15 | import android.widget.Toast; 16 | 17 | import org.mapsforge.map.android.graphics.AndroidGraphicFactory; 18 | 19 | import java.util.Locale; 20 | 21 | import tacoball.com.geomancer.map.TaiwanMapView; 22 | 23 | /** 24 | * 前端程式進入點 25 | */ 26 | public class MainActivity extends AppCompatActivity { 27 | 28 | private static final String TAG = "MainActivity"; 29 | 30 | private static final boolean SIMULATE_OLD_MTIME = false; 31 | 32 | private MapViewFragment mMapFragment = new MapViewFragment(); 33 | private Fragment mUpdateFragment = new UpdateToolFragment(); 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | 39 | // 配置 Android 繪圖資源,必須在 inflate 之前完成 40 | AndroidGraphicFactory.createInstance(getApplication()); 41 | setContentView(R.layout.activity_main); 42 | 43 | // 配置廣播接收器 44 | this.registerReceiver(receiver, MainUtils.buildFragmentSwitchIntentFilter()); 45 | 46 | // 清理儲存空間 47 | MainUtils.cleanStorage(this); 48 | 49 | // 檢查是否殘留除錯設定,釋出前使用 50 | checkDebugParameters(); 51 | 52 | // 先進入更新介面 53 | changeFragment(mUpdateFragment); 54 | } 55 | 56 | @Override 57 | protected void onDestroy() { 58 | super.onDestroy(); 59 | unregisterReceiver(receiver); 60 | } 61 | 62 | @Override 63 | public void onBackPressed() { 64 | // 從設定頁返回主畫面要重新載入設定值 65 | // TODO: 目前的寫法會導致貢獻者頁面和授權頁面返回也重新載入 66 | FragmentManager fm = getSupportFragmentManager(); 67 | if (fm.getBackStackEntryCount() > 0) { 68 | mMapFragment.reloadSettings(); 69 | } 70 | super.onBackPressed(); 71 | } 72 | 73 | @Override 74 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 75 | // 位置權限被允許 76 | if (requestCode == PermissionUtils.RC_GOTO_POSITION) { 77 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 78 | // TODO: 繼續定位動作 79 | } 80 | } 81 | } 82 | 83 | // 地毯式檢查用到的除錯參數 84 | private void checkDebugParameters() { 85 | int cnt = 0; 86 | 87 | if (MainUtils.MIRROR_NUM != 0) { 88 | Log.w(TAG, getString(R.string.log_use_debugging_mirror)); 89 | cnt++; 90 | } 91 | 92 | if (TaiwanMapView.SEE_DEBUGGING_POINT) { 93 | Log.w(TAG, getString(R.string.log_see_debugging_point)); 94 | cnt++; 95 | } 96 | 97 | if (SIMULATE_OLD_MTIME) { 98 | Log.w(TAG, getString(R.string.log_simulate_old_mtime)); 99 | cnt++; 100 | } 101 | 102 | if (cnt > 0) { 103 | String pat = getString(R.string.pattern_enable_debugging); 104 | String msg = String.format(Locale.getDefault(), pat, cnt); 105 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); 106 | } 107 | } 108 | 109 | // 切換 Fragment 110 | private void changeFragment(Fragment nextFrag) { 111 | // Issue #65 處理方式 112 | if (isFinishing() || isDestroyed()) { 113 | Log.w(TAG, "應用程式即將關閉,取消畫面切換"); 114 | return; 115 | } 116 | 117 | FragmentManager fm = getSupportFragmentManager(); 118 | if (nextFrag == mMapFragment || nextFrag == mUpdateFragment) { 119 | // 放在堆疊底層 120 | fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); 121 | fm.beginTransaction() 122 | .replace(R.id.frag_container, nextFrag) 123 | .commit(); 124 | } else { 125 | // 疊在 mMapFragment 上面 126 | fm.beginTransaction() 127 | .add(R.id.frag_container, nextFrag) 128 | .attach(nextFrag) 129 | .addToBackStack("detail") 130 | .commit(); 131 | } 132 | } 133 | 134 | // 廣播接收器,處理使用者更新要求用 135 | private BroadcastReceiver receiver = new BroadcastReceiver() { 136 | 137 | @Override 138 | public void onReceive(Context context, Intent intent) { 139 | String msg = String.format(Locale.getDefault(), "Got broadcast intent action=%s", intent.getAction()); 140 | Log.d(TAG, msg); 141 | 142 | String action = intent.getAction(); 143 | if (action == null) { 144 | return; 145 | } 146 | 147 | if (action.equals("MAIN")) { 148 | changeFragment(mMapFragment); 149 | } 150 | 151 | if (action.equals("UPDATE")) { 152 | changeFragment(mUpdateFragment); 153 | } 154 | 155 | if (action.equals("SETTINGS")) { 156 | Fragment f = new SettingsFragment(); 157 | changeFragment(f); 158 | } 159 | 160 | if (action.equals("CONTRIBUTORS")) { 161 | Fragment f = new SimpleFragment(); 162 | Bundle args = new Bundle(); 163 | args.putInt("LAYOUT_ID", R.layout.fragment_contributors); 164 | f.setArguments(args); 165 | changeFragment(f); 166 | } 167 | 168 | if (action.equals("LICENSE")) { 169 | Fragment f = new SimpleFragment(); 170 | Bundle args = new Bundle(); 171 | args.putInt("LAYOUT_ID", R.layout.fragment_license); 172 | f.setArguments(args); 173 | changeFragment(f); 174 | } 175 | } 176 | 177 | }; 178 | 179 | } 180 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/MainUtils.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.IntentFilter; 6 | import android.content.SharedPreferences; 7 | import android.database.sqlite.SQLiteDatabase; 8 | import android.net.ConnectivityManager; 9 | import android.net.NetworkInfo; 10 | import android.preference.PreferenceManager; 11 | import android.util.Log; 12 | 13 | import org.apache.commons.io.FileUtils; 14 | import org.mapsforge.map.datastore.MapDataStore; 15 | import org.mapsforge.map.reader.MapFile; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.util.Locale; 20 | 21 | /** 22 | * 共用程式 23 | */ 24 | public class MainUtils { 25 | 26 | // 除錯標籤 27 | private static final String TAG = "MainUtils"; 28 | 29 | // 各偏好設定 KEY 值 30 | private static final String PREFKEY_UPDATE_BY_MOBILE = "UPDATE_FROM_MOBILE"; // 允許行動網路更新 31 | 32 | // 地圖檔名 33 | public static final String MAP_NAME = "taiwan-taco.map"; 34 | 35 | // 資料庫檔名 36 | public static final String UNLUCKY_HOUSE = "unluckyhouse.sqlite"; 37 | 38 | // 前端狀態事件的分類名稱 39 | private static final String INTENT_CATEGORY = "tacoball.com.geomancer.FrontEndState"; 40 | 41 | // 更新伺服器 42 | public static final int MIRROR_NUM = 0; 43 | private static final String[] MIRROR_SITES = { 44 | "tacosync.com" 45 | }; 46 | 47 | /** 48 | * 取得更新鏡像站的網址 49 | * 50 | * @return 網址 51 | */ 52 | public static String getUpdateSource() { 53 | return String.format(Locale.getDefault(), "http://%s/geomancer/0.1.0", MIRROR_SITES[MIRROR_NUM]); 54 | } 55 | 56 | /** 57 | * 取得 DB 路徑 58 | * 59 | * @param context Activity 或 Service 60 | * @return DB 路徑 61 | * @throws IOException ... 62 | */ 63 | public static File getDbPath(Context context) throws IOException { 64 | File[] dirs = context.getExternalFilesDirs("db"); 65 | for (int i=dirs.length-1;i>=0;i--) { 66 | if (dirs[i]!=null) return dirs[i]; 67 | } 68 | throw new IOException(""); 69 | } 70 | 71 | /** 72 | * 取得紀錄檔路徑 73 | * 74 | * @param context Activity 或 Service 75 | * @return 紀錄檔路徑 76 | * @throws IOException ... 77 | */ 78 | public static File getLogPath(Context context) throws IOException { 79 | File[] dirs = context.getExternalFilesDirs("log"); 80 | for (int i=dirs.length-1;i>=0;i--) { 81 | if (dirs[i]!=null) return dirs[i]; 82 | } 83 | throw new IOException(""); 84 | } 85 | 86 | /** 87 | * 取得地圖路徑 88 | * 89 | * @param context Activity 或 Service 90 | * @return 地圖路徑 91 | * @throws IOException 92 | */ 93 | public static File getMapPath(Context context) throws IOException { 94 | File[] dirs = context.getExternalFilesDirs("map"); 95 | for (int i=dirs.length-1;i>=0;i--) { 96 | if (dirs[i]!=null) return dirs[i]; 97 | } 98 | throw new IOException(""); 99 | } 100 | 101 | /** 102 | * 開啟地圖 103 | * 104 | * @param context Activity 或 Service 105 | * @return 圖資存取介面 106 | * @throws IOException 107 | */ 108 | public static MapDataStore openMapData(Context context) throws IOException { 109 | File path = new File(getMapPath(context), MAP_NAME); 110 | return new MapFile(path); 111 | } 112 | 113 | /** 114 | * 唯讀模式開啟 SQLite 資料庫 115 | * 116 | * @param context Activity 或 Service 117 | * @param filename 資料庫檔名 118 | * @return 資料庫連線 119 | * @throws IOException 120 | */ 121 | public static SQLiteDatabase openReadOnlyDB(Context context, String filename) throws IOException { 122 | String path = getDbPath(context).getAbsolutePath() + "/" + filename; 123 | return SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY); 124 | } 125 | 126 | /** 127 | * 檢查是否可以傳輸資料 128 | * 129 | * @param context Activity 或 Service 130 | * @return 是否可以傳輸 131 | */ 132 | public static boolean isNetworkConnected(Context context) { 133 | ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); 134 | NetworkInfo ni = cm.getActiveNetworkInfo(); 135 | 136 | if (ni != null) { 137 | // TODO: 檢查是否限用 WiFi 138 | return ni.isConnected(); 139 | } else { 140 | return false; 141 | } 142 | } 143 | 144 | /** 145 | * 清理儲存空間 146 | */ 147 | public static void cleanStorage(Context context) { 148 | File[] dirs = context.getExternalFilesDirs("database"); 149 | for (int i=dirs.length-1;i>=0;i--) { 150 | if (dirs[i]!=null) { 151 | try { 152 | FileUtils.deleteDirectory(dirs[i]); 153 | } catch(IOException ex) { 154 | Log.e(TAG, getReason(ex)); 155 | } 156 | } 157 | } 158 | 159 | // TODO: 移除故障的殘留檔案 160 | } 161 | 162 | /** 163 | * 例外訊息改進程式,避免捕捉例外時還發生例外 164 | */ 165 | public static String getReason(final Exception ex) { 166 | String msg = ex.getMessage(); 167 | 168 | if (msg==null) { 169 | StackTraceElement ste = ex.getStackTrace()[0]; 170 | msg = String.format( 171 | Locale.getDefault(), 172 | "%s with null message (%s.%s() Line:%d)", 173 | ex.getClass().getSimpleName(), 174 | ste.getClassName(), 175 | ste.getMethodName(), 176 | ste.getLineNumber() 177 | ); 178 | } 179 | 180 | return msg; 181 | } 182 | 183 | /** 184 | * 是否允許透過行動網路更新 185 | */ 186 | public static boolean canUpdateByMobile(Context context) { 187 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 188 | return prefs.getBoolean(PREFKEY_UPDATE_BY_MOBILE, true); 189 | } 190 | 191 | /** 192 | * 產生 Fragment 切換事件 193 | * 194 | * @param action 動作名稱 195 | * @return Fragment 切換事件 196 | */ 197 | public static Intent buildFragmentSwitchIntent(String action) { 198 | Intent i = new Intent(); 199 | i.addCategory(INTENT_CATEGORY); 200 | i.setAction(action); 201 | return i; 202 | } 203 | 204 | /** 205 | * 產生 Fragment 切換事件過濾器 206 | * 207 | * @return Fragment 切換事件過濾器 208 | */ 209 | public static IntentFilter buildFragmentSwitchIntentFilter() { 210 | IntentFilter filter = new IntentFilter(); 211 | filter.addCategory(INTENT_CATEGORY); 212 | filter.addAction("MAIN"); 213 | filter.addAction("UPDATE"); 214 | filter.addAction("SETTINGS"); 215 | filter.addAction("CONTRIBUTORS"); 216 | filter.addAction("LICENSE"); 217 | return filter; 218 | } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/PermissionUtils.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer; 2 | 3 | import android.Manifest; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.provider.Settings; 8 | import android.support.annotation.NonNull; 9 | import android.support.v7.app.AlertDialog; 10 | import android.support.v7.app.AppCompatActivity; 11 | 12 | public class PermissionUtils { 13 | 14 | public static int RC_GOTO_POSITION = 32769; 15 | 16 | /** 17 | * 請求位置權限 18 | * 19 | * @param activity 應用程式 20 | * @param requestCode 請求代碼,完成請求後接續處理用 21 | */ 22 | public static void requestLocationPermission(@NonNull final AppCompatActivity activity, final int requestCode) { 23 | // 沒有位置資訊權限 24 | if (activity.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) { 25 | // 權限已拒絕狀態,提示用戶開啟 26 | DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { 27 | @Override 28 | public void onClick(DialogInterface dialog, int which) { 29 | Intent intent = new Intent(); 30 | intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 31 | Uri uri = Uri.fromParts("package", activity.getPackageName(), null); 32 | intent.setData(uri); 33 | activity.startActivityForResult(intent, requestCode); 34 | } 35 | }; 36 | 37 | new AlertDialog.Builder(activity) 38 | .setMessage(R.string.prompt_loc_permission_rejected) 39 | .setPositiveButton(R.string.prompt_loc_permission_enable, listener) 40 | .setNegativeButton(R.string.prompt_loc_permission_cancel, null) 41 | .create() 42 | .show(); 43 | } else { 44 | // 權限未設定狀態,請求權限 45 | activity.requestPermissions(new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, requestCode); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/SettingsFragment.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.preference.PreferenceFragmentCompat; 5 | 6 | /** 7 | * 設定畫面 8 | */ 9 | public class SettingsFragment extends PreferenceFragmentCompat { 10 | 11 | // POI 項目 (可擴充) 12 | // private final String[] POI_KEYS = {"search_unlucky_house"}; 13 | 14 | @Override 15 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 16 | setPreferencesFromResource(R.xml.settings, rootKey); 17 | 18 | // 驗證設定 19 | /* 20 | PreferenceScreen ps = getPreferenceScreen(); 21 | for (String k : POI_KEYS) { 22 | ps.findPreference(k).setOnPreferenceChangeListener(mPoiValidator); 23 | } 24 | */ 25 | } 26 | 27 | // POI 至少要選一項的檢查程式 28 | /* 29 | Preference.OnPreferenceChangeListener mPoiValidator = new Preference.OnPreferenceChangeListener() { 30 | 31 | @Override 32 | public boolean onPreferenceChange(Preference preference, Object newValue) { 33 | PreferenceScreen ps = getPreferenceScreen(); 34 | Boolean newChecked = (Boolean)newValue; 35 | 36 | if (newChecked.booleanValue() == false) { 37 | int checkedCount = 0; 38 | 39 | for (String k : POI_KEYS) { 40 | if (preference.getKey().equals(k)) continue; 41 | CheckBoxPreference cbp = (CheckBoxPreference)ps.findPreference(k); 42 | if (cbp.isChecked()) { 43 | checkedCount++; 44 | } 45 | } 46 | 47 | if (checkedCount == 0) { 48 | Toast.makeText(getActivity(), R.string.prompt_at_least_one_poi, Toast.LENGTH_SHORT).show(); 49 | return false; 50 | } 51 | } 52 | 53 | return true; 54 | } 55 | 56 | }; 57 | */ 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/SimpleFragment.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | /** 11 | * 簡易頁面用的 Fragment,目前用於授權和貢獻者頁面 12 | */ 13 | public class SimpleFragment extends Fragment { 14 | 15 | @Override 16 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 17 | Bundle args = this.getArguments(); 18 | if (args != null) { 19 | int layoutId = args.getInt("LAYOUT_ID"); 20 | return inflater.inflate(layoutId, null); 21 | } 22 | return null; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/checkupdate/AutoUpdateAdapter.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer.checkupdate; 2 | 3 | /** 4 | * 自動更新進度接收程式 5 | */ 6 | public class AutoUpdateAdapter { 7 | 8 | /** 9 | * 回報檔案需要更新 10 | * 11 | * @param filename 更新中檔案 12 | * @param reason 需要更新的原因 13 | * @param gzLength 更新檔壓縮長度 14 | * @param exLength 更新檔原始長度 15 | */ 16 | public void onFileExpired(String filename, String reason, long gzLength, long exLength) {} 17 | 18 | /** 19 | * 回報開始更新檔案 20 | * 21 | * @param filename 更新中檔案 22 | */ 23 | public void onFileBegin(String filename) {} 24 | 25 | /** 26 | * 回報傳輸進度,每填滿一次緩衝區觸發一次 27 | * 28 | * @param filename 更新中檔案 29 | * @param transfered 已傳輸長度 30 | */ 31 | public void onFileTransferLength(String filename, long transfered) {} 32 | 33 | /** 34 | * 回報傳輸進度,進度百分比異動時觸發一次 35 | * 36 | * @param filename 更新中檔案 37 | * @param percent 進度百分比 38 | */ 39 | public void onFileTransfer(String filename, int percent) {} 40 | 41 | /** 42 | * 回報解壓縮進度 43 | * 44 | * @param filename 更新中檔案 45 | * @param percent 進度百分比 46 | */ 47 | public void onFileExtract(String filename, int percent) {} 48 | 49 | /** 50 | * 回報完成一個檔案 51 | * 52 | * @param filename 更新中檔案 53 | * @param isNew 是否有更新 54 | */ 55 | public void onFileComplete(String filename, boolean isNew) {} 56 | 57 | /** 58 | * 回報一項警告,此時更新程序會自動修復 59 | * 60 | * @param filename 更新中檔案 61 | * @param reason 原因 62 | */ 63 | public void onFileWarning(String filename, String reason) {} 64 | 65 | /** 66 | * 回報更新完成 67 | */ 68 | public void onComplete() {} 69 | 70 | /** 71 | * 回報更新失敗,發生後會結束更新作業 72 | * 73 | * @param reason 原因 74 | */ 75 | public void onError(String reason) {} 76 | 77 | /** 78 | * 回報更新取消,發生後會結束更新作業 79 | */ 80 | public void onUserCancel(String progress) {} 81 | 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/checkupdate/CheckUpdateAdapter.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer.checkupdate; 2 | 3 | public class CheckUpdateAdapter { 4 | 5 | /** 6 | * 檢查完成 7 | * 8 | * @param totalLength 需要更新的總長度 9 | * @param lastModified 相關檔案最後更新時間 10 | */ 11 | public void onCheck(long totalLength, String lastModified) { 12 | System.out.printf("需要更新 %d bytes, 異動日期: %s\n", totalLength, lastModified); 13 | } 14 | 15 | /** 16 | * 檢查更新時發生錯誤 17 | * 18 | * @param reason 錯誤原因 19 | */ 20 | public void onError(String reason) { 21 | System.err.println(reason); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/map/FixedFpsSurfaceView.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer.map; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.util.AttributeSet; 6 | import android.util.Log; 7 | import android.view.SurfaceHolder; 8 | import android.view.SurfaceView; 9 | import android.view.ViewParent; 10 | 11 | import org.mapsforge.map.android.graphics.AndroidGraphicFactory; 12 | 13 | import java.util.Locale; 14 | import java.util.concurrent.Executors; 15 | import java.util.concurrent.RejectedExecutionException; 16 | import java.util.concurrent.ScheduledExecutorService; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | public class FixedFpsSurfaceView extends SurfaceView { 20 | 21 | private static final String TAG = "FixedFpsSurfaceView"; 22 | private static final int FPS = 24; 23 | private static final boolean TRACE = true; 24 | 25 | private SurfaceMapView parallelMapView; 26 | 27 | public FixedFpsSurfaceView(Context context) { 28 | this(context, null); 29 | } 30 | 31 | public FixedFpsSurfaceView(Context context, AttributeSet attrs) { 32 | super(context, attrs); 33 | 34 | this.setWillNotDraw(false); 35 | this.getHolder().addCallback(callback); 36 | 37 | if (TRACE) { 38 | long thId = Thread.currentThread().getId(); 39 | String msg = String.format(Locale.getDefault(), "Init MySurface at thread #%d", thId); 40 | Log.d(TAG, msg); 41 | } 42 | } 43 | 44 | public SurfaceMapView getParentViewGroup() { 45 | if (parallelMapView == null) { 46 | ViewParent parent = getParent(); 47 | if (parent instanceof SurfaceMapView) { 48 | parallelMapView = (SurfaceMapView)parent; 49 | } 50 | } 51 | 52 | return parallelMapView; 53 | } 54 | 55 | private void onDrawSurface(Canvas androidCanvas) { 56 | if (getParentViewGroup()!=null) { 57 | org.mapsforge.core.graphics.Canvas graphicContext = AndroidGraphicFactory.createGraphicContext(androidCanvas); 58 | 59 | parallelMapView.getFrameBuffer().draw(graphicContext); 60 | parallelMapView.getMapScaleBar().draw(graphicContext); 61 | parallelMapView.getFpsCounter().draw(graphicContext); 62 | 63 | graphicContext.destroy(); 64 | } 65 | 66 | if (TRACE) { 67 | long thId = Thread.currentThread().getId(); 68 | String msg = String.format(Locale.getDefault(), "Draw MySurface at thread #%d", thId); 69 | Log.d(TAG, msg); 70 | } 71 | } 72 | 73 | private SurfaceHolder.Callback callback = new SurfaceHolder.Callback() { 74 | 75 | ScheduledExecutorService exesvc; 76 | 77 | @Override 78 | public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { 79 | if (TRACE) { 80 | Log.d(TAG, "surfaceChanged()"); 81 | } 82 | try { 83 | long period = 1000000 / FPS; 84 | exesvc.scheduleAtFixedRate(drawTask, 0, period, TimeUnit.MICROSECONDS); 85 | exesvc.schedule(new Runnable() { 86 | @Override 87 | public void run() { 88 | exesvc.shutdown(); 89 | } 90 | }, 1000, TimeUnit.SECONDS); 91 | } catch(RejectedExecutionException ex) { 92 | Log.e(TAG, "Cannot run drawTask."); 93 | } 94 | } 95 | 96 | @Override 97 | public void surfaceCreated(SurfaceHolder surfaceHolder) { 98 | if (TRACE) { 99 | Log.d(TAG, "surfaceCreated()"); 100 | } 101 | exesvc = Executors.newSingleThreadScheduledExecutor(); 102 | } 103 | 104 | @Override 105 | public void surfaceDestroyed(SurfaceHolder surfaceHolder) { 106 | if (TRACE) { 107 | Log.d(TAG, "surfaceDestroyed()"); 108 | } 109 | exesvc.shutdown(); 110 | exesvc = null; 111 | } 112 | 113 | }; 114 | 115 | private Runnable drawTask = new Runnable() { 116 | 117 | @Override 118 | public void run() { 119 | Canvas canvas = getHolder().lockCanvas(); 120 | if (canvas!=null) { 121 | onDrawSurface(canvas); 122 | getHolder().unlockCanvasAndPost(canvas); 123 | postInvalidate(); 124 | } else { 125 | Log.w(TAG, "canvas is null."); 126 | } 127 | } 128 | }; 129 | 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/map/Pin.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer.map; 2 | 3 | import org.mapsforge.core.graphics.Bitmap; 4 | import org.mapsforge.core.graphics.Canvas; 5 | import org.mapsforge.core.graphics.FontFamily; 6 | import org.mapsforge.core.graphics.FontStyle; 7 | import org.mapsforge.core.graphics.GraphicFactory; 8 | import org.mapsforge.core.graphics.Matrix; 9 | import org.mapsforge.core.graphics.Paint; 10 | import org.mapsforge.core.graphics.Path; 11 | import org.mapsforge.core.model.BoundingBox; 12 | import org.mapsforge.core.model.LatLong; 13 | import org.mapsforge.core.model.Point; 14 | import org.mapsforge.core.util.MercatorProjection; 15 | import org.mapsforge.map.layer.Layer; 16 | 17 | /** 18 | * Pin of POI 19 | * 20 | * This Layer draw a pin and some text on the map directly. 21 | * The pin has selected and unselected state, using two different colors to represent. 22 | * Under selected state, a label string appears below the pin to explain what it is. 23 | * 24 | * @author 小璋丸 25 | */ 26 | public class Pin extends Layer { 27 | 28 | private static final int PIN_WIDTH = 100; 29 | private static final int PIN_HEIGHT = 200; 30 | 31 | private LatLong latLong; 32 | private String category; 33 | private String label; 34 | private GraphicFactory gf; 35 | private boolean selected = false; 36 | private float angle = 0f; 37 | private float scale = 0.4f; 38 | 39 | private int darkColor = 0xff900000; 40 | private int brightColor = 0xffff0000; 41 | 42 | /** 43 | * Create a pin without category or label. 44 | * 45 | * @param latLong position 46 | * @param gf GraphicFactory 47 | */ 48 | public Pin(LatLong latLong, GraphicFactory gf) { 49 | this(latLong, "", "", gf); 50 | } 51 | 52 | /** 53 | * Create a pin with category and label. 54 | * 55 | * @param latLong position 56 | * @param category text in the pin 57 | * @param label text below the pin 58 | * @param gf GraphicFactory 59 | */ 60 | public Pin(LatLong latLong, String category, String label, GraphicFactory gf) { 61 | super(); 62 | 63 | this.latLong = latLong; 64 | this.category = category; 65 | this.label = label; 66 | this.gf = gf; 67 | 68 | if (gf.getClass().getSimpleName().equals("AndroidGraphicFactory")) { 69 | scale = 1.0f; 70 | } 71 | } 72 | 73 | @Override 74 | public boolean onTap(LatLong tapLatLong, Point layerXY, Point tapXY) { 75 | double dx1 = tapXY.x - layerXY.x; 76 | double dy1 = tapXY.y - layerXY.y; 77 | 78 | double rad = Math.toRadians(-angle); 79 | double dx = (dx1 * Math.cos(rad) - dy1 * Math.sin(rad)) / scale; 80 | double dy = (dx1 * Math.sin(rad) + dy1 * Math.cos(rad)) / scale; 81 | 82 | if (Math.abs(dx) < PIN_WIDTH/2 && Math.abs(dy + PIN_HEIGHT/2) < PIN_HEIGHT/2) { 83 | selected = !selected; 84 | requestRedraw(); 85 | return true; 86 | } 87 | 88 | return false; 89 | } 90 | 91 | @Override 92 | public synchronized void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas, Point topLeftPoint) { 93 | long mapSize = MercatorProjection.getMapSize(zoomLevel, displayModel.getTileSize()); 94 | int tx = (int)(MercatorProjection.longitudeToPixelX(latLong.longitude, mapSize) - topLeftPoint.x); 95 | int ty = (int)(MercatorProjection.latitudeToPixelY(latLong.latitude, mapSize) - topLeftPoint.y); 96 | 97 | Paint paint = gf.createPaint(); 98 | if (selected) { 99 | paint.setColor(brightColor); 100 | } else { 101 | paint.setColor(darkColor); 102 | } 103 | 104 | Bitmap tempBitmap = gf.createBitmap(PIN_WIDTH, PIN_HEIGHT); 105 | Canvas tempCanvas = gf.createCanvas(); 106 | tempCanvas.setBitmap(tempBitmap); 107 | 108 | // Draw triangle 109 | Path path = gf.createPath(); 110 | path.moveTo(PIN_WIDTH/2, PIN_HEIGHT-1); 111 | path.lineTo((int)(PIN_WIDTH*0.2), PIN_WIDTH/2); 112 | path.lineTo((int)(PIN_WIDTH*0.8), PIN_WIDTH/2); 113 | path.close(); 114 | tempCanvas.drawPath(path, paint); 115 | 116 | // Draw circle 117 | tempCanvas.drawCircle(PIN_WIDTH/2, PIN_WIDTH/2, PIN_WIDTH/2, paint); 118 | 119 | // Draw category 120 | paint.setColor(0xffffffff); 121 | paint.setTextSize(PIN_WIDTH * 0.55f); 122 | paint.setTypeface(FontFamily.SANS_SERIF, FontStyle.BOLD); 123 | int cx = (PIN_WIDTH - paint.getTextWidth(category))/2; 124 | int cy = (int)(PIN_WIDTH/2 + paint.getTextHeight(category)*0.35); 125 | tempCanvas.drawText(category, cx, cy, paint); 126 | 127 | // Paste pin 128 | Matrix matrix = gf.createMatrix(); 129 | matrix.translate(tx, ty); 130 | matrix.scale(scale, scale); 131 | matrix.rotate((float)Math.toRadians(angle)); 132 | matrix.translate(-PIN_WIDTH/2, -PIN_HEIGHT); 133 | canvas.drawBitmap(tempBitmap, matrix); 134 | 135 | // Draw label 136 | if (selected) { 137 | paint.setColor(0xff000000); 138 | paint.setTextSize(PIN_WIDTH * 0.4f); 139 | int margin = 10; 140 | int lw = paint.getTextWidth(label); 141 | int lh = paint.getTextHeight(label); 142 | int fw = lw + margin * 2; 143 | int fh = lh + margin * 2; 144 | tempBitmap = gf.createBitmap(fw, fh); 145 | tempCanvas.setBitmap(tempBitmap); 146 | tempCanvas.fillColor(0xe0ffd070); 147 | tempCanvas.drawText(label, margin, lh + margin - 3, paint); 148 | 149 | matrix.reset(); 150 | matrix.translate(tx, ty); 151 | matrix.scale(scale, scale); 152 | matrix.rotate((float)Math.toRadians(angle)); 153 | matrix.translate(-fw/2, margin); 154 | 155 | canvas.drawBitmap(tempBitmap, matrix); 156 | } 157 | } 158 | 159 | /** 160 | * Return label of the pin. 161 | * 162 | * @return label 163 | */ 164 | public synchronized String getLabel() { 165 | return label; 166 | } 167 | 168 | @Override 169 | public synchronized LatLong getPosition() { 170 | return this.latLong; 171 | } 172 | 173 | /** 174 | * Set colors of the pin. 175 | * 176 | * @param darkColor unselected state color 177 | * @param brightColor selected state color 178 | */ 179 | public synchronized void setPinColors(int darkColor, int brightColor) { 180 | this.darkColor = darkColor; 181 | this.brightColor = brightColor; 182 | } 183 | 184 | /** 185 | * Set position of the pin. 186 | * 187 | * @param latLong position 188 | */ 189 | public synchronized void setPosition(LatLong latLong) { 190 | this.latLong = latLong; 191 | } 192 | 193 | /** 194 | * Set selection state of the pin. 195 | * 196 | * @param selected 197 | */ 198 | public synchronized void setSelected(boolean selected) { 199 | this.selected = selected; 200 | } 201 | 202 | /** 203 | * Set angle of pin. 204 | * 205 | * @param angle 206 | */ 207 | public synchronized void setAngle(float angle) { 208 | this.angle = angle; 209 | } 210 | 211 | } 212 | 213 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/map/PinGroup.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer.map; 2 | 3 | import org.mapsforge.core.graphics.Canvas; 4 | import org.mapsforge.core.graphics.GraphicFactory; 5 | import org.mapsforge.core.model.BoundingBox; 6 | import org.mapsforge.core.model.LatLong; 7 | import org.mapsforge.core.model.Point; 8 | import org.mapsforge.map.layer.Layer; 9 | import org.mapsforge.map.util.MapViewProjection; 10 | 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * Maintain a set of pins and make all pins trigger onTap() event in single selection mode. 18 | * 19 | * @author 小璋丸 20 | */ 21 | public class PinGroup extends Layer { 22 | 23 | private String category; 24 | private GraphicFactory gf; 25 | private MapViewProjection proj; 26 | private Map idOfPin = new HashMap<>(); 27 | 28 | private int darkColor = 0xff900000; 29 | private int brightColor = 0xffff0000; 30 | private float angle = 0; 31 | 32 | private List pinPool = new ArrayList<>(); 33 | private OnSelectListener listener = null; 34 | 35 | public interface OnSelectListener { 36 | void OnSelectPin(String category, String id); 37 | } 38 | 39 | /** 40 | * Create a PinGroup 41 | * 42 | * @param category text in the pin 43 | * @param gf GraphicFactory 44 | * @param proj Projection 45 | */ 46 | public PinGroup(String category, GraphicFactory gf, MapViewProjection proj) { 47 | this.category = category; 48 | this.gf = gf; 49 | this.proj = proj; 50 | } 51 | 52 | /** 53 | * Add a pin into group. 54 | * 55 | * @param latLong position 56 | * @param label text below the pin 57 | * @param id if of the pin 58 | */ 59 | public synchronized void add(LatLong latLong, String label, String id) { 60 | Pin p = new Pin(latLong, category, label, gf); 61 | p.setPinColors(darkColor, brightColor); 62 | p.setAngle(angle); 63 | pinPool.add(p); 64 | idOfPin.put(p, id); 65 | } 66 | 67 | /** 68 | * Remove all pins in the group. 69 | */ 70 | public synchronized void clear() { 71 | pinPool.clear(); 72 | requestRedraw(); 73 | } 74 | 75 | /** 76 | * Set colors of all pins in the group. 77 | * 78 | * @param darkColor unselected state color 79 | * @param brightColor selected state color 80 | */ 81 | public synchronized void setPinColors(int darkColor, int brightColor) { 82 | this.darkColor = darkColor; 83 | this.brightColor = brightColor; 84 | 85 | if (pinPool.size() > 0) { 86 | for (Pin p : pinPool) { 87 | p.setPinColors(darkColor, brightColor); 88 | } 89 | requestRedraw(); 90 | } 91 | } 92 | 93 | public synchronized void setAngle(float angle) { 94 | this.angle = angle; 95 | 96 | if (pinPool.size() > 0) { 97 | for (Pin p : pinPool) { 98 | p.setAngle(angle); 99 | } 100 | requestRedraw(); 101 | } 102 | } 103 | 104 | public synchronized void setOnSelectListener(OnSelectListener listener) { 105 | this.listener = listener; 106 | } 107 | 108 | public synchronized int size() { 109 | return pinPool.size(); 110 | } 111 | 112 | @Override 113 | public boolean onTap(LatLong tapLatLong, Point layerXY, Point tapXY) { 114 | Pin bingo = null; 115 | 116 | // trigger bottom first 117 | for (int i = pinPool.size() - 1; i >= 0; i--) { 118 | Pin p = pinPool.get(i); 119 | Point pinXY = proj.toPixels(p.getPosition()); 120 | if (p.onTap(tapLatLong, pinXY, tapXY)) { 121 | bingo = p; 122 | break; 123 | } 124 | } 125 | 126 | // deselect others & move bingo to the top. 127 | if (bingo != null) { 128 | pinPool.remove(bingo); 129 | for (Pin p : pinPool) { 130 | p.setSelected(false); 131 | } 132 | pinPool.add(0, bingo); 133 | requestRedraw(); 134 | 135 | if (listener != null) { 136 | String id = idOfPin.get(bingo); 137 | listener.OnSelectPin(category, id); 138 | } 139 | } 140 | 141 | return false; 142 | } 143 | 144 | @Override 145 | public synchronized void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas, Point topLeftPoint) { 146 | // draw bottom first 147 | for (int i = pinPool.size() - 1; i >= 0; i--) { 148 | Pin p = pinPool.get(i); 149 | p.setDisplayModel(getDisplayModel()); 150 | p.draw(boundingBox, zoomLevel, canvas, topLeftPoint); 151 | } 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/map/SurfaceMapView.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer.map; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.util.AttributeSet; 6 | import android.view.ViewGroup; 7 | 8 | import org.mapsforge.map.android.view.MapView; 9 | 10 | /** 11 | * Android MapView using SurfaceView 12 | */ 13 | public class SurfaceMapView extends MapView { 14 | 15 | private FixedFpsSurfaceView mapSurface; 16 | 17 | public SurfaceMapView(Context context) { 18 | this(context, null); 19 | } 20 | 21 | public SurfaceMapView(Context context, AttributeSet attrs) { 22 | super(context, attrs); 23 | 24 | // Create SurfaceView to draw tiles. 25 | mapSurface = new FixedFpsSurfaceView(context); 26 | ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( 27 | ViewGroup.LayoutParams.MATCH_PARENT, 28 | ViewGroup.LayoutParams.MATCH_PARENT 29 | ); 30 | addViewInLayout(mapSurface, -1, params, true); 31 | 32 | // Move MapZoomControls to top of MapView. 33 | removeViewInLayout(getMapZoomControls()); 34 | params = getMapZoomControls().getLayoutParams(); 35 | addViewInLayout(getMapZoomControls(), -1, params, true); 36 | } 37 | 38 | @Override 39 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 40 | super.onLayout(changed, left, top, right, bottom); 41 | int w = mapSurface.getMeasuredWidth(); 42 | int h = mapSurface.getMeasuredHeight(); 43 | mapSurface.layout(0, 0, w, h); 44 | } 45 | 46 | @Override 47 | protected void onDraw(Canvas androidCanvas) { 48 | // Do nothing, draw everything by SurfaceView. 49 | } 50 | 51 | @Override 52 | public void destroy() { 53 | this.removeViewInLayout(mapSurface); 54 | // mapSurface.release(); 55 | mapSurface = null; 56 | super.destroy(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/view/CircleButton.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.graphics.Matrix; 7 | import android.graphics.Paint; 8 | import android.graphics.PorterDuff; 9 | import android.graphics.PorterDuffXfermode; 10 | import android.graphics.drawable.Drawable; 11 | import android.support.v7.widget.AppCompatImageView; 12 | import android.util.AttributeSet; 13 | 14 | /** 15 | * 覆蓋在地圖介面上的圓形按鈕,用來切換 Fragment 16 | */ 17 | public class CircleButton extends AppCompatImageView { 18 | 19 | /** 20 | * 建構方法 21 | */ 22 | public CircleButton(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | } 25 | 26 | /** 27 | * 畫圓形按鈕的動作細節 28 | * 29 | * @param canvas 畫布 30 | */ 31 | @Override 32 | protected void onDraw(Canvas canvas) { 33 | Canvas tempCv = new Canvas(); 34 | Drawable d = getDrawable(); 35 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 36 | 37 | int w = getMeasuredWidth(); 38 | int h = getMeasuredHeight(); 39 | int dw = d.getIntrinsicWidth(); 40 | int dh = d.getIntrinsicHeight(); 41 | float sw = (float)w/dw; 42 | float sh = (float)h/dh; 43 | float s = Math.max(sw, sh); 44 | float strokeWidth = 6.0f; 45 | 46 | // Create circle mask 47 | Bitmap mask = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 48 | tempCv.setBitmap(mask); 49 | p.setColor(0xffffffff); 50 | tempCv.drawCircle(w/2, h/2, w/2-1, p); 51 | 52 | // Paste Image 53 | Bitmap result = Bitmap.createBitmap(w, h , Bitmap.Config.ARGB_8888); 54 | tempCv.setBitmap(result); 55 | tempCv.scale(s, s); 56 | d.draw(tempCv); 57 | tempCv.setMatrix(new Matrix()); 58 | 59 | // Apply circle mask 60 | p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); 61 | tempCv.drawBitmap(mask, 0, 0, p); 62 | p.setXfermode(null); 63 | 64 | // Draw circle border 65 | p.setColor(0xff7b5d1d); 66 | p.setStyle(Paint.Style.STROKE); 67 | p.setStrokeWidth(strokeWidth); 68 | tempCv.drawCircle(w/2, h/2, w/2-(float)Math.ceil(strokeWidth/2), p); 69 | 70 | // Display 71 | canvas.drawBitmap(result, 0, 0, p); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/tacoball/com/geomancer/view/LinkView.java: -------------------------------------------------------------------------------- 1 | package tacoball.com.geomancer.view; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.AppCompatTextView; 5 | import android.text.method.LinkMovementMethod; 6 | import android.util.AttributeSet; 7 | 8 | /** 9 | * 用於 License 頁面,讓文字自動變成超連結 10 | */ 11 | public class LinkView extends AppCompatTextView { 12 | 13 | /** 14 | * 自動變超連結的設定 15 | */ 16 | @Override 17 | protected void onAttachedToWindow() { 18 | super.onAttachedToWindow(); 19 | setMovementMethod(LinkMovementMethod.getInstance()); 20 | } 21 | 22 | /** 23 | * 建構方法 24 | */ 25 | public LinkView(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/contrib96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OsmHackTW/GeomancerAndroid/f134ee8751a0abe85325c3fd9707cd0f84f7a9c7/app/src/main/res/drawable/contrib96.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/debug_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/g0v_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OsmHackTW/GeomancerAndroid/f134ee8751a0abe85325c3fd9707cd0f84f7a9c7/app/src/main/res/drawable/g0v_dark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/geomancer_info_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/license96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OsmHackTW/GeomancerAndroid/f134ee8751a0abe85325c3fd9707cd0f84f7a9c7/app/src/main/res/drawable/license96.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/measure_result_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/more96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OsmHackTW/GeomancerAndroid/f134ee8751a0abe85325c3fd9707cd0f84f7a9c7/app/src/main/res/drawable/more96.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/settings96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OsmHackTW/GeomancerAndroid/f134ee8751a0abe85325c3fd9707cd0f84f7a9c7/app/src/main/res/drawable/settings96.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_license.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 17 | 18 | 27 | 28 | 37 | 38 | 44 | 45 | 50 | 51 | 57 | 58 | 63 | 64 | 70 | 71 | 76 | 77 | 83 | 84 | 89 | 90 | 99 | 100 | 106 | 107 | 112 | 113 | 119 | 120 | 125 | 126 | 127 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_updater.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 14 | 15 | 22 | 23 | 30 | 31 | 38 | 39 | 48 | 49 | 55 | 56 | 57 | 58 |