├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── .travis.yml ├── LICENSE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── rubenwardy │ │ └── minetestmodmanager │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── rubenwardy │ │ │ └── minetestmodmanager │ │ │ ├── MyApplication.kt │ │ │ ├── manager │ │ │ ├── BCastReceiver.java │ │ │ ├── ModInstallService.java │ │ │ ├── ModManager.java │ │ │ ├── ServiceResultReceiver.java │ │ │ └── Utils.java │ │ │ ├── models │ │ │ ├── Events.java │ │ │ ├── Game.java │ │ │ ├── MinetestConf.java │ │ │ ├── MinetestDepends.java │ │ │ ├── Mod.java │ │ │ ├── ModList.java │ │ │ └── ModSpec.java │ │ │ ├── presenters │ │ │ ├── DisclaimerPresenter.kt │ │ │ └── ModListPresenter.kt │ │ │ ├── restapi │ │ │ ├── StoreAPI.java │ │ │ └── StoreAPIBuilder.java │ │ │ └── views │ │ │ ├── DisclaimerActivity.java │ │ │ ├── InstallDependsDialogFragment.java │ │ │ ├── ModDetailActivity.java │ │ │ ├── ModDetailFragment.java │ │ │ ├── ModInfoDialogFragment.java │ │ │ ├── ModListActivity.java │ │ │ ├── ReadmeActivity.java │ │ │ ├── ReportActivity.java │ │ │ ├── SectionedRecyclerViewAdapter.java │ │ │ ├── SettingsAndAboutActivity.kt │ │ │ ├── SplashActivity.kt │ │ │ └── WorldConfigActivity.java │ └── res │ │ ├── drawable-hdpi-v11 │ │ └── notification_icon.png │ │ ├── drawable-hdpi-v9 │ │ └── notification_icon.png │ │ ├── drawable-hdpi │ │ └── notification_icon.png │ │ ├── drawable-mdpi-v11 │ │ └── notification_icon.png │ │ ├── drawable-mdpi-v9 │ │ └── notification_icon.png │ │ ├── drawable-mdpi │ │ └── notification_icon.png │ │ ├── drawable-xhdpi-v11 │ │ └── notification_icon.png │ │ ├── drawable-xhdpi-v9 │ │ └── notification_icon.png │ │ ├── drawable-xhdpi │ │ └── notification_icon.png │ │ ├── drawable-xxhdpi-v11 │ │ └── notification_icon.png │ │ ├── drawable-xxhdpi-v9 │ │ └── notification_icon.png │ │ ├── drawable-xxhdpi │ │ └── notification_icon.png │ │ ├── drawable │ │ ├── background_splash.xml │ │ ├── ic_mod_action_check_depends_24dp.xml │ │ ├── ic_mod_action_find_elsewhere_24dp.xml │ │ ├── ic_mod_action_forum_topic_24dp.xml │ │ ├── ic_mod_action_info_24dp.xml │ │ ├── ic_mod_action_mods_by_author.xml │ │ ├── ic_mod_action_readme_24dp.xml │ │ ├── ic_mod_action_report_24dp.xml │ │ ├── ic_public_24dp.xml │ │ └── mod_preview_circle.xml │ │ ├── layout │ │ ├── activity_disclaimer.xml │ │ ├── activity_mod_detail.xml │ │ ├── activity_mod_list.xml │ │ ├── activity_readme.xml │ │ ├── activity_report.xml │ │ ├── activity_settingsandabout.xml │ │ ├── activity_world_config.xml │ │ ├── depends_list_item.xml │ │ ├── dialog_mod_information.xml │ │ ├── fragment_install_depends_dialog.xml │ │ ├── mod_checklist_content.xml │ │ ├── mod_detail.xml │ │ ├── mod_list.xml │ │ ├── mod_list_content.xml │ │ ├── mod_list_twopane.xml │ │ └── section.xml │ │ ├── menu │ │ ├── menu_mod_list.xml │ │ ├── menu_report.xml │ │ └── menu_world_config.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-cs │ │ └── strings.xml │ │ ├── values-de │ │ └── strings.xml │ │ ├── values-es │ │ └── strings.xml │ │ ├── values-fr │ │ └── strings.xml │ │ ├── values-it │ │ └── strings.xml │ │ ├── values-ms │ │ └── strings.xml │ │ ├── values-night │ │ └── colors.xml │ │ ├── values-pl │ │ └── strings.xml │ │ ├── values-tr │ │ └── strings.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values-w900dp │ │ └── refs.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── rubenwardy │ └── minetestmodmanager │ └── ExampleUnitTest.java ├── build.gradle ├── gradle-app.setting ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── misc ├── logo.svg ├── logo_fancy.svg └── notification_icon.svg └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | *.apk 10 | 11 | # Created by https://www.gitignore.io/api/java,linux,macos,android,windows,intellij,androidstudio 12 | # Edit at https://www.gitignore.io/?templates=java,linux,macos,android,windows,intellij,androidstudio 13 | 14 | ### Android ### 15 | # Built application files 16 | *.apk 17 | *.ap_ 18 | *.aab 19 | 20 | # Files for the ART/Dalvik VM 21 | *.dex 22 | 23 | # Java class files 24 | *.class 25 | 26 | # Generated files 27 | bin/ 28 | gen/ 29 | out/ 30 | 31 | # Gradle files 32 | .gradle/ 33 | build/ 34 | 35 | # Local configuration file (sdk path, etc) 36 | local.properties 37 | 38 | # Proguard folder generated by Eclipse 39 | proguard/ 40 | 41 | # Log Files 42 | *.log 43 | 44 | # Android Studio Navigation editor temp files 45 | .navigation/ 46 | 47 | # Android Studio captures folder 48 | captures/ 49 | 50 | # IntelliJ 51 | *.iml 52 | .idea/workspace.xml 53 | .idea/tasks.xml 54 | .idea/gradle.xml 55 | .idea/assetWizardSettings.xml 56 | .idea/dictionaries 57 | .idea/libraries 58 | .idea/caches 59 | # Android Studio 3 in .gitignore file. 60 | .idea/caches/build_file_checksums.ser 61 | .idea/modules.xml 62 | 63 | # Keystore files 64 | # Uncomment the following lines if you do not want to check your keystore files in. 65 | #*.jks 66 | #*.keystore 67 | 68 | # External native build folder generated in Android Studio 2.2 and later 69 | .externalNativeBuild 70 | 71 | # Google Services (e.g. APIs or Firebase) 72 | # google-services.json 73 | 74 | # Freeline 75 | freeline.py 76 | freeline/ 77 | freeline_project_description.json 78 | 79 | # fastlane 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots 83 | fastlane/test_output 84 | fastlane/readme.md 85 | 86 | # Version control 87 | vcs.xml 88 | 89 | # lint 90 | lint/intermediates/ 91 | lint/generated/ 92 | lint/outputs/ 93 | lint/tmp/ 94 | # lint/reports/ 95 | 96 | ### Android Patch ### 97 | gen-external-apklibs 98 | output.json 99 | 100 | ### AndroidStudio ### 101 | # Covers files to be ignored for android development using Android Studio. 102 | 103 | # Built application files 104 | 105 | # Files for the ART/Dalvik VM 106 | 107 | # Java class files 108 | 109 | # Generated files 110 | 111 | # Gradle files 112 | .gradle 113 | 114 | # Signing files 115 | .signing/ 116 | 117 | # Local configuration file (sdk path, etc) 118 | 119 | # Proguard folder generated by Eclipse 120 | 121 | # Log Files 122 | 123 | # Android Studio 124 | /*/build/ 125 | /*/local.properties 126 | /*/out 127 | /*/*/build 128 | /*/*/production 129 | *.ipr 130 | *~ 131 | *.swp 132 | 133 | # Android Patch 134 | 135 | # External native build folder generated in Android Studio 2.2 and later 136 | 137 | # NDK 138 | obj/ 139 | 140 | # IntelliJ IDEA 141 | *.iws 142 | /out/ 143 | 144 | # User-specific configurations 145 | .idea/caches/ 146 | .idea/libraries/ 147 | .idea/shelf/ 148 | .idea/.name 149 | .idea/compiler.xml 150 | .idea/copyright/profiles_settings.xml 151 | .idea/encodings.xml 152 | .idea/misc.xml 153 | .idea/scopes/scope_settings.xml 154 | .idea/vcs.xml 155 | .idea/jsLibraryMappings.xml 156 | .idea/datasources.xml 157 | .idea/dataSources.ids 158 | .idea/sqlDataSources.xml 159 | .idea/dynamic.xml 160 | .idea/uiDesigner.xml 161 | 162 | # OS-specific files 163 | .DS_Store 164 | .DS_Store? 165 | ._* 166 | .Spotlight-V100 167 | .Trashes 168 | ehthumbs.db 169 | Thumbs.db 170 | 171 | # Legacy Eclipse project files 172 | .classpath 173 | .project 174 | .cproject 175 | .settings/ 176 | 177 | # Mobile Tools for Java (J2ME) 178 | .mtj.tmp/ 179 | 180 | # Package Files # 181 | *.war 182 | *.ear 183 | 184 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 185 | hs_err_pid* 186 | 187 | ## Plugin-specific files: 188 | 189 | # mpeltonen/sbt-idea plugin 190 | .idea_modules/ 191 | 192 | # JIRA plugin 193 | atlassian-ide-plugin.xml 194 | 195 | # Mongo Explorer plugin 196 | .idea/mongoSettings.xml 197 | 198 | # Crashlytics plugin (for Android Studio and IntelliJ) 199 | com_crashlytics_export_strings.xml 200 | crashlytics.properties 201 | crashlytics-build.properties 202 | fabric.properties 203 | 204 | ### AndroidStudio Patch ### 205 | 206 | !/gradle/wrapper/gradle-wrapper.jar 207 | 208 | ### Intellij ### 209 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 210 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 211 | 212 | # User-specific stuff 213 | .idea/**/workspace.xml 214 | .idea/**/tasks.xml 215 | .idea/**/usage.statistics.xml 216 | .idea/**/dictionaries 217 | .idea/**/shelf 218 | 219 | # Generated files 220 | .idea/**/contentModel.xml 221 | 222 | # Sensitive or high-churn files 223 | .idea/**/dataSources/ 224 | .idea/**/dataSources.ids 225 | .idea/**/dataSources.local.xml 226 | .idea/**/sqlDataSources.xml 227 | .idea/**/dynamic.xml 228 | .idea/**/uiDesigner.xml 229 | .idea/**/dbnavigator.xml 230 | 231 | # Gradle 232 | .idea/**/gradle.xml 233 | .idea/**/libraries 234 | 235 | # Gradle and Maven with auto-import 236 | # When using Gradle or Maven with auto-import, you should exclude module files, 237 | # since they will be recreated, and may cause churn. Uncomment if using 238 | # auto-import. 239 | # .idea/modules.xml 240 | # .idea/*.iml 241 | # .idea/modules 242 | 243 | # CMake 244 | cmake-build-*/ 245 | 246 | # Mongo Explorer plugin 247 | .idea/**/mongoSettings.xml 248 | 249 | # File-based project format 250 | 251 | # IntelliJ 252 | 253 | # mpeltonen/sbt-idea plugin 254 | 255 | # JIRA plugin 256 | 257 | # Cursive Clojure plugin 258 | .idea/replstate.xml 259 | 260 | # Crashlytics plugin (for Android Studio and IntelliJ) 261 | 262 | # Editor-based Rest Client 263 | .idea/httpRequests 264 | 265 | # Android studio 3.1+ serialized cache file 266 | 267 | # JetBrains templates 268 | **___jb_tmp___ 269 | 270 | ### Intellij Patch ### 271 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 272 | 273 | # *.iml 274 | # modules.xml 275 | # .idea/misc.xml 276 | # *.ipr 277 | 278 | # Sonarlint plugin 279 | .idea/sonarlint 280 | 281 | ### Java ### 282 | # Compiled class file 283 | 284 | # Log file 285 | 286 | # BlueJ files 287 | *.ctxt 288 | 289 | # Mobile Tools for Java (J2ME) 290 | 291 | # Package Files # 292 | *.jar 293 | *.nar 294 | *.zip 295 | *.tar.gz 296 | *.rar 297 | 298 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 299 | 300 | ### Linux ### 301 | 302 | # temporary files which can be created if a process still has a handle open of a deleted file 303 | .fuse_hidden* 304 | 305 | # KDE directory preferences 306 | .directory 307 | 308 | # Linux trash folder which might appear on any partition or disk 309 | .Trash-* 310 | 311 | # .nfs files are created when an open file is removed but is still being accessed 312 | .nfs* 313 | 314 | ### macOS ### 315 | # General 316 | .AppleDouble 317 | .LSOverride 318 | 319 | # Icon must end with two \r 320 | Icon 321 | 322 | # Thumbnails 323 | 324 | # Files that might appear in the root of a volume 325 | .DocumentRevisions-V100 326 | .fseventsd 327 | .TemporaryItems 328 | .VolumeIcon.icns 329 | .com.apple.timemachine.donotpresent 330 | 331 | # Directories potentially created on remote AFP share 332 | .AppleDB 333 | .AppleDesktop 334 | Network Trash Folder 335 | Temporary Items 336 | .apdisk 337 | 338 | ### Windows ### 339 | # Windows thumbnail cache files 340 | ehthumbs_vista.db 341 | 342 | # Dump file 343 | *.stackdump 344 | 345 | # Folder config file 346 | [Dd]esktop.ini 347 | 348 | # Recycle Bin used on file shares 349 | $RECYCLE.BIN/ 350 | 351 | # Windows Installer files 352 | *.cab 353 | *.msi 354 | *.msix 355 | *.msm 356 | *.msp 357 | 358 | # Windows shortcuts 359 | *.lnk 360 | 361 | # End of https://www.gitignore.io/api/java,linux,macos,android,windows,intellij,androidstudio 362 | 363 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | sudo: false 3 | android: 4 | components: 5 | - tools 6 | - build-tools-25.0.2 7 | - android-25 8 | - extra-android-support 9 | - extra-google-m2repository 10 | - extra-android-m2repository 11 | - platform-tools 12 | script: ./gradlew build test 13 | jdk: oraclejdk8 14 | os: 15 | - linux 16 | notifications: 17 | email: false 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minetest Mods for Android 2 | 3 | [![Build Status](https://travis-ci.org/rubenwardy/mtmods4android.svg?branch=master)](https://travis-ci.org/rubenwardy/mtmods4android) 4 | [![Translated](https://img.shields.io/badge/translated-weblate-blue.svg)](https://hosted.weblate.org/projects/minetest/mtmods4android/) 5 | ![MinSdk](https://img.shields.io/badge/MinSdk-14-blue.svg) 6 | [![Release](https://img.shields.io/github/release/rubenwardy/mtmods4android.svg)](https://github.com/rubenwardy/mtmods4android/releases) 7 | ![License](https://img.shields.io/badge/license-GPLv3-blue.svg) 8 | 9 | A mod manager for Android. Used as a project to learn the Android SDK. 10 | 11 | Created by [rubenwardy](http://rubenwardy.com). [Backend](http://app-mtmm.rubenwardy.com) 12 | 13 | 14 | Get it on Google Play 15 | 16 | Get it on F-Droid 17 | 18 | # License 19 | 20 | Minetest Mods for Android 21 | Copyright (C) 2016-2017 rubenwardy 22 | 23 | This program is free software: you can redistribute it and/or modify 24 | it under the terms of the GNU General Public License as published by 25 | the Free Software Foundation, either version 3 of the License, or 26 | (at your option) any later version. 27 | 28 | This program is distributed in the hope that it will be useful, 29 | but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | GNU General Public License for more details. 32 | 33 | You should have received a copy of the GNU General Public License 34 | along with this program. If not, see . 35 | 36 | 37 | ## Exceptions 38 | 39 | All resources which feature the Minetest logo, such as the notification icon and the launcher icon, 40 | are licensed under CC-BY-SA 3.0. The original author is erlehmann. 41 | 42 | The following are from the Android material design asset studio, and are CC-BY 4.0: 43 | 44 | * app/src/main/res/drawable/ic_public_24dp.xml 45 | * app/src/main/res/drawable/ic_report_problem_24dp.xml 46 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 28 7 | buildToolsVersion "28.0.3" 8 | defaultConfig { 9 | applicationId "com.rubenwardy.minetestmodmanager" 10 | minSdkVersion 14 11 | targetSdkVersion 28 12 | versionCode 23 13 | versionName "1.9.1" 14 | buildConfigField "boolean", "ENABLE_RATE_ME", "false" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled true 19 | shrinkResources true 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | 22 | lintOptions { 23 | disable 'MissingTranslation' 24 | } 25 | } 26 | } 27 | lintOptions { 28 | abortOnError false 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(include: ['*.jar'], dir: 'libs') 34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 35 | implementation 'com.android.support:appcompat-v7:28.0.0' 36 | implementation 'com.android.support:support-v4:28.0.0' 37 | implementation 'com.android.support:recyclerview-v7:28.0.0' 38 | implementation 'com.android.support:design:28.0.0' 39 | implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5' 40 | implementation 'com.squareup.retrofit2:retrofit:2.5.0' 41 | implementation 'com.squareup.retrofit2:converter-gson:2.5.0' 42 | implementation 'org.greenrobot:eventbus:3.1.1' 43 | implementation 'com.squareup.picasso:picasso:2.71828' 44 | testImplementation 'junit:junit:4.12' 45 | } 46 | -------------------------------------------------------------------------------- /app/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 C:\Users\ruben\AppData\Local\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 | 19 | -keep class android.support.** { *; } 20 | -keep interface android.support.** { *; } 21 | -dontwarn okio.** 22 | 23 | -keepattributes SourceFile,LineNumberTable 24 | -keep class com.parse.*{ *; } 25 | -dontwarn com.parse.** 26 | -dontwarn com.squareup.picasso.** 27 | -keepclasseswithmembernames class * { 28 | native ; 29 | } 30 | 31 | # Platform calls Class.forName on types which do not exist on Android to determine platform. 32 | -dontnote retrofit2.Platform 33 | # Platform used when running on RoboVM on iOS. Will not be used at runtime. 34 | -dontnote retrofit2.Platform$IOS$MainThreadExecutor 35 | # Platform used when running on Java 8 VMs. Will not be used at runtime. 36 | -dontwarn retrofit2.Platform$Java8 37 | # Retain generic type information for use by reflection by converters and adapters. 38 | -keepattributes Signature 39 | # Retain declared checked exceptions for use by a Proxy instance. 40 | -keepattributes Exceptions 41 | 42 | -keepattributes *Annotation* 43 | -keepclassmembers class ** { 44 | @org.greenrobot.eventbus.Subscribe ; 45 | } 46 | -keep enum org.greenrobot.eventbus.ThreadMode { *; } 47 | 48 | # Obfuscation breaks GSON 49 | -dontobfuscate -------------------------------------------------------------------------------- /app/src/androidTest/java/com/rubenwardy/minetestmodmanager/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 41 | 44 | 45 | 50 | 53 | 54 | 58 | 61 | 62 | 67 | 70 | 71 | 75 | 78 | 79 | 83 | 86 | 87 | 88 | 91 | 92 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.content.Context 6 | import android.support.v7.app.AppCompatDelegate 7 | import com.rubenwardy.minetestmodmanager.views.DisclaimerActivity 8 | 9 | class MyApplication : Application() { 10 | override fun onCreate() { 11 | super.onCreate() 12 | 13 | val theme = getSharedPreferences(DisclaimerActivity.PREFS_NAME, 0).getInt("theme", 0) 14 | AppCompatDelegate.setDefaultNightMode(themeToDayNightMode(theme)) 15 | } 16 | } 17 | 18 | fun themeToDayNightMode(themeId: Int): Int { 19 | if (themeId == 0) { 20 | return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM 21 | } else if (themeId == 1) { 22 | return AppCompatDelegate.MODE_NIGHT_NO 23 | } else if (themeId == 2) { 24 | return AppCompatDelegate.MODE_NIGHT_YES 25 | } else { 26 | return AppCompatDelegate.MODE_NIGHT_AUTO 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/manager/BCastReceiver.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.manager; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | public class BCastReceiver extends BroadcastReceiver { 8 | public static final String ACTION_STOP_SERVICE = "stop_service"; 9 | 10 | public BCastReceiver() { 11 | } 12 | 13 | @Override 14 | public void onReceive(Context context, Intent intent) { 15 | switch (intent.getAction()) { 16 | case ACTION_STOP_SERVICE: 17 | ModManager modman = ModManager.getInstance(); 18 | modman.cancelAsyncTask(); 19 | break; 20 | default: 21 | throw new UnsupportedOperationException("Not yet implemented"); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/manager/ServiceResultReceiver.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.manager; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.os.ResultReceiver; 9 | import android.util.Log; 10 | 11 | import com.rubenwardy.minetestmodmanager.models.Events; 12 | import com.rubenwardy.minetestmodmanager.models.ModList; 13 | 14 | import org.greenrobot.eventbus.EventBus; 15 | 16 | /** 17 | * Receive a result from the service 18 | */ 19 | @SuppressLint("ParcelCreator") 20 | class ServiceResultReceiver extends ResultReceiver { 21 | ServiceResultReceiver(Handler handler) { 22 | super(handler); 23 | } 24 | 25 | private void handleInstall(@NonNull Bundle b, @Nullable String modname, @Nullable String listname) { 26 | if (b.containsKey(ModInstallService.RET_ERROR)) { 27 | final String error = b.getString(ModInstallService.RET_ERROR); 28 | EventBus.getDefault().post(new Events.ModInstallEvent(modname, null, listname, error)); 29 | } else { 30 | ModManager modman = ModManager.getInstance(); 31 | ModList list = modman.getModList(listname); 32 | if (list != null) { 33 | list.valid = false; 34 | } 35 | 36 | modman.updateLocalModList(list); 37 | 38 | EventBus.getDefault().post( 39 | new Events.ModInstallEvent(modname, listname + "/" + modname, listname, "")); 40 | } 41 | } 42 | 43 | private void handleUninstall(@NonNull Bundle b, @Nullable String modname, @Nullable String listname) { 44 | if (b.containsKey(ModInstallService.RET_ERROR)) { 45 | final String error = b.getString(ModInstallService.RET_ERROR); 46 | EventBus.getDefault().post(new Events.ModUninstallEvent(modname, null, listname, error)); 47 | } else { 48 | ModManager modman = ModManager.getInstance(); 49 | ModList list = modman.getModList(listname); 50 | if (list != null) { 51 | list.valid = false; 52 | } 53 | 54 | EventBus.getDefault().post( 55 | new Events.ModUninstallEvent(modname, listname + "/" + modname, listname, "")); 56 | } 57 | } 58 | 59 | @Override 60 | protected void onReceiveResult(int resultCode, @NonNull Bundle b) { 61 | String modname = b.getString(ModInstallService.RET_NAME); 62 | String dest = b.getString(ModInstallService.RET_DEST); 63 | String action = b.getString(ModInstallService.RET_ACTION); 64 | if (action == null) { 65 | Log.e("SRR", "Invalid null action"); 66 | return; 67 | } 68 | 69 | switch (action) { 70 | case ModInstallService.ACTION_INSTALL: 71 | handleInstall(b, modname, dest); 72 | break; 73 | case ModInstallService.ACTION_UNINSTALL: 74 | handleUninstall(b, modname, dest); 75 | break; 76 | default: 77 | Log.e("SRR", "Unknown service action"); 78 | break; 79 | } 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/manager/Utils.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.manager; 2 | 3 | import android.support.annotation.CheckResult; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.util.Log; 7 | 8 | import com.rubenwardy.minetestmodmanager.models.Mod; 9 | 10 | import java.io.BufferedInputStream; 11 | import java.io.BufferedReader; 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.FileNotFoundException; 15 | import java.io.FileOutputStream; 16 | import java.io.FileReader; 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.OutputStream; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.zip.ZipEntry; 23 | import java.util.zip.ZipInputStream; 24 | 25 | /** 26 | * Mod utility library 27 | */ 28 | public class Utils { 29 | public interface UnzipFile_Progress 30 | { 31 | void Progress(long done, long total, String FileName); 32 | } 33 | 34 | @CheckResult 35 | public static boolean UnzipFile(@NonNull File zipFile, File targetDirectory, @Nullable UnzipFile_Progress progress) 36 | throws IOException { 37 | long total_len = zipFile.length(); 38 | long total_installed_len = 0; 39 | 40 | ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFile))); 41 | try { 42 | ZipEntry ze; 43 | int count; 44 | byte[] buffer = new byte[8192]; 45 | while ((ze = zis.getNextEntry()) != null) { 46 | if (progress != null) { 47 | total_installed_len += ze.getCompressedSize(); 48 | String file_name = ze.getName(); 49 | progress.Progress(total_installed_len, total_len, file_name); 50 | } 51 | 52 | File file = new File(targetDirectory, ze.getName()); 53 | File dir = ze.isDirectory() ? file : file.getParentFile(); 54 | if (!dir.isDirectory() && !dir.mkdirs()) 55 | throw new FileNotFoundException("Failed to ensure directory: " + dir.getAbsolutePath()); 56 | if (ze.isDirectory()) 57 | continue; 58 | FileOutputStream fout = new FileOutputStream(file); 59 | try { 60 | while ((count = zis.read(buffer)) != -1) 61 | fout.write(buffer, 0, count); 62 | } finally { 63 | fout.close(); 64 | } 65 | } 66 | } catch (Exception e) { 67 | return false; 68 | } finally { 69 | zis.close(); 70 | } 71 | return true; 72 | } 73 | 74 | @Nullable 75 | public static String readTextFile(@Nullable File file) { 76 | if (file == null) { 77 | return null; 78 | } 79 | 80 | try { 81 | StringBuilder text = new StringBuilder(); 82 | BufferedReader br = new BufferedReader(new FileReader(file)); 83 | String line; 84 | while ((line = br.readLine()) != null) { 85 | text.append(line); 86 | text.append('\n'); 87 | } 88 | br.close(); 89 | return text.toString(); 90 | } catch (IOException e) { 91 | e.printStackTrace(); 92 | return null; 93 | } 94 | } 95 | 96 | @Nullable 97 | public static String checkReadTextFile(@Nullable File file) { 98 | if (file != null && file.isFile()) { 99 | String text = Utils.readTextFile(file); 100 | if (text == null) { 101 | return null; 102 | } else { 103 | text = text.trim(); 104 | return text; 105 | } 106 | } else { 107 | return null; 108 | } 109 | } 110 | 111 | @NonNull 112 | public static File getTmpPath(File cachedir, String fileext) { 113 | File file; 114 | int i = 0; 115 | do { 116 | file = new File(cachedir, "tmp" + Integer.toString(i) + fileext); 117 | i++; 118 | } while (file.exists()); 119 | return file; 120 | } 121 | 122 | public static void deleteRecursive(@NonNull File fileOrDir) { 123 | if (fileOrDir.isDirectory()) { 124 | for (File child : fileOrDir.listFiles()) { 125 | deleteRecursive(child); 126 | } 127 | } 128 | if (!fileOrDir.delete()) { 129 | Log.e("utils", "Failed to delete path: " + fileOrDir.getAbsolutePath()); 130 | } 131 | } 132 | 133 | public static boolean copyFolder(@NonNull File src, @NonNull File dest) 134 | throws IOException{ 135 | 136 | if (src.isDirectory()) { 137 | if (!dest.exists()) { 138 | if (!dest.mkdir()) { 139 | return false; 140 | } 141 | } 142 | 143 | for (String file : src.list()) { 144 | File srcFile = new File(src, file); 145 | File destFile = new File(dest, file); 146 | 147 | if (!copyFolder(srcFile,destFile)) 148 | return false; 149 | } 150 | } else { 151 | InputStream in = new FileInputStream(src); 152 | OutputStream out = new FileOutputStream(dest); 153 | byte[] buffer = new byte[1024]; 154 | 155 | int length; 156 | while ((length = in.read(buffer)) > 0){ 157 | out.write(buffer, 0, length); 158 | } 159 | 160 | in.close(); 161 | out.close(); 162 | } 163 | 164 | return true; 165 | } 166 | 167 | @NonNull 168 | public static Mod.ModType detectModType(@NonNull File file) { 169 | if (new File(file.getAbsolutePath(), "init.lua").exists()) { 170 | return Mod.ModType.EMT_MOD; 171 | } else if (new File(file.getAbsolutePath(), "modpack.txt").exists()) { 172 | return Mod.ModType.EMT_MODPACK; 173 | } else { 174 | return Mod.ModType.EMT_INVALID; 175 | } 176 | } 177 | 178 | @Nullable 179 | public static File findRootDir(@NonNull File dir) { 180 | if (!dir.isDirectory()) { 181 | return null; 182 | } 183 | 184 | Mod.ModType type = detectModType(dir); 185 | if (type == Mod.ModType.EMT_INVALID) { 186 | File subdir = null; 187 | for (File child : dir.listFiles()) { 188 | if (child.isDirectory()) { 189 | if (subdir == null) { 190 | subdir = child; 191 | } else { 192 | return null; 193 | } 194 | } 195 | } 196 | if (subdir == null) { 197 | return null; 198 | } else { 199 | return findRootDir(subdir); 200 | } 201 | } else { 202 | return dir; 203 | } 204 | } 205 | 206 | @Nullable 207 | public static File getReadmePath(@NonNull File folder) { 208 | List files = new ArrayList<>(); 209 | files.add(new File(folder, "readme.md")); 210 | files.add(new File(folder, "readme.txt")); 211 | files.add(new File(folder, "README.md")); 212 | files.add(new File(folder, "README.txt")); 213 | for (File file : files) { 214 | if (file.exists() && file.isFile()) { 215 | return file; 216 | } 217 | } 218 | return null; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/models/Events.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.models; 2 | 3 | import android.graphics.drawable.Drawable; 4 | 5 | public class Events { 6 | private static abstract class Event { 7 | public String error = ""; 8 | public boolean didError() { 9 | return !error.isEmpty(); 10 | }; 11 | } 12 | 13 | // 14 | // LIST EVENTS 15 | // 16 | 17 | private static abstract class ListEvent extends Event { 18 | public String list; 19 | } 20 | 21 | public final static class FetchedListEvent extends ListEvent { 22 | public FetchedListEvent(String list, String error) { 23 | this.list = list; 24 | this.error = error; 25 | } 26 | } 27 | 28 | private static abstract class ModEvent extends ListEvent { 29 | public String modname; 30 | public String dest; 31 | 32 | ModEvent(String modname, String dest, String list, String error) { 33 | this.modname = modname; 34 | this.dest = dest; 35 | this.list = list; 36 | this.error = error; 37 | } 38 | } 39 | 40 | public final static class ModInstallEvent extends ModEvent { 41 | public ModInstallEvent(String modname, String dest, String list, String error) { 42 | super(modname, dest, list, error); 43 | } 44 | } 45 | 46 | public final static class ModUninstallEvent extends ModEvent { 47 | public ModUninstallEvent(String modname, String dest, String list, String error) { 48 | super(modname, dest, list, error); 49 | } 50 | } 51 | 52 | // 53 | // OTHER EVENTS 54 | // 55 | 56 | public final static class SearchEvent { 57 | public String query; 58 | public SearchEvent(String query) { 59 | this.query = query; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/models/Game.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.models; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public class Game { 13 | public final String name; 14 | private final File file; 15 | 16 | private final Map lists = new HashMap<>(); 17 | 18 | public Game(@NonNull String name, @NonNull File file) { 19 | this.name = name; 20 | 21 | this.file = file; 22 | } 23 | 24 | public String getPath() { 25 | return file.getAbsolutePath(); 26 | } 27 | 28 | public ModList getList(String path) { 29 | return lists.get(new File(path).getAbsolutePath()); 30 | } 31 | 32 | public Mod getMod(String name, String author) { 33 | for (ModList list : lists.values()) { 34 | Mod mod = list.get(name, author); 35 | if (mod != null) { 36 | return mod; 37 | } 38 | } 39 | 40 | return null; 41 | } 42 | 43 | public boolean isValid() { 44 | return file.isDirectory() && new File(file, "mods").isDirectory(); 45 | } 46 | 47 | public boolean isLoaded() { 48 | return new File(file, "games").isDirectory(); 49 | } 50 | 51 | public boolean hasWorld() { 52 | return new File(file, "worlds/world").isDirectory(); 53 | } 54 | 55 | @SuppressWarnings("ResultOfMethodCallIgnored") 56 | public void forceCreate() { 57 | new File(file, "mods").mkdirs(); 58 | new File(file, "worlds").mkdirs(); 59 | } 60 | 61 | public List getModPaths() { 62 | List paths = new ArrayList<>(); 63 | paths.add(new ModDir(new File(file, "mods").getAbsolutePath(), ModList.ModListType.EMLT_MODS)); 64 | paths.add(new ModDir(new File(file, "games/minetest_game/mods").getAbsolutePath(), ModList.ModListType.EMLT_GAME_MODS)); 65 | return paths; 66 | } 67 | 68 | public List getAllModLists() { 69 | List ret = new ArrayList<>(); 70 | ret.addAll(lists.values()); 71 | return ret; 72 | } 73 | 74 | public Map getAllMods() { 75 | Map map = new HashMap<>(); 76 | 77 | for (ModList list : lists.values()) { 78 | for (Mod mod : list.mods) { 79 | map.put(mod.name, mod); 80 | } 81 | } 82 | 83 | return map; 84 | } 85 | 86 | public void addList(String path, ModList list) { 87 | lists.put(path, list); 88 | } 89 | 90 | public boolean hasPath(String path) { 91 | String root; 92 | try { 93 | root = file.getCanonicalPath(); 94 | } catch (IOException e) { 95 | root = file.getAbsolutePath(); 96 | } 97 | 98 | File file2 = new File(path); 99 | try { 100 | path = file2.getCanonicalPath(); 101 | } catch (IOException e) { 102 | path = file2.getAbsolutePath(); 103 | } 104 | 105 | return path.startsWith(root); 106 | } 107 | 108 | public static boolean isMTGMod(String mod_name) { 109 | String[] mods = { 110 | "beds", 111 | "boats", 112 | "bones", 113 | "bucket", 114 | "carts", 115 | "creative", 116 | "default", 117 | "doors", 118 | "dye", 119 | "farming", 120 | "fire", 121 | "flowers", 122 | "give_initial_stuff", 123 | "killme", 124 | "screwdriver", 125 | "sethome", 126 | "sfinv", 127 | "stairs", 128 | "tnt", 129 | "vessels", 130 | "walls", 131 | "wool", 132 | "xpanes" 133 | }; 134 | 135 | for (String mod : mods) { 136 | if (mod.equals(mod_name)) { 137 | return true; 138 | } 139 | } 140 | 141 | return false; 142 | } 143 | 144 | public class ModDir { 145 | public String path; 146 | public ModList.ModListType type; 147 | 148 | ModDir(String absolutePath, ModList.ModListType emltGameMods) { 149 | this.path = absolutePath; 150 | this.type = emltGameMods; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/models/MinetestConf.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.models; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.util.Log; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.BufferedWriter; 9 | import java.io.File; 10 | import java.io.FileReader; 11 | import java.io.FileWriter; 12 | import java.io.IOException; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * Reads and writes Minetest style configurations 18 | **/ 19 | public class MinetestConf { 20 | private final Map settings = new HashMap<>(); 21 | 22 | public boolean read(File file) { 23 | if (!file.isFile()) { 24 | Log.e("Conf", "Configfile is not a file! " + file.getAbsolutePath()); 25 | return false; 26 | } 27 | try { 28 | BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); 29 | String line; 30 | 31 | while ((line = bufferedReader.readLine()) != null) { 32 | int idx = line.indexOf("="); 33 | if (idx >= 0) { 34 | String key = line.substring(0, idx).trim(); 35 | String value = line.substring(idx + 1, line.length()).trim(); 36 | settings.put(key, value); 37 | } 38 | 39 | } 40 | bufferedReader.close(); 41 | return true; 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | return false; 45 | } 46 | } 47 | 48 | public boolean save(File file) { 49 | try { 50 | BufferedWriter writer = new BufferedWriter(new FileWriter(file)); 51 | for (Map.Entry pair : settings.entrySet()) { 52 | writer.write(pair.getKey() + " = " + pair.getValue() + "\n"); 53 | } 54 | writer.close(); 55 | 56 | return true; 57 | } catch (IOException e) { 58 | e.printStackTrace(); 59 | return false; 60 | } 61 | } 62 | 63 | public static boolean isYes(String v) { 64 | v = v.trim(); 65 | return (v.equals("true") || v.equals("1") || v.equals("yes")); 66 | } 67 | 68 | public String get(@NonNull String key) { 69 | return settings.get(key); 70 | } 71 | 72 | public boolean getBool(@NonNull String key) { 73 | String value = get(key); 74 | return (value != null) && isYes(value); 75 | } 76 | 77 | public void set(@NonNull String key, @Nullable String value) { 78 | if (value == null) { 79 | value = ""; 80 | } 81 | settings.put(key, value); 82 | } 83 | 84 | public void setBool(@NonNull String key, boolean value) { 85 | set(key, value?"true":"false"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/models/MinetestDepends.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.models; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.util.Log; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.BufferedWriter; 8 | import java.io.File; 9 | import java.io.FileReader; 10 | import java.io.FileWriter; 11 | import java.io.IOException; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class MinetestDepends { 16 | public final List hard = new ArrayList<>(); 17 | public final List soft = new ArrayList<>(); 18 | 19 | public boolean read(@NonNull File file) { 20 | if (!file.isFile()) { 21 | Log.e("Depends", "depfle is not a file! " + file.getAbsolutePath()); 22 | return false; 23 | } 24 | 25 | try { 26 | BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); 27 | String line; 28 | 29 | while ((line = bufferedReader.readLine()) != null) { 30 | line = line.trim(); 31 | if (!line.isEmpty()) { 32 | if (line.endsWith("?")) { 33 | Log.e("Depends", "Soft: " + line.substring(0, line.length() - 1)); 34 | soft.add(line.substring(0, line.length() - 1)); 35 | } else { 36 | Log.e("Depends", "Hard: " + line); 37 | hard.add(line); 38 | } 39 | } 40 | } 41 | bufferedReader.close(); 42 | return true; 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | return false; 46 | } 47 | } 48 | 49 | public boolean save(File file) { 50 | try { 51 | BufferedWriter writer = new BufferedWriter(new FileWriter(file)); 52 | for (String dep : hard) { 53 | writer.write(dep + "\n"); 54 | } 55 | for (String dep : soft) { 56 | writer.write(dep + "?\n"); 57 | } 58 | writer.close(); 59 | 60 | return true; 61 | } catch (IOException e) { 62 | e.printStackTrace(); 63 | return false; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/models/Mod.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.models; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import com.rubenwardy.minetestmodmanager.manager.ModManager; 7 | 8 | /** 9 | * Represents a mod, installed or not. 10 | */ 11 | public class Mod extends ModSpec { 12 | public enum ModType { 13 | EMT_INVALID, 14 | EMT_MOD, 15 | EMT_MODPACK, 16 | EMT_SUBGAME 17 | } 18 | 19 | @NonNull public final ModType type; 20 | 21 | @Nullable public final String title; 22 | @NonNull public final String desc; 23 | 24 | @NonNull public String link; 25 | @Nullable public String path; 26 | @Nullable public String thumbnail_url; 27 | @Nullable public String screenshot_uri; 28 | @Nullable public String forum_url; 29 | public int verified; 30 | public int size; 31 | 32 | public Mod(@NonNull ModType type, @Nullable String listname, @NonNull String name, 33 | @Nullable String title, @NonNull String desc) { 34 | super(name, "", listname); 35 | 36 | this.type = type; 37 | this.title = title; 38 | this.desc = desc; 39 | this.link = ""; 40 | this.path = ""; 41 | this.screenshot_uri = ""; 42 | this.thumbnail_url = null; 43 | this.verified = 0; 44 | this.size = -1; 45 | } 46 | 47 | public boolean isLocalMod() { 48 | return path != null && !path.equals(""); 49 | } 50 | 51 | @NonNull 52 | public String getShortDesc() { 53 | String cleaned_desc = desc.trim().replace("\n", " "); 54 | int len = cleaned_desc.indexOf(".", 20) + 1; 55 | int slen = cleaned_desc.length(); 56 | len = Math.min(len, slen); 57 | if (len < 20) { 58 | len = slen; 59 | } 60 | 61 | if (len > 100) { 62 | String short_desc = cleaned_desc.substring(0, 99); 63 | char c = short_desc.charAt(short_desc.length() - 1); 64 | while (!(Character.isDigit(c) || Character.isLetter(c))) { 65 | short_desc = short_desc.substring(0, short_desc.length() - 1); 66 | c = short_desc.charAt(short_desc.length() - 1); 67 | } 68 | return short_desc + "…"; 69 | } else { 70 | return cleaned_desc.substring(0, len); 71 | } 72 | } 73 | 74 | @NonNull 75 | public String getShortLink() { 76 | String res = (link == null) ? "" : link.replace("https://", "").replace("http://", ""); 77 | if (res.length() > 100) { 78 | res = res.substring(0, 99) + "…"; 79 | } 80 | return res; 81 | } 82 | 83 | @NonNull 84 | public String getShortForumLink() { 85 | String res = (forum_url == null) ? "" : forum_url.replace("https://", "").replace("http://", ""); 86 | if (res.length() > 100) { 87 | res = res.substring(0, 99) + "…"; 88 | } 89 | return res; 90 | } 91 | 92 | @Nullable 93 | public String getDownloadSize() { 94 | if (size > 1000000) { 95 | double size2 = Math.round(size / 100000.0) / 10.0; 96 | return size2 + " MB"; 97 | } else if (size > 500) { 98 | double size2 = Math.round(size / 100.0) / 10.0; 99 | return size2 + " KB"; 100 | } else if (size > 0) { 101 | return size + " B"; 102 | } else { 103 | return null; 104 | } 105 | } 106 | 107 | @Override 108 | @NonNull 109 | public String toString() { 110 | return this.name; 111 | } 112 | 113 | public boolean isEnabled(@NonNull MinetestConf conf) { 114 | if (type == ModType.EMT_MOD) { 115 | return conf.getBool("load_mod_" + name); 116 | } else if (type == ModType.EMT_MODPACK) { 117 | assert (path != null); 118 | ModList sublist = new ModList(ModList.ModListType.EMLT_MODS, "", null, path); 119 | ModManager modman = ModManager.getInstance(); 120 | modman.updatePathModList(sublist); 121 | for (Mod submod : sublist.mods) { 122 | if (!submod.isEnabled(conf)) { 123 | return false; 124 | } 125 | } 126 | return true; 127 | } else { 128 | return false; 129 | } 130 | } 131 | 132 | public void setEnabled(@NonNull MinetestConf conf, boolean enable) { 133 | if (type == ModType.EMT_MOD) { 134 | conf.setBool("load_mod_" + name, enable); 135 | } else if (type == ModType.EMT_MODPACK) { 136 | assert (path != null); 137 | ModList sublist = new ModList(ModList.ModListType.EMLT_MODS, "", "", path); 138 | ModManager modman = ModManager.getInstance(); 139 | modman.updatePathModList(sublist); 140 | for (Mod submod : sublist.mods) { 141 | submod.setEnabled(conf, enable); 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/models/ModList.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.models; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.util.Log; 6 | 7 | import java.io.File; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * A list of mods. May represent a folder in the file system or 15 | */ 16 | public class ModList { 17 | public enum ModListType { 18 | EMLT_ONLINE, 19 | EMLT_MODS, 20 | EMLT_GAME_MODS; 21 | 22 | public boolean isLocal() { 23 | return this == EMLT_MODS || this == EMLT_GAME_MODS; 24 | } 25 | } 26 | 27 | @Nullable public final String game_name; 28 | @NonNull public final String listname; 29 | @Nullable public final String engine_root; 30 | @NonNull public final ModListType type; 31 | public boolean valid; 32 | @NonNull public List mods = new ArrayList<>(); 33 | 34 | @NonNull 35 | public Map> mods_map = new HashMap<>(); 36 | 37 | public ModList(@NonNull ModListType type, @Nullable String game_name, @Nullable String engine_root, 38 | @NonNull String listname) { 39 | this.type = type; 40 | this.game_name = game_name; 41 | this.engine_root = engine_root; 42 | this.listname = listname; 43 | this.valid = true; 44 | } 45 | 46 | @NonNull 47 | public String getShortname() { 48 | // TODO: make this less hacky 49 | return listname.replace("/storage/emulated/0/", ""); 50 | } 51 | 52 | public void add(@NonNull Mod mod) { 53 | mods.add(mod); 54 | if (mods_map.containsKey(mod.name)) { 55 | List tmp = mods_map.get(mod.name); 56 | tmp.add(mod); 57 | } else { 58 | List tmp = new ArrayList<>(); 59 | tmp.add(mod); 60 | mods_map.put(mod.name, tmp); 61 | } 62 | } 63 | 64 | @Nullable 65 | public String getWorldsDir() { 66 | if (engine_root == null) { 67 | return null; 68 | } else { 69 | return (new File(engine_root, "worlds")).getAbsolutePath(); 70 | } 71 | } 72 | 73 | @Nullable 74 | public Mod get(@Nullable String name, @Nullable String author) { 75 | if (name == null) { 76 | return null; 77 | } 78 | if (author != null) { 79 | author = author.trim(); 80 | if (author.equals("")) { 81 | author = null; 82 | } 83 | } 84 | 85 | List res = mods_map.get(name); 86 | if (res != null && res.size() > 0) { 87 | if (author == null) { 88 | if (res.size() > 1) { 89 | Log.e("ModList", 90 | "getModList() called without author, yet there are multiple mods of that name."); 91 | } 92 | return res.get(0); 93 | } 94 | for (Mod mod : res) { 95 | if (mod.author.equals(author)) { 96 | return mod; 97 | } 98 | } 99 | } 100 | return null; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/models/ModSpec.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.models; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | public class ModSpec { 7 | @NonNull public final String name; 8 | @NonNull public String author; 9 | @Nullable public String listname; 10 | 11 | public ModSpec(@NonNull String name, @NonNull String author, @Nullable String listname) { 12 | this.name = name; 13 | this.author = author; 14 | this.listname = listname; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/presenters/DisclaimerPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.presenters 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import com.rubenwardy.minetestmodmanager.manager.ModManager 6 | import com.rubenwardy.minetestmodmanager.models.ModSpec 7 | 8 | class DisclaimerPresenter(val view: View) { 9 | 10 | fun onAcceptClicked(context: Context) { 11 | view.setAgreedToDisclaimer() 12 | 13 | 14 | val modspec = view.getModInfo() 15 | if (modspec.listname != null && !modspec.name.isEmpty()) { 16 | val modman = ModManager.getInstance() 17 | val list = modman.getModList(modspec.listname) 18 | if (list != null) { 19 | val mod = list.get(modspec.name, modspec.author) 20 | if (mod != null && !mod.link.isEmpty()) { 21 | modman.installUrlModAsync(context, mod, 22 | mod.link, 23 | modman.installDir) 24 | } else { 25 | Log.e("DAct", "Unable to find an installable mod of that name! " + modspec.name) 26 | } 27 | } else { 28 | Log.e("DAct", "Unable to find a ModList of that id! " + modspec.listname) 29 | } 30 | } 31 | 32 | view.finishActivity() 33 | } 34 | 35 | interface View { 36 | fun setAgreedToDisclaimer() 37 | fun finishActivity() 38 | fun getModInfo() : ModSpec 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/presenters/ModListPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.presenters 2 | 3 | import android.os.Environment 4 | import android.util.Log 5 | import com.rubenwardy.minetestmodmanager.manager.ModManager 6 | import com.rubenwardy.minetestmodmanager.models.Events 7 | import com.rubenwardy.minetestmodmanager.models.Game 8 | import org.greenrobot.eventbus.Subscribe 9 | import java.io.File 10 | 11 | class ModListPresenter(val view: View) { 12 | fun scanFileSystem() { 13 | val extern = Environment.getExternalStorageDirectory() 14 | if (!extern.exists()) { 15 | view.showNoExternalFSDialog() 16 | return 17 | } 18 | 19 | val minetest = Game("Minetest", File(extern, "Minetest")) 20 | val multicraft = Game("Multicraft", File(extern, "MultiCraft")) 21 | 22 | if (!minetest.isValid && !multicraft.isValid) { 23 | view.getIsMinetestInstalled() 24 | 25 | view.showMinetestNotInstalledDialog() 26 | 27 | minetest.forceCreate() 28 | } 29 | 30 | if (!minetest.isValid && !multicraft.isValid) { 31 | view.showNoGameAvailable() 32 | return 33 | } 34 | 35 | val modman = ModManager.getInstance() 36 | modman.registerGame(minetest) 37 | modman.fetchModListAsync() 38 | } 39 | 40 | fun forceModListRefresh() { 41 | val modman = ModManager.getInstance() 42 | for (list in ModManager.getInstance().allModLists) { 43 | if (list.type.isLocal) { 44 | modman.updateLocalModList(list) 45 | } 46 | } 47 | modman.fetchModListAsync() 48 | } 49 | 50 | @Subscribe 51 | fun onModInstall(e: Events.ModInstallEvent) { 52 | if (e.didError()) { 53 | view.showModInstallErrorDialog(e.modname, e.error); 54 | return 55 | } else { 56 | val modman = ModManager.getInstance() 57 | val uninstalled = modman.getMissingDependsForMod(modman.getModList(e.list)!!.get(e.modname, null)!!) 58 | for (a in uninstalled) { 59 | Log.e("MDAct", "Mod not installed: " + a) 60 | } 61 | 62 | if (!uninstalled.isEmpty()) { 63 | view.showInstallsDependsDialog(uninstalled) 64 | } 65 | 66 | view.showModOnlyIfTwoPane(e.list, e.modname); 67 | view.showInstallMessage(e.modname) 68 | } 69 | 70 | view.updateModListAndRecyclerView(e.list) 71 | } 72 | 73 | 74 | interface View { 75 | fun showNoExternalFSDialog() 76 | fun showMinetestNotInstalledDialog() 77 | fun showNoGameAvailable() 78 | fun getIsMinetestInstalled(): Boolean 79 | fun showModInstallErrorDialog(modname: String, error: String) 80 | fun showInstallsDependsDialog(uninstalled: List) 81 | fun updateModListAndRecyclerView(list: String) 82 | fun showInstallMessage(modname: String) 83 | fun showModOnlyIfTwoPane(list: String, modname: String) 84 | } 85 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/restapi/StoreAPI.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.restapi; 2 | 3 | import android.support.annotation.Nullable; 4 | import android.util.Log; 5 | 6 | import com.rubenwardy.minetestmodmanager.models.Mod; 7 | import com.rubenwardy.minetestmodmanager.models.ModList; 8 | 9 | import java.util.List; 10 | 11 | import okhttp3.ResponseBody; 12 | import retrofit2.Call; 13 | import retrofit2.http.Body; 14 | import retrofit2.http.Field; 15 | import retrofit2.http.FormUrlEncoded; 16 | import retrofit2.http.GET; 17 | import retrofit2.http.POST; 18 | 19 | public interface StoreAPI { 20 | @GET("v2/list") 21 | Call> getModList(); 22 | 23 | @FormUrlEncoded 24 | @POST("v1/report") 25 | Call sendReport(@Field("modname") String modname, 26 | @Field("msg") String msg, 27 | @Field("reason") String reason, 28 | @Field("author") String author, 29 | @Field("list") String list, 30 | @Field("link") String link); 31 | 32 | @FormUrlEncoded 33 | @POST("v1/on-download") 34 | Call sendDownloadReport(@Field("modname") String modname, 35 | @Field("link") String link, 36 | @Field("size") int size, 37 | @Field("status") int status, 38 | @Field("author") String author, 39 | @Field("error") String error); 40 | 41 | class MissingModReport { 42 | public List mods; 43 | public String required_by; 44 | 45 | public MissingModReport(List mods, String required_by) { 46 | this.mods = mods; 47 | this.required_by = required_by; 48 | } 49 | } 50 | 51 | @POST("v2/on-missing-dep") 52 | Call sendMissingDependsReport(@Body MissingModReport info); 53 | 54 | class RestMod { 55 | public String author; 56 | public String type; 57 | public String basename; 58 | public String title; 59 | public String description; 60 | public String forum_url; 61 | public String download_link; 62 | public String thumbnail; 63 | 64 | @Nullable 65 | Mod toMod(final String modstore_url) { 66 | String modname = this.basename; 67 | String title = this.title; 68 | String link = this.download_link; 69 | 70 | if (modname == null || title == null || null == link) { 71 | return null; 72 | } 73 | 74 | String author = this.author; 75 | String type_s = this.type; 76 | 77 | String desc = ""; 78 | if (this.description != null) { 79 | desc = this.description; 80 | } 81 | 82 | String forum = null; 83 | if (this.forum_url != null) { 84 | forum = this.forum_url; 85 | } 86 | 87 | Mod.ModType type = Mod.ModType.EMT_MOD; 88 | if (type_s != null) { 89 | if (type_s.equals("1")) { 90 | type = Mod.ModType.EMT_MOD; 91 | } else if (type_s.equals("2")) { 92 | type = Mod.ModType.EMT_MODPACK; 93 | } 94 | } 95 | 96 | Mod mod = new Mod(type, modstore_url, modname, title, desc); 97 | mod.link = link; 98 | mod.author = author; 99 | mod.forum_url = forum; 100 | mod.size = 0; 101 | mod.thumbnail_url = thumbnail; 102 | return mod; 103 | } 104 | 105 | public static void addAllToList(ModList list, List mods, String modstore_url) { 106 | for (StoreAPI.RestMod rmod : mods) { 107 | Mod mod = rmod.toMod(modstore_url); 108 | if (mod == null) { 109 | Log.e("RestMod", "Invalid object in list"); 110 | } else { 111 | list.add(mod); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/restapi/StoreAPIBuilder.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.restapi; 2 | 3 | import com.google.gson.GsonBuilder; 4 | 5 | import retrofit2.Retrofit; 6 | import retrofit2.converter.gson.GsonConverterFactory; 7 | 8 | public class StoreAPIBuilder { 9 | public static final String API_BASE_URL = "https://minetest-mods.rubenwardy.com/"; 10 | 11 | private static Retrofit.Builder createBaseBuilder() { 12 | GsonBuilder gsonBuilder = new GsonBuilder(); 13 | 14 | return new Retrofit.Builder() 15 | .baseUrl(API_BASE_URL) 16 | .addConverterFactory(GsonConverterFactory.create(gsonBuilder.create())); 17 | } 18 | 19 | public static StoreAPI createService() { 20 | return createBaseBuilder().build().create(StoreAPI.class); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/views/DisclaimerActivity.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.views; 2 | 3 | import android.content.SharedPreferences; 4 | import android.content.res.Resources; 5 | import android.support.annotation.NonNull; 6 | import android.support.v7.app.ActionBar; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.os.Bundle; 9 | import android.view.View; 10 | import android.widget.Button; 11 | 12 | import com.rubenwardy.minetestmodmanager.R; 13 | import com.rubenwardy.minetestmodmanager.models.ModSpec; 14 | import com.rubenwardy.minetestmodmanager.presenters.DisclaimerPresenter; 15 | 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | public class DisclaimerActivity extends AppCompatActivity implements DisclaimerPresenter.View { 19 | public static final String PREFS_NAME = "com.rubenwardy.minetestmodmanager.PREFS"; 20 | 21 | DisclaimerPresenter presenter = new DisclaimerPresenter(this); 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_disclaimer); 27 | 28 | // Show the Up button in the action bar. 29 | ActionBar actionBar = getSupportActionBar(); 30 | if (actionBar != null) { 31 | actionBar.setDisplayHomeAsUpEnabled(true); 32 | Resources res = getResources(); 33 | actionBar.setTitle(res.getString(R.string.disclaimer_title)); 34 | } 35 | 36 | Button button = (Button) findViewById(R.id.accept); 37 | button.setOnClickListener(new View.OnClickListener() { 38 | @Override 39 | public void onClick(@NonNull View v) { 40 | presenter.onAcceptClicked(getApplicationContext()); 41 | } 42 | }); 43 | } 44 | 45 | @Override 46 | public void setAgreedToDisclaimer() { 47 | SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); 48 | SharedPreferences.Editor editor = settings.edit(); 49 | editor.putBoolean("agreed_to_disclaimer", true); 50 | editor.apply(); 51 | } 52 | 53 | @Override 54 | public void finishActivity() { 55 | finish(); 56 | } 57 | 58 | @NotNull 59 | @Override 60 | public ModSpec getModInfo() { 61 | final String listname = getIntent().getStringExtra(ModDetailFragment.ARG_MOD_LIST); 62 | final String modname = getIntent().getStringExtra(ModDetailFragment.ARG_MOD_NAME); 63 | final String author = getIntent().getStringExtra(ModDetailFragment.ARG_MOD_AUTHOR); 64 | return new ModSpec(modname, author, listname); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/views/ModInfoDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.views; 2 | 3 | import android.app.Dialog; 4 | import android.content.DialogInterface; 5 | import android.content.res.Resources; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.v4.app.DialogFragment; 9 | import android.support.v7.app.AlertDialog; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.widget.TextView; 13 | 14 | import com.rubenwardy.minetestmodmanager.R; 15 | import com.rubenwardy.minetestmodmanager.manager.ModManager; 16 | import com.rubenwardy.minetestmodmanager.models.Mod; 17 | import com.rubenwardy.minetestmodmanager.models.ModList; 18 | 19 | public class ModInfoDialogFragment extends DialogFragment { 20 | private Mod mod; 21 | 22 | @NonNull 23 | @Override 24 | public Dialog onCreateDialog(Bundle savedInstanceState) { 25 | if (savedInstanceState == null) { 26 | if (getArguments().containsKey(ModDetailFragment.ARG_MOD_NAME) && 27 | getArguments().containsKey(ModDetailFragment.ARG_MOD_LIST)) { 28 | String name = getArguments().getString(ModDetailFragment.ARG_MOD_NAME); 29 | String author = getArguments().getString(ModDetailFragment.ARG_MOD_AUTHOR); 30 | String listname = getArguments().getString(ModDetailFragment.ARG_MOD_LIST); 31 | ModManager modman = ModManager.getInstance(); 32 | 33 | ModList list = modman.getModList(listname); 34 | if (list == null) { 35 | Resources res = getResources(); 36 | mod = new Mod(Mod.ModType.EMT_INVALID, 37 | "", "invalid", 38 | res.getString(R.string.mod_invalid_modlist_title), 39 | listname + ": " + res.getString(R.string.mod_invalid_modlist_desc)); 40 | } else { 41 | mod = list.get(name, author); 42 | if (mod == null) { 43 | Resources res = getResources(); 44 | list.valid = false; 45 | mod = new Mod(Mod.ModType.EMT_INVALID, 46 | "", "invalid", 47 | res.getString(R.string.mod_invalid_mod_title), 48 | author + "/" + name + ": " + res.getString(R.string.mod_invalid_mod_desc)); 49 | } 50 | } 51 | } 52 | } 53 | 54 | 55 | // Use the Builder class for convenient dialog construction 56 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 57 | LayoutInflater inflater = getActivity().getLayoutInflater(); 58 | View rootView = inflater.inflate(R.layout.dialog_mod_information, null); 59 | 60 | setupView(rootView); 61 | 62 | builder.setMessage(R.string.modinfo_title) 63 | .setPositiveButton(R.string.dialog_close, new DialogInterface.OnClickListener() { 64 | public void onClick(DialogInterface dialog, int id) { } 65 | }) 66 | .setView(rootView); 67 | 68 | // Create the AlertDialog object and return it 69 | return builder.create(); 70 | } 71 | 72 | public void setupView(View rootView) { 73 | Resources res = getResources(); 74 | 75 | // Mod name 76 | ((TextView) rootView.findViewById(R.id.mod_detail_name)).setText(mod.name); 77 | 78 | // Type 79 | String type; 80 | if (mod.type == Mod.ModType.EMT_MOD) { 81 | type = res.getString(R.string.modinfo_details_type_mod); 82 | } else if (mod.type == Mod.ModType.EMT_MODPACK) { 83 | type = res.getString(R.string.modinfo_details_type_modpack); 84 | } else if (mod.type == Mod.ModType.EMT_SUBGAME) { 85 | type = res.getString(R.string.modinfo_details_type_subgame); 86 | } else { 87 | type = "Invalid"; 88 | } 89 | ((TextView) rootView.findViewById(R.id.mod_detail_type)).setText(type); 90 | 91 | // Location 92 | if (mod.isLocalMod()) { 93 | ((TextView) rootView.findViewById(R.id.mod_detail_location)).setText(mod.path); 94 | } else { 95 | rootView.findViewById(R.id.mod_detail_loc_row).setVisibility(View.GONE); 96 | } 97 | 98 | // Download link and size 99 | if (mod.isLocalMod()) { 100 | rootView.findViewById(R.id.mod_detail_link_row).setVisibility(View.GONE); 101 | rootView.findViewById(R.id.mod_detail_size_row).setVisibility(View.GONE); 102 | } else { 103 | ((TextView) rootView.findViewById(R.id.mod_detail_link)).setText(mod.getShortLink()); 104 | String dlsize = mod.getDownloadSize(); 105 | if (dlsize == null) { 106 | dlsize = res.getString(R.string.modinfo_details_size_unknown); 107 | } 108 | ((TextView) rootView.findViewById(R.id.mod_detail_size)).setText(dlsize); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/views/ReadmeActivity.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.views; 2 | 3 | import android.content.res.Resources; 4 | import android.support.v7.app.ActionBar; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | import android.view.MenuItem; 8 | import android.widget.TextView; 9 | 10 | import com.rubenwardy.minetestmodmanager.R; 11 | import com.rubenwardy.minetestmodmanager.manager.Utils; 12 | 13 | import java.io.File; 14 | 15 | public class ReadmeActivity extends AppCompatActivity { 16 | public static final String ARG_MOD_PATH = "path"; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_readme); 22 | 23 | ActionBar actionBar = getSupportActionBar(); 24 | if (actionBar != null) { 25 | actionBar.setDisplayHomeAsUpEnabled(true); 26 | Resources res = getResources(); 27 | actionBar.setTitle(res.getString(R.string.readme_title)); 28 | } 29 | 30 | TextView text = (TextView) findViewById(R.id.text); 31 | assert text != null; 32 | 33 | String path = getIntent().getStringExtra(ARG_MOD_PATH); 34 | File folder = new File(path); 35 | if (!folder.isDirectory()) { 36 | text.setText("Unable to find dir " + path); 37 | return; 38 | } 39 | 40 | File readme = Utils.getReadmePath(folder); 41 | if (readme == null) { 42 | text.setText("Unable to find any valid readme files"); 43 | } else { 44 | text.setText(Utils.readTextFile(readme)); 45 | } 46 | 47 | } 48 | 49 | @Override 50 | public boolean onOptionsItemSelected(MenuItem item) { 51 | switch (item.getItemId()) { 52 | case android.R.id.home: 53 | finish(); 54 | return true; 55 | } 56 | return super.onOptionsItemSelected(item); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/views/ReportActivity.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.views; 2 | 3 | import android.content.res.Resources; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.support.design.widget.Snackbar; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.Toolbar; 10 | import android.util.Log; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | import android.view.View; 14 | import android.widget.AdapterView; 15 | import android.widget.ArrayAdapter; 16 | import android.widget.Button; 17 | import android.widget.EditText; 18 | import android.widget.Spinner; 19 | import android.widget.TextView; 20 | 21 | import com.rubenwardy.minetestmodmanager.R; 22 | import com.rubenwardy.minetestmodmanager.manager.ModManager; 23 | 24 | public class ReportActivity extends AppCompatActivity { 25 | public static final String EXTRA_LIST = "list"; 26 | public static final String EXTRA_MOD_NAME = "modname"; 27 | public static final String EXTRA_AUTHOR = "author"; 28 | public static final String EXTRA_LINK = "link"; 29 | private String selected = "mal"; 30 | 31 | private String listname; 32 | private String link; 33 | private String author; 34 | private String modname; 35 | 36 | private @NonNull String str_make_nonnull(@Nullable String str) { 37 | if (str == null) { 38 | return ""; 39 | } else { 40 | return str; 41 | } 42 | } 43 | 44 | 45 | @Override 46 | protected void onCreate(Bundle savedInstanceState) { 47 | super.onCreate(savedInstanceState); 48 | setContentView(R.layout.activity_report); 49 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 50 | setSupportActionBar(toolbar); 51 | if (getSupportActionBar() != null) { 52 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 53 | } 54 | 55 | listname = str_make_nonnull(getIntent().getStringExtra(EXTRA_LIST)); 56 | link = str_make_nonnull(getIntent().getStringExtra(EXTRA_LINK)); 57 | author = str_make_nonnull(getIntent().getStringExtra(EXTRA_AUTHOR)); 58 | modname = str_make_nonnull(getIntent().getStringExtra(EXTRA_MOD_NAME)); 59 | 60 | Resources res = getResources(); 61 | 62 | TextView tv = (TextView) findViewById(R.id.mod_details); 63 | String details = String.format(res.getString(R.string.report_modname_by_authorname), modname, author); 64 | details += "\n" + link; 65 | details += "\n" + listname; 66 | tv.setText(details); 67 | 68 | final Spinner spinner = (Spinner) findViewById(R.id.spinner); 69 | ArrayAdapter adapter = ArrayAdapter.createFromResource(this, 70 | R.array.report_reasons, android.R.layout.simple_spinner_item); 71 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 72 | spinner.setAdapter(adapter); 73 | spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 74 | @Override 75 | public void onItemSelected(AdapterView parent, View view, int position, long id) { 76 | switch (position) { 77 | case 0: 78 | selected = "mal"; 79 | break; 80 | case 1: 81 | selected = "dw"; 82 | break; 83 | case 2: 84 | default: 85 | selected = "other"; 86 | break; 87 | } 88 | 89 | Log.w("RAct", selected); 90 | } 91 | 92 | @Override 93 | public void onNothingSelected(AdapterView parent) { 94 | 95 | } 96 | }); 97 | } 98 | 99 | @Override 100 | public boolean onCreateOptionsMenu(Menu menu) { 101 | getMenuInflater().inflate(R.menu.menu_report, menu); 102 | 103 | return true; 104 | } 105 | 106 | @Override 107 | public boolean onOptionsItemSelected(MenuItem item) { 108 | switch (item.getItemId()) { 109 | case R.id.send: 110 | EditText textbox = (EditText) findViewById(R.id.editText); 111 | String info = str_make_nonnull(textbox.getText().toString()); 112 | 113 | ModManager modman = ModManager.getInstance(); 114 | modman.reportModAsync(modname, author, listname, link, selected, info); 115 | finish(); 116 | return true; 117 | } 118 | 119 | return false; 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/views/SettingsAndAboutActivity.kt: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.views 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.view.View 6 | import android.widget.AdapterView 7 | import android.widget.ArrayAdapter 8 | import android.widget.Spinner 9 | 10 | import com.rubenwardy.minetestmodmanager.R 11 | import com.rubenwardy.minetestmodmanager.themeToDayNightMode 12 | import android.content.Intent 13 | import android.app.AlarmManager 14 | import android.app.PendingIntent 15 | import android.content.Context 16 | import android.net.Uri 17 | import android.util.Log 18 | import android.widget.Switch 19 | 20 | 21 | class SettingsAndAboutActivity : AppCompatActivity() { 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | setContentView(R.layout.activity_settingsandabout) 26 | 27 | if (supportActionBar != null) { 28 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) 29 | } 30 | 31 | val settings = getSharedPreferences(DisclaimerActivity.PREFS_NAME, 0) 32 | 33 | val previewScreens = findViewById(R.id.settings_screenshot_previews) as Switch 34 | previewScreens.isChecked = settings.getBoolean("showScreenshotPreviews", true) 35 | previewScreens.setOnCheckedChangeListener { _, isChecked -> 36 | val editor = getSharedPreferences(DisclaimerActivity.PREFS_NAME, 0).edit() 37 | editor.putBoolean("showScreenshotPreviews", isChecked) 38 | editor.apply() 39 | } 40 | 41 | val spinner = findViewById(R.id.settings_theme) 42 | val adapter = ArrayAdapter.createFromResource(this, R.array.settings_theme_options, android.R.layout.simple_spinner_item) 43 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) 44 | spinner.adapter = adapter 45 | spinner.setSelection(settings.getInt("theme", 0)) 46 | spinner.onItemSelectedListener = object: AdapterView.OnItemSelectedListener { 47 | override fun onNothingSelected(parent: AdapterView<*>?) {} 48 | 49 | override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { 50 | val editor = getSharedPreferences(DisclaimerActivity.PREFS_NAME, 0).edit() 51 | editor.putInt("theme", position) 52 | editor.apply() 53 | 54 | delegate.setLocalNightMode(themeToDayNightMode(position)) 55 | } 56 | } 57 | 58 | findViewById(R.id.settings_restart).setOnClickListener { 59 | // This is quite hacky, however it seems to be the only way to reliably do this 60 | 61 | Log.e("SettingsAndAbout", "Restarting app") 62 | 63 | val mStartActivity = Intent(this, SettingsAndAboutActivity::class.java) 64 | val mPendingIntentId = 123456 65 | val mPendingIntent = PendingIntent.getActivity(this, mPendingIntentId, mStartActivity, 66 | PendingIntent.FLAG_CANCEL_CURRENT) 67 | val mgr = this.getSystemService(Context.ALARM_SERVICE) as AlarmManager 68 | mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent) 69 | System.exit(0) 70 | } 71 | 72 | findViewById(R.id.source).setOnClickListener { 73 | val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/rubenwardy/mtmods4android/")) 74 | startActivity(browserIntent) 75 | } 76 | 77 | findViewById(R.id.translate).setOnClickListener { 78 | val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://hosted.weblate.org/projects/minetest/mtmods4android/")) 79 | startActivity(browserIntent) 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/views/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.views 2 | 3 | import android.Manifest 4 | import android.annotation.SuppressLint 5 | import android.content.pm.PackageManager 6 | import android.support.v7.app.AppCompatActivity 7 | import android.os.Bundle 8 | import android.support.v4.app.ActivityCompat 9 | import android.support.v4.content.ContextCompat 10 | import android.content.Intent 11 | import android.widget.Toast 12 | import com.rubenwardy.minetestmodmanager.R 13 | 14 | 15 | class SplashActivity : AppCompatActivity() { 16 | 17 | private val PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE: Int = 122 18 | 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | checkPermission() 23 | } 24 | 25 | private fun permissionsOK() { 26 | val intent = Intent(this, ModListActivity::class.java) 27 | startActivity(intent) 28 | finish() 29 | } 30 | 31 | private fun permissionsDenied() { 32 | Toast.makeText(this, R.string.perm_storage_needed, Toast.LENGTH_SHORT).show() 33 | finish() 34 | } 35 | 36 | private fun checkPermission() { 37 | if (ContextCompat.checkSelfPermission(this, 38 | Manifest.permission.WRITE_EXTERNAL_STORAGE) 39 | != PackageManager.PERMISSION_GRANTED) { 40 | if (ActivityCompat.shouldShowRequestPermissionRationale(this, 41 | Manifest.permission.WRITE_EXTERNAL_STORAGE)) { 42 | permissionsDenied() 43 | } else { 44 | ActivityCompat.requestPermissions(this, 45 | arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 46 | PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE) 47 | } 48 | } else { 49 | permissionsOK() 50 | } 51 | } 52 | 53 | override fun onRequestPermissionsResult(requestCode: Int, 54 | permissions: Array, grantResults: IntArray) { 55 | when (requestCode) { 56 | PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE -> { 57 | // If request is cancelled, the result arrays are empty. 58 | if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { 59 | permissionsOK() 60 | } else { 61 | permissionsDenied() 62 | } 63 | return 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/rubenwardy/minetestmodmanager/views/WorldConfigActivity.java: -------------------------------------------------------------------------------- 1 | package com.rubenwardy.minetestmodmanager.views; 2 | 3 | import android.content.DialogInterface; 4 | import android.content.res.Resources; 5 | import android.os.Environment; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.support.design.widget.Snackbar; 9 | import android.support.v7.app.ActionBar; 10 | import android.support.v7.app.AlertDialog; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.os.Bundle; 13 | import android.support.v7.widget.RecyclerView; 14 | import android.support.v7.widget.Toolbar; 15 | import android.view.LayoutInflater; 16 | import android.view.Menu; 17 | import android.view.MenuItem; 18 | import android.view.View; 19 | import android.view.ViewGroup; 20 | import android.widget.CheckBox; 21 | import android.widget.CompoundButton; 22 | 23 | import com.rubenwardy.minetestmodmanager.R; 24 | import com.rubenwardy.minetestmodmanager.models.MinetestConf; 25 | import com.rubenwardy.minetestmodmanager.models.Mod; 26 | import com.rubenwardy.minetestmodmanager.models.ModList; 27 | import com.rubenwardy.minetestmodmanager.manager.ModManager; 28 | 29 | import java.io.File; 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | 33 | public class WorldConfigActivity extends AppCompatActivity { 34 | @Nullable 35 | private String modpath = null; 36 | @Nullable 37 | private MinetestConf conf = null; 38 | @Nullable 39 | private File conf_file = null; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_world_config); 45 | 46 | File extern = Environment.getExternalStorageDirectory(); 47 | File mt_root = new File(extern, "/Minetest"); 48 | File mt_dir = new File(mt_root, "/mods"); 49 | modpath = mt_dir.getAbsolutePath(); 50 | 51 | conf = new MinetestConf(); 52 | File world_dir = new File(mt_root, "/worlds/singleplayerworld"); 53 | conf_file = new File(world_dir, "/world.mt"); 54 | if (!world_dir.isDirectory()) { 55 | if (!world_dir.mkdirs()) { 56 | AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); 57 | alertDialogBuilder.setTitle(R.string.dialog_nowld_title); 58 | alertDialogBuilder.setCancelable(false); 59 | alertDialogBuilder.setMessage(R.string.dialog_nowld_msg); 60 | alertDialogBuilder.setNegativeButton(R.string.dialog_close, new DialogInterface.OnClickListener() { 61 | public void onClick(DialogInterface dialog, int id) { 62 | WorldConfigActivity.this.finish(); 63 | } 64 | }); 65 | AlertDialog alertDialog = alertDialogBuilder.create(); 66 | alertDialog.show(); 67 | return; 68 | } 69 | } 70 | if (!conf.read(conf_file)) { 71 | // Create configuration file 72 | conf.set("gameid", "minetest"); 73 | conf.set("backend", "sqlite3"); 74 | } 75 | 76 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 77 | setSupportActionBar(toolbar); 78 | toolbar.setTitle(getTitle()); 79 | 80 | // Show the Up button in the action bar. 81 | ActionBar actionBar = getSupportActionBar(); 82 | if (actionBar != null) { 83 | actionBar.setDisplayHomeAsUpEnabled(true); 84 | Resources res = getResources(); 85 | actionBar.setTitle(res.getString(R.string.world_config)); 86 | } 87 | 88 | View recyclerView = findViewById(R.id.mod_list); 89 | assert recyclerView != null; 90 | setupRecyclerView((RecyclerView) recyclerView); 91 | } 92 | 93 | @Override 94 | public boolean onCreateOptionsMenu(Menu menu) { 95 | getMenuInflater().inflate(R.menu.menu_world_config, menu); 96 | 97 | return true; 98 | } 99 | 100 | @Override 101 | public boolean onOptionsItemSelected(MenuItem item) { 102 | if (conf == null) { 103 | return false; 104 | } 105 | 106 | switch (item.getItemId()) { 107 | case R.id.accept: 108 | conf.save(conf_file); 109 | Resources res = getResources(); 110 | String text = res.getString(R.string.world_config_saved); 111 | Snackbar.make(findViewById(R.id.mod_list), text, Snackbar.LENGTH_LONG) 112 | .setAction("Action", null).show(); 113 | return true; 114 | } 115 | 116 | return false; 117 | } 118 | 119 | private void setupRecyclerView(@NonNull RecyclerView recyclerView) { 120 | //Add your adapter to the sectionAdapter 121 | ModListRecyclerViewAdapter adapter = new ModListRecyclerViewAdapter(); 122 | recyclerView.setAdapter(adapter); 123 | 124 | ModManager modman = ModManager.getInstance(); 125 | ModList list = modman.getModList(modpath); 126 | if (list == null) { 127 | finish(); 128 | return; 129 | } 130 | List mods = new ArrayList<>(list.mods); 131 | adapter.setMods(mods); 132 | 133 | } 134 | 135 | public class ModListRecyclerViewAdapter 136 | extends RecyclerView.Adapter { 137 | 138 | private List mods; 139 | 140 | public ModListRecyclerViewAdapter() { 141 | mods = new ArrayList<>(); 142 | } 143 | 144 | public void setMods(List mods) { 145 | this.mods = mods; 146 | } 147 | 148 | @NonNull 149 | @Override 150 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 151 | View view = LayoutInflater.from(parent.getContext()) 152 | .inflate(R.layout.mod_checklist_content, parent, false); 153 | return new ViewHolder(view); 154 | } 155 | 156 | @Override 157 | public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { 158 | // Get Mod 159 | holder.mod = mods.get(position); 160 | 161 | // 162 | // Fill out TextViews 163 | // 164 | 165 | holder.view_modname.setText(holder.mod.name); 166 | 167 | // 168 | // Register callback 169 | // 170 | assert conf != null; 171 | boolean enabled = holder.mod.isEnabled(conf); 172 | if (holder.mod.type == Mod.ModType.EMT_MODPACK) { 173 | holder.view_modname.setText(holder.mod.name + " (Modpack)"); 174 | } 175 | holder.view_modname.setChecked(enabled); 176 | 177 | holder.view_modname.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 178 | @Override 179 | public void onCheckedChanged(CompoundButton comp_btn, boolean checked) { 180 | holder.mod.setEnabled(conf, checked); 181 | } 182 | }); 183 | } 184 | 185 | @Override 186 | public int getItemCount() { 187 | return mods.size(); 188 | } 189 | 190 | public class ViewHolder extends RecyclerView.ViewHolder { 191 | @NonNull 192 | public final View view; 193 | @NonNull 194 | public final CheckBox view_modname; 195 | @Nullable 196 | public Mod mod; 197 | 198 | public ViewHolder(@NonNull View view) { 199 | super(view); 200 | this.view = view; 201 | view_modname = (CheckBox) view.findViewById(R.id.modname); 202 | } 203 | 204 | @NonNull 205 | @Override 206 | public String toString() { 207 | return super.toString() + " '" + view_modname.getText() + "'"; 208 | } 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi-v11/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-hdpi-v11/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi-v9/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-hdpi-v9/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-hdpi/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi-v11/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-mdpi-v11/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi-v9/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-mdpi-v9/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-mdpi/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi-v11/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-xhdpi-v11/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi-v9/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-xhdpi-v9/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-xhdpi/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi-v11/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-xxhdpi-v11/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi-v9/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-xxhdpi-v9/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenwardy/mtmods4android/90792b7ad86d8e2e01edf1794d8a31e018eadc7f/app/src/main/res/drawable-xxhdpi/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mod_action_check_depends_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mod_action_find_elsewhere_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mod_action_forum_topic_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mod_action_info_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mod_action_mods_by_author.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mod_action_readme_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mod_action_report_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_public_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mod_preview_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_disclaimer.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 25 | 26 | 30 | 31 | 41 | 45 | 46 | 56 | 60 | 61 | 71 | 75 | 76 | 90 | 91 |