From 1be463483836cb84d648a615ce5edc4d0763716b Mon Sep 17 00:00:00 2001 From: ubulem Date: Fri, 4 Sep 2020 20:04:28 +0200 Subject: [PATCH] Backport MultiCraft Android Java part --- build/android/app/build.gradle | 113 +++-- .../android/app/src/main/AndroidManifest.xml | 58 ++- .../java/com/bugsnag/android/Bugsnag.java | 18 + .../com/multicraft/game/CustomEditText.java | 43 ++ .../com/multicraft/game/GameActivity.java | 315 ++++++++++++ .../com/multicraft/game/JsonSettings.java | 88 ++++ .../com/multicraft/game/MainActivity.java | 468 ++++++++++++++++++ .../com/multicraft/game/MyApplication.java | 33 ++ .../multicraft/game}/UnzipService.java | 124 +++-- .../game/callbacks/CallBackListener.java | 25 + .../multicraft/game/helpers/AdManager.java | 41 ++ .../game/helpers/ApiLevelHelper.java | 54 ++ .../multicraft/game/helpers/Constants.java | 37 ++ .../game/helpers/PermissionHelper.java | 136 +++++ .../game/helpers/PreferencesHelper.java | 132 +++++ .../multicraft/game/helpers/RateMeHelper.java | 96 ++++ .../multicraft/game/helpers/Utilities.java | 180 +++++++ .../game/helpers/VersionManagerHelper.java | 157 ++++++ .../net/minetest/minetest/CopyZipTask.java | 82 --- .../net/minetest/minetest/GameActivity.java | 126 ----- .../minetest/InputDialogActivity.java | 98 ---- .../net/minetest/minetest/MainActivity.java | 153 ------ .../app/src/main/res/drawable/background.png | Bin 83 -> 8482 bytes .../res/drawable/custom_dialog_rounded.xml | 6 + .../res/drawable/custom_edittext_rounded.xml | 10 + .../main/res/drawable/custom_progress_bar.xml | 27 + .../res/drawable/ic_launcher_background.xml | 170 +++++++ .../app/src/main/res/drawable/update.png | Bin 0 -> 134 bytes .../app/src/main/res/layout/activity_main.xml | 31 +- .../app/src/main/res/layout/rate_dialog.xml | 50 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../ic_launcher_foreground.png | Bin 0 -> 18321 bytes .../main/res/mipmap-v25/ic_launcher_round.png | Bin 0 -> 47791 bytes .../ic_launcher_foreground.png | Bin 0 -> 28837 bytes .../ic_launcher_foreground.png | Bin 0 -> 54697 bytes .../ic_launcher_foreground.png | Bin 0 -> 86222 bytes .../app/src/main/res/mipmap/ic_launcher.png | Bin 5780 -> 32456 bytes .../app/src/main/res/values-ru/strings.xml | 44 ++ .../app/src/main/res/values/strings.xml | 49 +- .../app/src/main/res/values/styles.xml | 16 +- .../app/src/main/res/xml/backup_rules.xml | 12 + .../main/res/xml/network_security_config.xml | 12 + build/android/build.gradle | 15 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 8 +- build/android/keystore-minetest.jks | Bin 2247 -> 0 bytes build/android/native/build.gradle | 62 ++- build/android/native/jni/Android.mk | 74 ++- build/android/native/jni/Application.mk | 26 +- .../native/src/main/AndroidManifest.xml | 2 +- build/android/settings.gradle | 2 +- 52 files changed, 2555 insertions(+), 648 deletions(-) create mode 100644 build/android/app/src/main/java/com/bugsnag/android/Bugsnag.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/CustomEditText.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/GameActivity.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/JsonSettings.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/MainActivity.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/MyApplication.java rename build/android/app/src/main/java/{net/minetest/minetest => com/multicraft/game}/UnzipService.java (51%) create mode 100644 build/android/app/src/main/java/com/multicraft/game/callbacks/CallBackListener.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/helpers/AdManager.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/helpers/ApiLevelHelper.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/helpers/Constants.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/helpers/PermissionHelper.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/helpers/PreferencesHelper.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/helpers/RateMeHelper.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/helpers/Utilities.java create mode 100644 build/android/app/src/main/java/com/multicraft/game/helpers/VersionManagerHelper.java delete mode 100644 build/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java delete mode 100644 build/android/app/src/main/java/net/minetest/minetest/GameActivity.java delete mode 100644 build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java delete mode 100644 build/android/app/src/main/java/net/minetest/minetest/MainActivity.java create mode 100644 build/android/app/src/main/res/drawable/custom_dialog_rounded.xml create mode 100644 build/android/app/src/main/res/drawable/custom_edittext_rounded.xml create mode 100644 build/android/app/src/main/res/drawable/custom_progress_bar.xml create mode 100644 build/android/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 build/android/app/src/main/res/drawable/update.png create mode 100644 build/android/app/src/main/res/layout/rate_dialog.xml create mode 100644 build/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 build/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 build/android/app/src/main/res/mipmap-hdpi-v26/ic_launcher_foreground.png create mode 100644 build/android/app/src/main/res/mipmap-v25/ic_launcher_round.png create mode 100644 build/android/app/src/main/res/mipmap-xhdpi-v26/ic_launcher_foreground.png create mode 100644 build/android/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_foreground.png create mode 100644 build/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_foreground.png create mode 100644 build/android/app/src/main/res/values-ru/strings.xml create mode 100644 build/android/app/src/main/res/xml/backup_rules.xml create mode 100644 build/android/app/src/main/res/xml/network_security_config.xml delete mode 100644 build/android/keystore-minetest.jks diff --git a/build/android/app/build.gradle b/build/android/app/build.gradle index e3619af17..d1ae39c8b 100644 --- a/build/android/app/build.gradle +++ b/build/android/app/build.gradle @@ -1,18 +1,23 @@ apply plugin: 'com.android.application' android { compileSdkVersion 29 - buildToolsVersion '29.0.3' - ndkVersion '21.1.6352462' + buildToolsVersion '30.0.3' + ndkVersion '22.0.7026061' defaultConfig { - applicationId 'net.minetest.minetest' + applicationId 'com.multicraft.game' minSdkVersion 16 + //noinspection OldTargetApi targetSdkVersion 29 versionName "${versionMajor}.${versionMinor}.${versionPatch}" versionCode project.versionCode + multiDexEnabled true } + // load properties Properties props = new Properties() - props.load(new FileInputStream(file('../local.properties'))) + def propfile = file('../local.properties') + if (propfile.exists()) + props.load(new FileInputStream(propfile)) if (props.getProperty('keystore') != null) { signingConfigs { @@ -26,9 +31,14 @@ android { buildTypes { release { + shrinkResources true minifyEnabled true signingConfig signingConfigs.release } + debug { + debuggable true + minifyEnabled false + } } } @@ -47,55 +57,75 @@ android { } } -task prepareAssets() { - def assetsFolder = "build/assets" +import com.android.build.OutputFile +import org.apache.tools.ant.taskdefs.condition.Os + +task prepareAssetsFiles() { + def assetsFolder = "build/assets/Files" def projRoot = "../../.." - def gameToCopy = "minetest_game" copy { - from "${projRoot}/minetest.conf.example", "${projRoot}/README.md" into assetsFolder + from "${projRoot}/builtin" into "${assetsFolder}/builtin" exclude '*.txt' } copy { - from "${projRoot}/doc/lgpl-2.1.txt" into "${assetsFolder}" - } - copy { - from "${projRoot}/builtin" into "${assetsFolder}/builtin" - } - /*copy { - // ToDo: fix Minetest shaders that currently don't work with OpenGL ES from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders" - }*/ + } copy { from "../native/deps/Android/Irrlicht/shaders" into "${assetsFolder}/client/shaders/Irrlicht" } copy { - from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts" + from "${projRoot}/fonts/Retron2000.ttf" into "${assetsFolder}/fonts" + } + fileTree("${projRoot}/po").include("**/*.po").forEach { poFile -> + def moPath = "${assetsFolder}/locale/${poFile.parentFile.name}/LC_MESSAGES/" + file(moPath).mkdirs() + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + exec { + commandLine 'msgfmt', '-o', "${moPath}/minetest.mo", poFile + } + } } copy { - from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}" - } - /*copy { - // ToDo: fix broken locales - from "${projRoot}/po" into "${assetsFolder}/po" - }*/ - copy { - from "${projRoot}/textures" into "${assetsFolder}/textures" + from "${projRoot}/textures" into "${assetsFolder}/textures" exclude '*.txt' } - file("${assetsFolder}/.nomedia").text = ""; + task zipAssetsFiles(type: Zip) { + archiveFileName = "Files.zip" + destinationDirectory = file("src/main/assets/data") - task zipAssets(type: Zip) { - archiveName "Minetest.zip" from "${assetsFolder}" - destinationDir file("src/main/assets") } } -preBuild.dependsOn zipAssets +task prepareAssetsGames() { + def assetsFolder = "build/assets/games" + def projRoot = "../../.." + def gameToCopy = "default" + + copy { + from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}" + } + + task zipAssetsGames(type: Zip) { + archiveFileName = "games.zip" + destinationDirectory = file("src/main/assets/data") + from "${assetsFolder}" + } +} + +task zipAssetsWorlds(type: Zip) { + archiveFileName = "worlds.zip" + destinationDirectory = file("src/main/assets/data") + from("../../worlds") { + into "worlds" + } +} + +preBuild.dependsOn zipAssetsFiles +preBuild.dependsOn zipAssetsGames +preBuild.dependsOn zipAssetsWorlds // Map for the version code that gives each ABI a value. -import com.android.build.OutputFile - def abiCodes = ['armeabi-v7a': 0, 'arm64-v8a': 1] android.applicationVariants.all { variant -> variant.outputs.each { @@ -106,6 +136,23 @@ android.applicationVariants.all { variant -> } dependencies { + /* MultiCraft Native */ implementation project(':native') - implementation 'androidx.appcompat:appcompat:1.1.0' + + /* Third-party libraries */ + implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.preference:preference:1.1.1' + implementation 'com.google.android.play:core:1.9.1' + implementation 'io.reactivex.rxjava2:rxjava:2.2.20' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + //noinspection GradleDependency + implementation 'com.squareup.okhttp3:okhttp:3.12.12' + //noinspection GradleDependency + implementation 'commons-io:commons-io:2.5' + implementation 'gun0912.ted:tedpermission-rx2:2.2.3' + implementation 'com.google.code.gson:gson:2.8.6' + + /* Analytics libraries */ + //noinspection GradleDynamicVersion + /*implementation 'com.bugsnag:bugsnag-android-core:5.+'*/ } diff --git a/build/android/app/src/main/AndroidManifest.xml b/build/android/app/src/main/AndroidManifest.xml index 0a7c8d95a..8b5e52a65 100644 --- a/build/android/app/src/main/AndroidManifest.xml +++ b/build/android/app/src/main/AndroidManifest.xml @@ -1,24 +1,43 @@ + + - + + - + + + + + + + + + - - - + diff --git a/build/android/app/src/main/java/com/bugsnag/android/Bugsnag.java b/build/android/app/src/main/java/com/bugsnag/android/Bugsnag.java new file mode 100644 index 000000000..698041ff3 --- /dev/null +++ b/build/android/app/src/main/java/com/bugsnag/android/Bugsnag.java @@ -0,0 +1,18 @@ +package com.bugsnag.android; + +import android.app.Application; +import android.util.Log; + +public class Bugsnag { + public static void notify(Throwable e) { + Log.getStackTraceString(e); + } + + public static void leaveBreadcrumb(String s) { + Log.d("Bugsnag", s); + } + + public static void start(Application application) { + Log.d("Bugsnag", "Bugsnag initialized"); + } +} diff --git a/build/android/app/src/main/java/com/multicraft/game/CustomEditText.java b/build/android/app/src/main/java/com/multicraft/game/CustomEditText.java new file mode 100644 index 000000000..03e9e827e --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/CustomEditText.java @@ -0,0 +1,43 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game; + +import android.content.Context; +import android.view.KeyEvent; +import android.view.inputmethod.InputMethodManager; + +import androidx.appcompat.widget.AppCompatEditText; + +public class CustomEditText extends AppCompatEditText { + public CustomEditText(Context context) { + super(context); + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + InputMethodManager mgr = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + mgr.hideSoftInputFromWindow(this.getWindowToken(), 0); + } + return false; + } +} diff --git a/build/android/app/src/main/java/com/multicraft/game/GameActivity.java b/build/android/app/src/main/java/com/multicraft/game/GameActivity.java new file mode 100644 index 000000000..923cd6214 --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/GameActivity.java @@ -0,0 +1,315 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game; + +import android.app.ActivityManager; +import android.app.NativeActivity; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Bundle; +import android.text.InputType; +import android.text.method.LinkMovementMethod; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import com.bugsnag.android.Bugsnag; +import com.multicraft.game.helpers.PreferencesHelper; +import com.multicraft.game.helpers.RateMeHelper; +import com.multicraft.game.helpers.Utilities; + +import org.json.JSONObject; +import org.json.JSONTokener; + +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +import static android.content.res.Configuration.KEYBOARD_QWERTY; +import static com.multicraft.game.helpers.AdManager.initAd; +import static com.multicraft.game.helpers.AdManager.setAdsCallback; +import static com.multicraft.game.helpers.AdManager.startAd; +import static com.multicraft.game.helpers.AdManager.stopAd; +import static com.multicraft.game.helpers.Constants.versionCode; +import static com.multicraft.game.helpers.PreferencesHelper.IS_ASK_CONSENT; +import static com.multicraft.game.helpers.PreferencesHelper.TAG_EXIT_GAME_COUNT; +import static com.multicraft.game.helpers.PreferencesHelper.TAG_LAST_RATE_VERSION_CODE; +import static com.multicraft.game.helpers.PreferencesHelper.getInstance; +import static com.multicraft.game.helpers.Utilities.getIcon; +import static com.multicraft.game.helpers.Utilities.makeFullScreen; + +public class GameActivity extends NativeActivity { + static { + try { + System.loadLibrary("MultiCraft"); + } catch (UnsatisfiedLinkError | OutOfMemoryError e) { + Bugsnag.notify(e); + System.exit(0); + } catch (IllegalArgumentException i) { + Bugsnag.notify(i); + System.exit(0); + } catch (Error | Exception e) { + Bugsnag.notify(e); + System.exit(0); + } + } + + private int messageReturnCode = -1; + private String messageReturnValue = ""; + private int height, width; + private boolean consent, isMultiPlayer; + private PreferencesHelper pf; + private Disposable adInitSub, gdprSub; + private boolean hasKeyboard; + + public static native void pauseGame(); + + public static native void keyboardEvent(boolean keyboard); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle bundle = getIntent().getExtras(); + Resources resources = getResources(); + height = bundle != null ? bundle.getInt("height", 0) : resources.getDisplayMetrics().heightPixels; + width = bundle != null ? bundle.getInt("width", 0) : resources.getDisplayMetrics().widthPixels; + getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); + hasKeyboard = !(resources.getConfiguration().hardKeyboardHidden == KEYBOARD_QWERTY); + keyboardEvent(hasKeyboard); + pf = getInstance(this); + RateMeHelper.onStart(this); + askGdpr(); + } + + private void subscribeAds() { + if (pf.isAdsEnable()) { + adInitSub = Completable.fromAction(() -> initAd(this, consent)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> setAdsCallback(this)); + } + } + + // GDPR check + private void askGdpr() { + if (pf.isAskConsent()) + isGdprSubject(); + else { + consent = true; + subscribeAds(); + } + } + + private void isGdprSubject() { + gdprSub = Observable.fromCallable(() -> Utilities.getJson("http://adservice.google.com/getconfig/pubvendors")) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + JSONObject json = new JSONObject(new JSONTokener(result)); + if (json.getBoolean("is_request_in_eea_or_unknown")) + showGdprDialog(); + else { + consent = true; + subscribeAds(); + } + }, + throwable -> { + consent = true; + subscribeAds(); + }); + } + + private void showGdprDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setIcon(getIcon(this)); + builder.setTitle(getString(R.string.app_name)); + TextView tv = new TextView(this); + tv.setText(R.string.gdpr_main_text); + tv.setTypeface(null); + tv.setPadding(20, 0, 20, 0); + tv.setGravity(Gravity.CENTER); + tv.setMovementMethod(LinkMovementMethod.getInstance()); + builder.setView(tv); + builder.setPositiveButton(getString(R.string.gdpr_agree), (dialogInterface, i) -> { + dialogInterface.dismiss(); + pf.saveSettings(IS_ASK_CONSENT, false); + consent = true; + subscribeAds(); + }); + builder.setNegativeButton(getString(R.string.gdpr_disagree), (dialogInterface, i) -> { + dialogInterface.dismiss(); + pf.saveSettings(IS_ASK_CONSENT, false); + consent = false; + subscribeAds(); + }); + builder.setCancelable(false); + final AlertDialog dialog = builder.create(); + if (!isFinishing()) + dialog.show(); + } + + private void checkRateDialog() { + if (RateMeHelper.shouldShowRateDialog()) { + pf.saveSettings(TAG_LAST_RATE_VERSION_CODE, versionCode); + runOnUiThread(() -> RateMeHelper.showRateDialog(this)); + } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) + makeFullScreen(this); + } + + @Override + protected void onResume() { + super.onResume(); + makeFullScreen(this); + } + + @Override + public void onBackPressed() { + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (adInitSub != null) adInitSub.dispose(); + if (gdprSub != null) gdprSub.dispose(); + } + + public void showDialog(String acceptButton, String hint, String current, int editType) { + runOnUiThread(() -> showDialogUI(hint, current, editType)); + } + + @Override + protected void onPause() { + super.onPause(); + pauseGame(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + boolean statusKeyboard = !(getResources().getConfiguration().hardKeyboardHidden == KEYBOARD_QWERTY); + if (hasKeyboard != statusKeyboard) { + hasKeyboard = statusKeyboard; + keyboardEvent(hasKeyboard); + } + } + + private void showDialogUI(String hint, String current, int editType) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + EditText editText = new CustomEditText(this); + builder.setView(editText); + AlertDialog alertDialog = builder.create(); + editText.requestFocus(); + editText.setHint(hint); + editText.setText(current); + final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, + InputMethodManager.HIDE_IMPLICIT_ONLY); + if (editType == 1) + editText.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_FLAG_MULTI_LINE); + else if (editType == 3) + editText.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_VARIATION_PASSWORD); + else + editText.setInputType(InputType.TYPE_CLASS_TEXT); + editText.setSelection(editText.getText().length()); + editText.setOnKeyListener((view, KeyCode, event) -> { + if (KeyCode == KeyEvent.KEYCODE_ENTER) { + imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); + messageReturnCode = 0; + messageReturnValue = editText.getText().toString(); + alertDialog.dismiss(); + return true; + } + return false; + }); + alertDialog.show(); + alertDialog.setOnCancelListener(dialog -> { + getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + messageReturnValue = current; + messageReturnCode = -1; + }); + } + + public int getDialogState() { + return messageReturnCode; + } + + public String getDialogValue() { + messageReturnCode = -1; + return messageReturnValue; + } + + public float getDensity() { + return getResources().getDisplayMetrics().density; + } + + public int getDisplayHeight() { + return height; + } + + public int getDisplayWidth() { + return width; + } + + public float getMemoryMax() { + ActivityManager actManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); + float memory = 1.0f; + if (actManager != null) { + actManager.getMemoryInfo(memInfo); + memory = memInfo.totalMem * 1.0f / (1024 * 1024 * 1024); + memory = Math.round(memory * 100) / 100.0f; + } + return memory; + } + + public void notifyServerConnect(boolean multiplayer) { + isMultiPlayer = multiplayer; + if (isMultiPlayer) + stopAd(); + } + + public void notifyExitGame() { + pf.saveSettings(TAG_EXIT_GAME_COUNT, pf.getExitGameCount() + 1); + if (!isFinishing()) { + if (isMultiPlayer) { + if (pf.isAdsEnable()) + startAd(this, false, true); + } else + checkRateDialog(); + } + } +} diff --git a/build/android/app/src/main/java/com/multicraft/game/JsonSettings.java b/build/android/app/src/main/java/com/multicraft/game/JsonSettings.java new file mode 100644 index 000000000..6f407f0ef --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/JsonSettings.java @@ -0,0 +1,88 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +public class JsonSettings { + @SerializedName(value = "version_code") + private int versionCode; + @SerializedName(value = "version_code_bad") + private List badVersionCodes; + @SerializedName(value = "rate_min_version_code") + private int rateMinVersionCode; + @SerializedName(value = "package") + private String packageName; + @SerializedName(value = "content_ru") + private String contentRus; + @SerializedName(value = "content_en") + private String contentEng; + @SerializedName(value = "ads_delay") + private int adsDelay; + @SerializedName(value = "ads_repeat") + private int adsRepeat; + @SerializedName(value = "ads_enable") + private boolean adsEnabled; + @SerializedName(value = "review_enable") + private boolean reviewEnabled; + + public int getVersionCode() { + return versionCode; + } + + public List getBadVersionCodes() { + return badVersionCodes; + } + + public int getRateMinVersionCode() { + return rateMinVersionCode; + } + + public String getPackageName() { + return packageName; + } + + public int getAdsDelay() { + return adsDelay; + } + + public int getAdsRepeat() { + return adsRepeat; + } + + public boolean isAdsEnabled() { + return adsEnabled; + } + + public boolean isReviewEnabled() { + return reviewEnabled; + } + + public String getContentRus() { + return contentRus; + } + + public String getContentEng() { + return contentEng; + } +} diff --git a/build/android/app/src/main/java/com/multicraft/game/MainActivity.java b/build/android/app/src/main/java/com/multicraft/game/MainActivity.java new file mode 100644 index 000000000..d949a2de2 --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/MainActivity.java @@ -0,0 +1,468 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; +import android.graphics.Color; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.text.Html; +import android.view.Display; +import android.view.View; +import android.view.WindowManager.LayoutParams; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; + +import com.bugsnag.android.Bugsnag; +import com.google.android.play.core.appupdate.AppUpdateInfo; +import com.google.android.play.core.appupdate.AppUpdateManager; +import com.google.android.play.core.appupdate.AppUpdateManagerFactory; +import com.google.android.play.core.install.InstallStateUpdatedListener; +import com.google.android.play.core.install.model.AppUpdateType; +import com.google.android.play.core.install.model.InstallStatus; +import com.google.android.play.core.install.model.UpdateAvailability; +import com.google.android.play.core.tasks.Task; +import com.multicraft.game.callbacks.CallBackListener; +import com.multicraft.game.helpers.PermissionHelper; +import com.multicraft.game.helpers.PreferencesHelper; +import com.multicraft.game.helpers.Utilities; +import com.multicraft.game.helpers.VersionManagerHelper; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +import static android.provider.Settings.ACTION_WIFI_SETTINGS; +import static android.provider.Settings.ACTION_WIRELESS_SETTINGS; +import static com.multicraft.game.UnzipService.ACTION_FAILURE; +import static com.multicraft.game.UnzipService.UNZIP_FAILURE; +import static com.multicraft.game.UnzipService.UNZIP_SUCCESS; +import static com.multicraft.game.helpers.ApiLevelHelper.isAndroidQ; +import static com.multicraft.game.helpers.ApiLevelHelper.isJellyBeanMR1; +import static com.multicraft.game.helpers.ApiLevelHelper.isLollipop; +import static com.multicraft.game.helpers.ApiLevelHelper.isOreo; +import static com.multicraft.game.helpers.Constants.FILES; +import static com.multicraft.game.helpers.Constants.GAMES; +import static com.multicraft.game.helpers.Constants.NO_SPACE_LEFT; +import static com.multicraft.game.helpers.Constants.REQUEST_CONNECTION; +import static com.multicraft.game.helpers.Constants.REQUEST_UPDATE; +import static com.multicraft.game.helpers.Constants.UPDATE_LINK; +import static com.multicraft.game.helpers.Constants.WORLDS; +import static com.multicraft.game.helpers.Constants.versionName; +import static com.multicraft.game.helpers.PreferencesHelper.TAG_BUILD_NUMBER; +import static com.multicraft.game.helpers.PreferencesHelper.TAG_LAUNCH_TIMES; +import static com.multicraft.game.helpers.Utilities.addShortcut; +import static com.multicraft.game.helpers.Utilities.deleteFiles; +import static com.multicraft.game.helpers.Utilities.getIcon; +import static com.multicraft.game.helpers.Utilities.getZipsFromAssets; +import static com.multicraft.game.helpers.Utilities.makeFullScreen; + +public class MainActivity extends AppCompatActivity implements CallBackListener { + private ArrayList zips; + private int height, width; + private ProgressBar mProgressBar, mProgressBarIndeterminate; + private TextView mLoading; + private VersionManagerHelper versionManagerHelper = null; + private PreferencesHelper pf; + private AppUpdateManager appUpdateManager; + final InstallStateUpdatedListener listener = state -> { + if (state.installStatus() == InstallStatus.DOWNLOADING) { + if (mProgressBar != null) { + int progress = (int) (state.bytesDownloaded() * 100 / state.totalBytesToDownload()); + showProgress(R.string.downloading, R.string.downloadingp, progress); + } + } else if (state.installStatus() == InstallStatus.DOWNLOADED) { + appUpdateManager.completeUpdate(); + } + }; + private Disposable connectionSub, versionManagerSub, cleanSub, copySub; + private final BroadcastReceiver myReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int progress = 0; + if (intent != null) + progress = intent.getIntExtra(UnzipService.ACTION_PROGRESS, 0); + if (progress >= 0) { + if (mProgressBar != null) { + showProgress(R.string.loading, R.string.loadingp, progress); + } + } else if (progress == UNZIP_FAILURE) { + Toast.makeText(MainActivity.this, intent.getStringExtra(ACTION_FAILURE), Toast.LENGTH_LONG).show(); + showRestartDialog(""); + } else if (progress == UNZIP_SUCCESS) { + deleteFiles(Arrays.asList(FILES, WORLDS, GAMES), getCacheDir().toString()); + runGame(); + } + } + }; + private Task appUpdateInfoTask; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); + setContentView(R.layout.activity_main); + pf = PreferencesHelper.getInstance(this); + appUpdateManager = AppUpdateManagerFactory.create(this); + appUpdateInfoTask = appUpdateManager.getAppUpdateInfo(); + IntentFilter filter = new IntentFilter(UnzipService.ACTION_UPDATE); + registerReceiver(myReceiver, filter); + if (!isTaskRoot()) { + finish(); + return; + } + addLaunchTimes(); + PermissionHelper permission = new PermissionHelper(this); + permission.setListener(this); + permission.askPermissions(); + } + + @Override + protected void onResume() { + super.onResume(); + makeFullScreen(this); + appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> { + if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) + appUpdateManager.completeUpdate(); + }); + } + + @Override + public void onBackPressed() { + // Prevent abrupt interruption when copy game files from assets + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (connectionSub != null) connectionSub.dispose(); + if (versionManagerSub != null) versionManagerSub.dispose(); + if (cleanSub != null) cleanSub.dispose(); + if (copySub != null) copySub.dispose(); + appUpdateManager.unregisterListener(listener); + unregisterReceiver(myReceiver); + } + + private void addLaunchTimes() { + pf.saveSettings(TAG_LAUNCH_TIMES, pf.getLaunchTimes() + 1); + } + + // interface + private void showProgress(int textMessage, int progressMessage, int progress) { + if (mProgressBar.getVisibility() == View.GONE) { + updateViews(textMessage, View.VISIBLE, View.GONE, View.VISIBLE); + mProgressBar.setProgress(0); + } else if (progress > 0) { + mLoading.setText(String.format(getResources().getString(progressMessage), progress)); + mProgressBar.setProgress(progress); + // colorize the progress bar + Drawable progressDrawable = ((LayerDrawable) + mProgressBar.getProgressDrawable()).getDrawable(1); + int color = Color.rgb(255 - progress * 2, progress * 2, 25); + if (isAndroidQ()) + progressDrawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.SRC_IN)); + else + progressDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); + } + } + + // real screen resolution + private void getDefaultResolution() { + Display display = getWindowManager().getDefaultDisplay(); + Point size = new Point(); + if (isJellyBeanMR1()) + display.getRealSize(size); + else + display.getSize(size); + height = Math.min(size.x, size.y); + width = Math.max(size.x, size.y); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) + makeFullScreen(this); + } + + private void init() { + mProgressBar = findViewById(R.id.PB1); + mProgressBarIndeterminate = findViewById(R.id.PB2); + mLoading = findViewById(R.id.tv_progress); + if (!pf.isCreateShortcut() && !isOreo()) + addShortcut(this); + checkAppVersion(); + } + + void showUpdateDialog() { + updateViews(R.string.loading, View.VISIBLE, View.VISIBLE, View.GONE); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setIcon(getIcon(this)) + .setTitle(R.string.available) + .setMessage(Html.fromHtml( + versionManagerHelper.getMessage(), null, versionManagerHelper.getCustomTagHandler())) + .setPositiveButton(R.string.update, (dialogInterface, i) -> { + versionManagerHelper.updateNow(versionManagerHelper.getUpdateUrl()); + finish(); + }) + .setNeutralButton(R.string.later, (dialogInterface, i) -> { + versionManagerHelper.remindMeLater(); + startNative(); + }); + builder.setCancelable(false); + final AlertDialog dialog = builder.create(); + if (!isFinishing()) + dialog.show(); + } + + public void startUpdate() { + appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> { + if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE + && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) { + try { + appUpdateManager.startUpdateFlowForResult( + appUpdateInfo, AppUpdateType.FLEXIBLE, this, REQUEST_UPDATE); + } catch (IntentSender.SendIntentException e) { + //Bugsnag.notify(e); + showUpdateDialog(); + } + } else { + showUpdateDialog(); + } + }); + appUpdateInfoTask.addOnFailureListener(e -> { + //Bugsnag.notify(e); + showUpdateDialog(); + }); + } + + private void checkUrlVersion() { + versionManagerHelper = new VersionManagerHelper(this); + if (versionManagerHelper.isCheckVersion()) + versionManagerSub = Observable.fromCallable(() -> Utilities.getJson(UPDATE_LINK)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .timeout(3000, TimeUnit.MILLISECONDS) + .subscribe(result -> isShowDialog(versionManagerHelper.isShow(result)), + throwable -> runOnUiThread(() -> isShowDialog(false))); + else isShowDialog(false); + } + + private void runGame() { + pf.saveSettings(TAG_BUILD_NUMBER, versionName); + connectionSub = checkConnection(); + } + + private void startNative() { + getDefaultResolution(); + Intent intent = new Intent(this, GameActivity.class); + intent.putExtra("height", height); + intent.putExtra("width", width); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CONNECTION) { + checkUrlVersion(); + } else if (requestCode == REQUEST_UPDATE) { + if (resultCode == RESULT_OK) + appUpdateManager.registerListener(listener); + else + startNative(); + } + } + + private void cleanUpOldFiles(boolean isAll) { + updateViews(R.string.preparing, View.VISIBLE, View.VISIBLE, View.GONE); + Completable delObs; + File externalStorage = getExternalFilesDir(null); + if (isAll) + delObs = Completable.fromAction(() -> deleteFiles(Collections.singletonList(externalStorage))); + else { + List filesList = Arrays.asList(new File(externalStorage, "cache"), + new File(getFilesDir(), "builtin"), + new File(getFilesDir(), "games"), + new File(externalStorage, "debug.txt")); + delObs = Completable.fromAction(() -> deleteFiles(filesList)); + } + cleanSub = delObs.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> startCopy(isAll)); + } + + private void checkAppVersion() { + String prefVersion; + try { + prefVersion = pf.getBuildNumber(); + } catch (ClassCastException e) { + prefVersion = "1"; + } + if (prefVersion.equals(versionName)) { + mProgressBarIndeterminate.setVisibility(View.VISIBLE); + runGame(); + } else { + cleanUpOldFiles(prefVersion.equals("0")); + } + } + + public void updateViews(int text, int textVisib, int progressIndetermVisib, int progressVisib) { + mLoading.setText(text); + mLoading.setVisibility(textVisib); + mProgressBarIndeterminate.setVisibility(progressIndetermVisib); + mProgressBar.setVisibility(progressVisib); + } + + public void isShowDialog(boolean flag) { + if (flag) { + if (isLollipop()) + startUpdate(); + else + showUpdateDialog(); + } else + startNative(); + } + + private Disposable checkConnection() { + return Observable.fromCallable(Utilities::isReachable) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .timeout(4000, TimeUnit.MILLISECONDS) + .subscribe(result -> { + if (result) checkUrlVersion(); + else showConnectionDialog(); + }, + throwable -> runOnUiThread(this::showConnectionDialog)); + } + + private void startCopy(boolean isAll) { + zips = getZipsFromAssets(this); + if (!isAll) zips.remove(WORLDS); + copySub = Completable.fromAction(() -> runOnUiThread(() -> copyAssets(zips))) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> startUnzipService(zips)); + } + + private void copyAssets(ArrayList zips) { + for (String zipName : zips) { + try (InputStream in = getAssets().open("data/" + zipName)) { + FileUtils.copyInputStreamToFile(in, new File(getCacheDir(), zipName)); + } catch (IOException e) { + Bugsnag.leaveBreadcrumb("Failed to copy " + zipName); + if (e.getLocalizedMessage().contains(NO_SPACE_LEFT)) + showRestartDialog(NO_SPACE_LEFT); + else { + showRestartDialog(""); + Bugsnag.notify(e); + } + } + } + } + + private void startUnzipService(ArrayList file) { + Intent intent = new Intent(this, UnzipService.class); + intent.putStringArrayListExtra(UnzipService.EXTRA_KEY_IN_FILE, file); + startService(intent); + } + + private void showRestartDialog(final String source) { + String message; + if (NO_SPACE_LEFT.equals(source)) + message = getString(R.string.no_space); + else + message = getString(R.string.restart); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(message) + .setPositiveButton(R.string.ok, (dialogInterface, i) -> restartApp()); + builder.setCancelable(false); + final AlertDialog dialog = builder.create(); + if (!isFinishing()) + dialog.show(); + } + + private void restartApp() { + Intent intent = new Intent(getApplicationContext(), MainActivity.class); + int mPendingIntentId = 1337; + AlarmManager mgr = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE); + if (mgr != null) + mgr.set(AlarmManager.RTC, System.currentTimeMillis(), PendingIntent.getActivity( + getApplicationContext(), mPendingIntentId, intent, PendingIntent.FLAG_CANCEL_CURRENT)); + System.exit(0); + } + + @Override + public void onEvent(boolean isContinue) { + if (isFinishing()) return; + if (isContinue) init(); + else finish(); + } + + private void showConnectionDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(R.string.conn_message) + .setPositiveButton(R.string.conn_wifi, (dialogInterface, i) -> startHandledActivity(new Intent(ACTION_WIFI_SETTINGS))) + .setNegativeButton(R.string.conn_mobile, (dialogInterface, i) -> startHandledActivity(new Intent(ACTION_WIRELESS_SETTINGS))) + .setNeutralButton(R.string.ignore, (dialogInterface, i) -> startNative()); + builder.setCancelable(false); + final AlertDialog dialog = builder.create(); + if (!isFinishing()) + dialog.show(); + } + + private void startHandledActivity(Intent intent) { + try { + startActivityForResult(intent, REQUEST_CONNECTION); + } catch (Exception e) { + startNative(); + } + } +} diff --git a/build/android/app/src/main/java/com/multicraft/game/MyApplication.java b/build/android/app/src/main/java/com/multicraft/game/MyApplication.java new file mode 100644 index 000000000..3972f78da --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/MyApplication.java @@ -0,0 +1,33 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game; + +import androidx.multidex.MultiDexApplication; + +import com.bugsnag.android.Bugsnag; + +public class MyApplication extends MultiDexApplication { + @Override + public void onCreate() { + super.onCreate(); + Bugsnag.start(this); + } +} diff --git a/build/android/app/src/main/java/net/minetest/minetest/UnzipService.java b/build/android/app/src/main/java/com/multicraft/game/UnzipService.java similarity index 51% rename from build/android/app/src/main/java/net/minetest/minetest/UnzipService.java rename to build/android/app/src/main/java/com/multicraft/game/UnzipService.java index b69f7f36e..9362e68b5 100644 --- a/build/android/app/src/main/java/net/minetest/minetest/UnzipService.java +++ b/build/android/app/src/main/java/com/multicraft/game/UnzipService.java @@ -1,11 +1,11 @@ /* -Minetest +MultiCraft Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik Copyright (C) 2014-2020 ubulem, Bektur Mambetov This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or +the Free Software Foundation; either version 3.0 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, @@ -18,7 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -package net.minetest.minetest; +package com.multicraft.game; import android.app.IntentService; import android.app.Notification; @@ -26,39 +26,44 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.content.Intent; -import android.os.Build; -import android.os.Environment; -import android.widget.Toast; + +import com.bugsnag.android.Bugsnag; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; +import static com.multicraft.game.helpers.ApiLevelHelper.isOreo; +import static com.multicraft.game.helpers.Constants.NO_SPACE_LEFT; +import static com.multicraft.game.helpers.Utilities.getLocationByZip; +import static com.multicraft.game.helpers.Utilities.getZipsFromAssets; + public class UnzipService extends IntentService { - public static final String ACTION_UPDATE = "net.minetest.minetest.UPDATE"; - public static final String ACTION_PROGRESS = "net.minetest.minetest.PROGRESS"; - public static final String ACTION_FAILURE = "net.minetest.minetest.FAILURE"; - public static final String EXTRA_KEY_IN_FILE = "file"; - public static final int SUCCESS = -1; - public static final int FAILURE = -2; + public static final String ACTION_UPDATE = "com.multicraft.game.UPDATE"; + public static final String EXTRA_KEY_IN_FILE = "com.multicraft.game.file"; + public static final String ACTION_PROGRESS = "com.multicraft.game.progress"; + public static final String ACTION_FAILURE = "com.multicraft.game.failure"; + public static final int UNZIP_SUCCESS = -1; + public static final int UNZIP_FAILURE = -2; private final int id = 1; private NotificationManager mNotifyManager; - private boolean isSuccess = true; private String failureMessage; + private boolean isSuccess = true; public UnzipService() { - super("net.minetest.minetest.UnzipService"); + super("com.multicraft.game.UnzipService"); } - private void isDir(String dir, String location) { - File f = new File(location, dir); - if (!f.isDirectory()) - f.mkdirs(); + private void isDir(String dir, String unzipLocation) { + File f = new File(unzipLocation, dir); + if (!f.mkdirs() && !f.isDirectory()) + Bugsnag.leaveBreadcrumb(f + " (destination) folder was not created"); } @Override @@ -68,13 +73,14 @@ public class UnzipService extends IntentService { } private void createNotification() { - String name = "net.minetest.minetest"; - String channelId = "Minetest channel"; - String description = "notifications from Minetest"; + // There are hardcoding only for show it's just strings + String name = "com.multicraft.game"; + String channelId = "MultiCraft channel"; // The user-visible name of the channel. + String description = "notifications from MultiCraft"; // The user-visible description of the channel. Notification.Builder builder; if (mNotifyManager == null) mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (isOreo()) { int importance = NotificationManager.IMPORTANCE_LOW; NotificationChannel mChannel = null; if (mNotifyManager != null) @@ -89,44 +95,52 @@ public class UnzipService extends IntentService { mNotifyManager.createNotificationChannel(mChannel); } builder = new Notification.Builder(this, channelId); - } else { + } else builder = new Notification.Builder(this); - } builder.setContentTitle(getString(R.string.notification_title)) - .setSmallIcon(R.mipmap.ic_launcher) - .setContentText(getString(R.string.notification_description)); + .setContentText(getString(R.string.notification_description)) + .setSmallIcon(R.drawable.update); mNotifyManager.notify(id, builder.build()); } private void unzip(Intent intent) { - String zip = intent.getStringExtra(EXTRA_KEY_IN_FILE); - isDir("Minetest", Environment.getExternalStorageDirectory().toString()); - String location = Environment.getExternalStorageDirectory() + File.separator + "Minetest" + File.separator; + ArrayList zips; + if (intent != null) + zips = intent.getStringArrayListExtra(EXTRA_KEY_IN_FILE); + else + zips = getZipsFromAssets(this); + String cacheDir = getCacheDir().toString(); int per = 0; - int size = getSummarySize(zip); - File zipFile = new File(zip); - int readLen; + int size = getSummarySize(zips, cacheDir); byte[] readBuffer = new byte[8192]; - try (FileInputStream fileInputStream = new FileInputStream(zipFile); - ZipInputStream zipInputStream = new ZipInputStream(fileInputStream)) { + for (String zip : zips) { + String location = getLocationByZip(this, zip); + File zipFile = new File(cacheDir, zip); ZipEntry ze; - while ((ze = zipInputStream.getNextEntry()) != null) { - if (ze.isDirectory()) { - ++per; - isDir(ze.getName(), location); - } else { - publishProgress(100 * ++per / size); - try (OutputStream outputStream = new FileOutputStream(location + ze.getName())) { - while ((readLen = zipInputStream.read(readBuffer)) != -1) { - outputStream.write(readBuffer, 0, readLen); + int readLen; + try (FileInputStream fileInputStream = new FileInputStream(zipFile); + ZipInputStream zipInputStream = new ZipInputStream(fileInputStream)) { + while ((ze = zipInputStream.getNextEntry()) != null) { + String fileName = ze.getName(); + if (ze.isDirectory()) { + ++per; + isDir(fileName, location); + } else { + File extractedFile = new File(location, fileName); + publishProgress(100 * ++per / size); + try (OutputStream outputStream = new FileOutputStream(extractedFile)) { + while ((readLen = zipInputStream.read(readBuffer)) != -1) { + outputStream.write(readBuffer, 0, readLen); + } } } } - zipFile.delete(); + } catch (IOException e) { + if (!e.getLocalizedMessage().contains(NO_SPACE_LEFT)) + Bugsnag.notify(e); + failureMessage = e.getLocalizedMessage(); + isSuccess = false; } - } catch (IOException e) { - isSuccess = false; - failureMessage = e.getLocalizedMessage(); } } @@ -137,13 +151,15 @@ public class UnzipService extends IntentService { sendBroadcast(intentUpdate); } - private int getSummarySize(String zip) { + private int getSummarySize(ArrayList zips, String path) { int size = 0; - try { - ZipFile zipSize = new ZipFile(zip); - size += zipSize.size(); - } catch (IOException e) { - Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + for (String z : zips) { + try { + ZipFile zipFile = new ZipFile(new File(path, z)); + size += zipFile.size(); + } catch (IOException e) { + Bugsnag.notify(e); + } } return size; } @@ -152,6 +168,6 @@ public class UnzipService extends IntentService { public void onDestroy() { super.onDestroy(); mNotifyManager.cancel(id); - publishProgress(isSuccess ? SUCCESS : FAILURE); + publishProgress(isSuccess ? UNZIP_SUCCESS : UNZIP_FAILURE); } } diff --git a/build/android/app/src/main/java/com/multicraft/game/callbacks/CallBackListener.java b/build/android/app/src/main/java/com/multicraft/game/callbacks/CallBackListener.java new file mode 100644 index 000000000..1a7bd889a --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/callbacks/CallBackListener.java @@ -0,0 +1,25 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game.callbacks; + +public interface CallBackListener { + void onEvent(boolean isContinue); +} diff --git a/build/android/app/src/main/java/com/multicraft/game/helpers/AdManager.java b/build/android/app/src/main/java/com/multicraft/game/helpers/AdManager.java new file mode 100644 index 000000000..0eb087543 --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/helpers/AdManager.java @@ -0,0 +1,41 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game.helpers; + +import android.app.Activity; + +public class AdManager { + public static void initAd(final Activity activity, boolean consent) { + // NDA code here + } + + public static void setAdsCallback(final Activity activity) { + // NDA code here + } + + public static void startAd(final Activity activity, boolean isFirstTime, boolean isShowNow) { + // NDA code here + } + + public static void stopAd() { + // NDA code here + } +} diff --git a/build/android/app/src/main/java/com/multicraft/game/helpers/ApiLevelHelper.java b/build/android/app/src/main/java/com/multicraft/game/helpers/ApiLevelHelper.java new file mode 100644 index 000000000..e97ff4da0 --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/helpers/ApiLevelHelper.java @@ -0,0 +1,54 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game.helpers; + +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; +import static android.os.Build.VERSION_CODES.KITKAT; +import static android.os.Build.VERSION_CODES.LOLLIPOP; +import static android.os.Build.VERSION_CODES.O; +import static android.os.Build.VERSION_CODES.Q; + +public class ApiLevelHelper { + public static boolean isGreaterOrEqual(int versionCode) { + return SDK_INT >= versionCode; + } + + public static boolean isJellyBeanMR1() { + return isGreaterOrEqual(JELLY_BEAN_MR1); + } + + public static boolean isKitkat() { + return isGreaterOrEqual(KITKAT); + } + + public static boolean isLollipop() { + return isGreaterOrEqual(LOLLIPOP); + } + + public static boolean isOreo() { + return isGreaterOrEqual(O); + } + + public static boolean isAndroidQ() { + return isGreaterOrEqual(Q); + } +} diff --git a/build/android/app/src/main/java/com/multicraft/game/helpers/Constants.java b/build/android/app/src/main/java/com/multicraft/game/helpers/Constants.java new file mode 100644 index 000000000..85275423a --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/helpers/Constants.java @@ -0,0 +1,37 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game.helpers; + +import com.multicraft.game.BuildConfig; + +public class Constants { + public static final int REQUEST_CONNECTION = 104; + public static final int REQUEST_UPDATE = 102; + public static final int SESSION_COUNT = 5; + public static final String NO_SPACE_LEFT = "ENOSPC"; + public static final String FILES = "Files.zip"; + public static final String WORLDS = "worlds.zip"; + public static final String GAMES = "games.zip"; + public static final int versionCode = BuildConfig.VERSION_CODE; + public static final String versionName = BuildConfig.VERSION_NAME; + public static final String appPackage = BuildConfig.APPLICATION_ID; + public static final String UPDATE_LINK = "http://updates.multicraft.world/Android.json"; +} diff --git a/build/android/app/src/main/java/com/multicraft/game/helpers/PermissionHelper.java b/build/android/app/src/main/java/com/multicraft/game/helpers/PermissionHelper.java new file mode 100644 index 000000000..e850294f3 --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/helpers/PermissionHelper.java @@ -0,0 +1,136 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game.helpers; + +import android.annotation.SuppressLint; + +import androidx.appcompat.app.AppCompatActivity; + +import com.multicraft.game.R; +import com.multicraft.game.callbacks.CallBackListener; +import com.tedpark.tedpermission.rx2.TedRx2Permission; + +import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; + +public class PermissionHelper { + private final AppCompatActivity activity; + private CallBackListener listener; + private PreferencesHelper pf; + + public PermissionHelper(AppCompatActivity activity) { + this.activity = activity; + } + + public void setListener(CallBackListener listener) { + this.listener = listener; + } + + public void askPermissions() { + pf = PreferencesHelper.getInstance(activity); + askStoragePermissions(); + } + + // permission block + @SuppressLint("CheckResult") + private void askStoragePermissions() { + TedRx2Permission.with(activity) + .setPermissions(WRITE_EXTERNAL_STORAGE) + .request() + .subscribe(tedPermissionResult -> { + if (tedPermissionResult.isGranted()) { + if (pf.getLaunchTimes() % 3 == 1) + askLocationPermissions(); + else listener.onEvent(true); + } else { + if (TedRx2Permission.canRequestPermission(activity, WRITE_EXTERNAL_STORAGE)) + askStorageRationalePermissions(); + else askStorageWhenDoNotShow(); + } + }); + } + + // storage permissions block + @SuppressLint("CheckResult") + private void askStorageRationalePermissions() { + TedRx2Permission.with(activity) + .setRationaleMessage(R.string.explain) + .setDeniedMessage(R.string.denied) + .setDeniedCloseButtonText(R.string.close_game) + .setGotoSettingButtonText(R.string.settings) + .setPermissions(WRITE_EXTERNAL_STORAGE) + .request() + .subscribe(tedPermissionResult -> { + if (tedPermissionResult.isGranted()) { + if (pf.getLaunchTimes() % 3 == 1) + askLocationPermissions(); + else listener.onEvent(true); + } else { + listener.onEvent(false); + } + }); + } + + @SuppressLint("CheckResult") + private void askStorageWhenDoNotShow() { + TedRx2Permission.with(activity) + .setDeniedMessage(R.string.denied) + .setDeniedCloseButtonText(R.string.close_game) + .setGotoSettingButtonText(R.string.settings) + .setPermissions(WRITE_EXTERNAL_STORAGE) + .request() + .subscribe(tedPermissionResult -> { + if (tedPermissionResult.isGranted()) { + if (pf.getLaunchTimes() % 3 == 1) + askLocationPermissions(); + else listener.onEvent(true); + } else { + listener.onEvent(false); + } + }); + } + + // location permissions block + @SuppressLint("CheckResult") + private void askLocationPermissions() { + TedRx2Permission.with(activity) + .setPermissions(ACCESS_FINE_LOCATION) + .request() + .subscribe(tedPermissionResult -> { + if (tedPermissionResult.isGranted()) { + listener.onEvent(true); + } else { + if (TedRx2Permission.canRequestPermission(activity, ACCESS_FINE_LOCATION)) + askLocationRationalePermissions(); + else listener.onEvent(true); + } + }); + } + + @SuppressLint("CheckResult") + private void askLocationRationalePermissions() { + TedRx2Permission.with(activity) + .setRationaleMessage(R.string.location) + .setPermissions(ACCESS_FINE_LOCATION) + .request() + .subscribe(tedPermissionResult -> listener.onEvent(true)); + } +} diff --git a/build/android/app/src/main/java/com/multicraft/game/helpers/PreferencesHelper.java b/build/android/app/src/main/java/com/multicraft/game/helpers/PreferencesHelper.java new file mode 100644 index 000000000..4313eae5d --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/helpers/PreferencesHelper.java @@ -0,0 +1,132 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game.helpers; + +import android.content.Context; +import android.content.SharedPreferences; + +public class PreferencesHelper { + public static final String TAG_SHORTCUT_EXIST = "createShortcut"; + public static final String TAG_BUILD_NUMBER = "buildNumber"; + public static final String TAG_LAUNCH_TIMES = "launchTimes"; + public static final String TAG_COPY_OLD_WORLDS = "copyOldWorlds"; + public static final String TAG_LAST_RATE_VERSION_CODE = "lastRateVersionCode"; + public static final String TAG_RATE_MIN_VERSION_CODE = "rateMinVersionCode"; + public static final String TAG_EXIT_GAME_COUNT = "exitGameCount"; + public static final String TAG_REVIEW_ENABLE = "reviewEnable"; + public static final String TAG_OPT_OUT = "optOut"; + public static final String IS_ASK_CONSENT = "isAskConsent"; + public static final String IS_LOADED = "interstitialLoaded"; + public static final String RV_LOADED = "rewardedVideoLoaded"; + public static final String ADS_DELAY = "adsDelay"; + public static final String ADS_REPEAT = "adsRepeat"; + public static final String ADS_ENABLE = "adsEnable"; + private static final String SETTINGS = "MultiCraftSettings"; + + private static PreferencesHelper instance; + private static SharedPreferences sharedPreferences; + + private PreferencesHelper(Context context) { + sharedPreferences = context.getSharedPreferences(SETTINGS, Context.MODE_PRIVATE); + } + + public static PreferencesHelper getInstance(Context context) { + if (instance == null) { + synchronized (PreferencesHelper.class) { + if (instance == null) + instance = new PreferencesHelper(context.getApplicationContext()); + } + } + return instance; + } + + public boolean isCreateShortcut() { + return sharedPreferences.getBoolean(TAG_SHORTCUT_EXIST, false); + } + + public boolean isInterstitialLoaded() { + return sharedPreferences.getBoolean(IS_LOADED, false); + } + + public boolean isVideoLoaded() { + return sharedPreferences.getBoolean(RV_LOADED, false); + } + + public boolean isAskConsent() { + return sharedPreferences.getBoolean(IS_ASK_CONSENT, true); + } + + public boolean isWorldsCopied() { + return sharedPreferences.getBoolean(TAG_COPY_OLD_WORLDS, false); + } + + public String getBuildNumber() { + return sharedPreferences.getString(TAG_BUILD_NUMBER, "0"); + } + + public int getLaunchTimes() { + return sharedPreferences.getInt(TAG_LAUNCH_TIMES, 0); + } + + public int getLastRateVersionCode() { + return sharedPreferences.getInt(TAG_LAST_RATE_VERSION_CODE, 0); + } + + public int getRateMinVersionCode() { + return sharedPreferences.getInt(TAG_RATE_MIN_VERSION_CODE, 0); + } + + public int getExitGameCount() { + return sharedPreferences.getInt(TAG_EXIT_GAME_COUNT, 0); + } + + public int getAdsDelay() { + return sharedPreferences.getInt(ADS_DELAY, 600); + } + + public int getAdsRepeat() { + return sharedPreferences.getInt(ADS_REPEAT, 900); + } + + public boolean isAdsEnable() { + return sharedPreferences.getBoolean(ADS_ENABLE, true); + } + + public boolean isReviewEnable() { + return sharedPreferences.getBoolean(TAG_REVIEW_ENABLE, true); + } + + public boolean isOptOut() { + return sharedPreferences.getBoolean(TAG_OPT_OUT, false); + } + + public void saveSettings(String tag, boolean bool) { + sharedPreferences.edit().putBoolean(tag, bool).apply(); + } + + public void saveSettings(String tag, String value) { + sharedPreferences.edit().putString(tag, value).apply(); + } + + public void saveSettings(String tag, int value) { + sharedPreferences.edit().putInt(tag, value).apply(); + } +} diff --git a/build/android/app/src/main/java/com/multicraft/game/helpers/RateMeHelper.java b/build/android/app/src/main/java/com/multicraft/game/helpers/RateMeHelper.java new file mode 100644 index 000000000..9dc014ca7 --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/helpers/RateMeHelper.java @@ -0,0 +1,96 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game.helpers; + +import android.app.Dialog; +import android.app.NativeActivity; +import android.content.Intent; +import android.net.Uri; +import android.view.View; +import android.widget.RatingBar; +import android.widget.Toast; + +import com.multicraft.game.R; + +import static com.multicraft.game.helpers.ApiLevelHelper.isKitkat; +import static com.multicraft.game.helpers.Constants.SESSION_COUNT; +import static com.multicraft.game.helpers.PreferencesHelper.TAG_OPT_OUT; +import static com.multicraft.game.helpers.Utilities.getStoreUrl; + +public class RateMeHelper { + private static PreferencesHelper pf; + + public static void onStart(NativeActivity activity) { + pf = PreferencesHelper.getInstance(activity); + } + + private static boolean isEnoughTimePassed() { + return (pf.getLastRateVersionCode() == 0) && pf.getLaunchTimes() >= SESSION_COUNT + && pf.getExitGameCount() >= SESSION_COUNT; + } + + private static boolean isWorthToReview() { + return (pf.getLastRateVersionCode() != 0) + && pf.getRateMinVersionCode() > pf.getLastRateVersionCode(); + } + + public static boolean shouldAskForReview() { + return pf.isReviewEnable() && (isEnoughTimePassed() || isWorthToReview()); + } + + public static boolean shouldShowRateDialog() { + if (pf.isOptOut()) + return false; + else { + return shouldAskForReview(); + } + } + + public static void showRateDialog(NativeActivity activity) { + final Dialog dialog = new Dialog(activity, R.style.RateMe); + dialog.setCancelable(false); + if (isKitkat()) + dialog.getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + dialog.setContentView(R.layout.rate_dialog); + RatingBar ratingBar = dialog.findViewById(R.id.ratingBar); + ratingBar.setOnRatingBarChangeListener((ratingBar1, rating, fromUser) -> { + if (rating >= 4) { + try { + Toast.makeText(activity, R.string.thank, Toast.LENGTH_LONG).show(); + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getStoreUrl())); + activity.startActivity(intent); + } catch (Exception e) { + // nothing + } + dialog.dismiss(); + pf.saveSettings(TAG_OPT_OUT, true); + } else { + dialog.dismiss(); + Toast.makeText(activity, R.string.sad, Toast.LENGTH_LONG).show(); + } + }); + if (!activity.isFinishing()) + dialog.show(); + } +} diff --git a/build/android/app/src/main/java/com/multicraft/game/helpers/Utilities.java b/build/android/app/src/main/java/com/multicraft/game/helpers/Utilities.java new file mode 100644 index 000000000..09d08b967 --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/helpers/Utilities.java @@ -0,0 +1,180 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game.helpers; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.view.View; + +import com.bugsnag.android.Bugsnag; +import com.multicraft.game.MainActivity; +import com.multicraft.game.R; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import static android.os.Environment.getExternalStorageDirectory; +import static com.multicraft.game.helpers.ApiLevelHelper.isKitkat; +import static com.multicraft.game.helpers.Constants.FILES; +import static com.multicraft.game.helpers.Constants.GAMES; +import static com.multicraft.game.helpers.Constants.WORLDS; +import static com.multicraft.game.helpers.Constants.appPackage; +import static com.multicraft.game.helpers.PreferencesHelper.TAG_SHORTCUT_EXIST; + +public class Utilities { + private static boolean isInternetAvailable(String url) { + try { + HttpURLConnection urlc = + (HttpURLConnection) new URL(url).openConnection(); + urlc.setRequestProperty("Connection", "close"); + urlc.setConnectTimeout(2000); + urlc.connect(); + int ResponseCode = urlc.getResponseCode(); + return ResponseCode == HttpURLConnection.HTTP_NO_CONTENT || + ResponseCode == HttpURLConnection.HTTP_OK; + } catch (IOException e) { + return false; + } + } + + public static boolean isReachable() { + return isInternetAvailable("http://clients3.google.com/generate_204") || + isInternetAvailable("http://servers.multicraft.world"); + } + + public static void deleteFiles(List files, String path) { + for (String f : files) { + File file = new File(path, f); + if (file.exists()) + FileUtils.deleteQuietly(file); + } + } + + public static void deleteFiles(List files) { + for (File file : files) { + if (file != null && file.exists()) + FileUtils.deleteQuietly(file); + } + } + + public static String getStoreUrl() { + return "market://details?id=" + appPackage; + } + + public static void makeFullScreen(Activity activity) { + if (isKitkat()) + activity.getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + public static Drawable getIcon(Activity activity) { + try { + return activity.getPackageManager().getApplicationIcon(activity.getPackageName()); + } catch (PackageManager.NameNotFoundException e) { + Bugsnag.notify(e); + return activity.getResources().getDrawable(R.mipmap.ic_launcher); + } + } + + public static void addShortcut(Activity activity) { + ActivityManager activityManager = + (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE); + int size = 0; + if (activityManager != null) + size = activityManager.getLauncherLargeIconSize(); + Bitmap shortcutIconBitmap = ((BitmapDrawable) getIcon(activity)).getBitmap(); + if (shortcutIconBitmap.getWidth() != size || shortcutIconBitmap.getHeight() != size) + shortcutIconBitmap = Bitmap.createScaledBitmap(shortcutIconBitmap, size, size, true); + PreferencesHelper.getInstance(activity).saveSettings(TAG_SHORTCUT_EXIST, true); + Intent shortcutIntent = new Intent(activity, MainActivity.class); + shortcutIntent.setAction(Intent.ACTION_MAIN); + Intent addIntent = new Intent(); + addIntent.putExtra("duplicate", false); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, activity.getResources().getString(R.string.app_name)); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, shortcutIconBitmap); + addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + activity.getApplicationContext().sendBroadcast(addIntent); + } + + public static String getJson(String url) { + OkHttpClient client = new OkHttpClient.Builder() + .callTimeout(3000, TimeUnit.MILLISECONDS) + .build(); + Request request = new Request.Builder() + .url(url) + .build(); + try { + Response response = client.newCall(request).execute(); + return response.body().string(); + } catch (IOException | NullPointerException e) { + return "{}"; + } + } + + public static String getLocationByZip(Context context, String zipName) { + String path; + switch (zipName) { + case FILES: + case GAMES: + path = context.getFilesDir().toString(); + break; + case WORLDS: + try { + path = context.getExternalFilesDir(null).toString(); + } catch (NullPointerException e) { + path = getExternalStorageDirectory() + "/Android/data/com.multicraft.game/files"; + } + break; + default: + throw new IllegalArgumentException("No such zip name"); + } + return path; + } + + public static ArrayList getZipsFromAssets(Context context) { + try { + return new ArrayList<>(Arrays.asList(context.getAssets().list("data"))); + } catch (IOException e) { + return new ArrayList<>(Arrays.asList(FILES, GAMES, WORLDS)); + } + } +} diff --git a/build/android/app/src/main/java/com/multicraft/game/helpers/VersionManagerHelper.java b/build/android/app/src/main/java/com/multicraft/game/helpers/VersionManagerHelper.java new file mode 100644 index 000000000..e2bbfc491 --- /dev/null +++ b/build/android/app/src/main/java/com/multicraft/game/helpers/VersionManagerHelper.java @@ -0,0 +1,157 @@ +/* +MultiCraft +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package com.multicraft.game.helpers; + +import android.content.Intent; +import android.net.Uri; +import android.text.Editable; +import android.text.Html; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.PreferenceManager; + +import com.google.gson.Gson; +import com.multicraft.game.JsonSettings; + +import org.xml.sax.XMLReader; + +import java.util.Calendar; +import java.util.List; +import java.util.Locale; + +import static com.multicraft.game.helpers.Constants.versionCode; +import static com.multicraft.game.helpers.PreferencesHelper.ADS_DELAY; +import static com.multicraft.game.helpers.PreferencesHelper.ADS_ENABLE; +import static com.multicraft.game.helpers.PreferencesHelper.ADS_REPEAT; +import static com.multicraft.game.helpers.PreferencesHelper.TAG_RATE_MIN_VERSION_CODE; +import static com.multicraft.game.helpers.PreferencesHelper.TAG_REVIEW_ENABLE; +import static com.multicraft.game.helpers.Utilities.getStoreUrl; + +public class VersionManagerHelper { + private final CustomTagHandler customTagHandler; + private final String PREF_REMINDER_TIME = "w.reminder.time"; + private final AppCompatActivity activity; + private String message, updateUrl; + + public VersionManagerHelper(AppCompatActivity act) { + this.activity = act; + this.customTagHandler = new CustomTagHandler(); + } + + public boolean isCheckVersion() { + long currentTimeStamp = Calendar.getInstance().getTimeInMillis(); + long reminderTimeStamp = getReminderTime(); + return currentTimeStamp > reminderTimeStamp; + } + + private boolean isBadVersion(List badVersions) { + return badVersions.contains(versionCode); + } + + private JsonSettings parseJson(String result) { + JsonSettings settings = new Gson().fromJson(result, JsonSettings.class); + String lang = Locale.getDefault().getLanguage(); + String content = lang.equals("ru") ? settings.getContentRus() : settings.getContentEng(); + setMessage(content); + setUpdateUrl("market://details?id=" + settings.getPackageName()); + savePrefSettings(settings); + return settings; + } + + private void savePrefSettings(JsonSettings settings) { + PreferencesHelper pf = PreferencesHelper.getInstance(activity); + pf.saveSettings(TAG_RATE_MIN_VERSION_CODE, settings.getRateMinVersionCode()); + pf.saveSettings(TAG_REVIEW_ENABLE, settings.isReviewEnabled()); + pf.saveSettings(ADS_DELAY, settings.getAdsDelay()); + pf.saveSettings(ADS_REPEAT, settings.getAdsRepeat()); + pf.saveSettings(ADS_ENABLE, settings.isAdsEnabled()); + } + + public boolean isShow(String result) { + if (result.equals("{}")) return false; + JsonSettings jsonSettings = parseJson(result); + return (versionCode < jsonSettings.getVersionCode()) || + isBadVersion(jsonSettings.getBadVersionCodes()); + } + + public String getMessage() { + String defaultMessage = "What's new?"; + return message != null ? message : defaultMessage; + } + + private void setMessage(String message) { + this.message = message; + } + + public String getUpdateUrl() { + return updateUrl != null ? updateUrl : getStoreUrl(); + } + + private void setUpdateUrl(String updateUrl) { + this.updateUrl = updateUrl; + } + + public void updateNow(String url) { + if (url != null) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + activity.startActivity(intent); + } catch (Exception e) { + // nothing + } + } + } + + public void remindMeLater() { + Calendar c = Calendar.getInstance(); + c.add(Calendar.MINUTE, 1); + long reminderTimeStamp = c.getTimeInMillis(); + setReminderTime(reminderTimeStamp); + } + + private long getReminderTime() { + return PreferenceManager.getDefaultSharedPreferences(activity).getLong( + PREF_REMINDER_TIME, 0); + } + + private void setReminderTime(long reminderTimeStamp) { + PreferenceManager.getDefaultSharedPreferences(activity).edit().putLong( + PREF_REMINDER_TIME, reminderTimeStamp).apply(); + } + + public CustomTagHandler getCustomTagHandler() { + return customTagHandler; + } + + private static class CustomTagHandler implements Html.TagHandler { + @Override + public void handleTag(boolean opening, String tag, Editable output, + XMLReader xmlReader) { + // you may add more tag handler which are not supported by android here + if ("li".equals(tag)) { + if (opening) + output.append(" \u2022 "); + else + output.append("\n"); + } + } + } +} diff --git a/build/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java b/build/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java deleted file mode 100644 index 6d4b6ab0f..000000000 --- a/build/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java +++ /dev/null @@ -1,82 +0,0 @@ -/* -Minetest -Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik -Copyright (C) 2014-2020 ubulem, Bektur Mambetov - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -package net.minetest.minetest; - -import android.content.Intent; -import android.os.AsyncTask; -import android.widget.Toast; - -import androidx.appcompat.app.AppCompatActivity; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.ref.WeakReference; - -public class CopyZipTask extends AsyncTask { - - private final WeakReference activityRef; - - CopyZipTask(AppCompatActivity activity) { - activityRef = new WeakReference<>(activity); - } - - protected String doInBackground(String... params) { - copyAsset(params[0]); - return params[0]; - } - - @Override - protected void onPostExecute(String result) { - startUnzipService(result); - } - - private void copyAsset(String zipName) { - String filename = zipName.substring(zipName.lastIndexOf("/") + 1); - try (InputStream in = activityRef.get().getAssets().open(filename); - OutputStream out = new FileOutputStream(zipName)) { - copyFile(in, out); - } catch (IOException e) { - AppCompatActivity activity = activityRef.get(); - if (activity != null) { - activity.runOnUiThread(() -> Toast.makeText(activityRef.get(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show()); - } - cancel(true); - } - } - - private void copyFile(InputStream in, OutputStream out) throws IOException { - byte[] buffer = new byte[1024]; - int read; - while ((read = in.read(buffer)) != -1) - out.write(buffer, 0, read); - } - - private void startUnzipService(String file) { - Intent intent = new Intent(activityRef.get(), UnzipService.class); - intent.putExtra(UnzipService.EXTRA_KEY_IN_FILE, file); - AppCompatActivity activity = activityRef.get(); - if (activity != null) { - activity.startService(intent); - } - } -} diff --git a/build/android/app/src/main/java/net/minetest/minetest/GameActivity.java b/build/android/app/src/main/java/net/minetest/minetest/GameActivity.java deleted file mode 100644 index 635512569..000000000 --- a/build/android/app/src/main/java/net/minetest/minetest/GameActivity.java +++ /dev/null @@ -1,126 +0,0 @@ -/* -Minetest -Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik -Copyright (C) 2014-2020 ubulem, Bektur Mambetov - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -package net.minetest.minetest; - -import android.app.NativeActivity; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.view.View; -import android.view.WindowManager; - -public class GameActivity extends NativeActivity { - static { - System.loadLibrary("c++_shared"); - System.loadLibrary("Minetest"); - } - - private int messageReturnCode; - private String messageReturnValue; - - public static native void putMessageBoxResult(String text); - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - messageReturnCode = -1; - messageReturnValue = ""; - } - - private void makeFullScreen() { - if (Build.VERSION.SDK_INT >= 19) - this.getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus) - makeFullScreen(); - } - - @Override - protected void onResume() { - super.onResume(); - makeFullScreen(); - } - - @Override - public void onBackPressed() { - // Ignore the back press so Minetest can handle it - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == 101) { - if (resultCode == RESULT_OK) { - String text = data.getStringExtra("text"); - messageReturnCode = 0; - messageReturnValue = text; - } else - messageReturnCode = 1; - } - } - - public void showDialog(String acceptButton, String hint, String current, int editType) { - Intent intent = new Intent(this, InputDialogActivity.class); - Bundle params = new Bundle(); - params.putString("acceptButton", acceptButton); - params.putString("hint", hint); - params.putString("current", current); - params.putInt("editType", editType); - intent.putExtras(params); - startActivityForResult(intent, 101); - messageReturnValue = ""; - messageReturnCode = -1; - } - - public int getDialogState() { - return messageReturnCode; - } - - public String getDialogValue() { - messageReturnCode = -1; - return messageReturnValue; - } - - public float getDensity() { - return getResources().getDisplayMetrics().density; - } - - public int getDisplayHeight() { - return getResources().getDisplayMetrics().heightPixels; - } - - public int getDisplayWidth() { - return getResources().getDisplayMetrics().widthPixels; - } - - public void openURL(String url) { - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(browserIntent); - } -} diff --git a/build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java b/build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java deleted file mode 100644 index 7c6aa111d..000000000 --- a/build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java +++ /dev/null @@ -1,98 +0,0 @@ -/* -Minetest -Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik -Copyright (C) 2014-2020 ubulem, Bektur Mambetov - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -package net.minetest.minetest; - -import android.app.Activity; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.text.InputType; -import android.view.KeyEvent; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; - -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; - -import java.util.Objects; - -public class InputDialogActivity extends AppCompatActivity { - private AlertDialog alertDialog; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle b = getIntent().getExtras(); - int editType = Objects.requireNonNull(b).getInt("editType"); - String hint = b.getString("hint"); - String current = b.getString("current"); - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - EditText editText = new EditText(this); - builder.setView(editText); - editText.requestFocus(); - editText.setHint(hint); - editText.setText(current); - final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - Objects.requireNonNull(imm).toggleSoftInput(InputMethodManager.SHOW_FORCED, - InputMethodManager.HIDE_IMPLICIT_ONLY); - if (editType == 3) - editText.setInputType(InputType.TYPE_CLASS_TEXT | - InputType.TYPE_TEXT_VARIATION_PASSWORD); - else - editText.setInputType(InputType.TYPE_CLASS_TEXT); - editText.setOnKeyListener((view, KeyCode, event) -> { - if (KeyCode == KeyEvent.KEYCODE_ENTER) { - imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); - pushResult(editText.getText().toString()); - return true; - } - return false; - }); - alertDialog = builder.create(); - if (!this.isFinishing()) - alertDialog.show(); - alertDialog.setOnCancelListener(dialog -> { - pushResult(editText.getText().toString()); - setResult(Activity.RESULT_CANCELED); - alertDialog.dismiss(); - makeFullScreen(); - finish(); - }); - } - - private void pushResult(String text) { - Intent resultData = new Intent(); - resultData.putExtra("text", text); - setResult(AppCompatActivity.RESULT_OK, resultData); - alertDialog.dismiss(); - makeFullScreen(); - finish(); - } - - private void makeFullScreen() { - if (Build.VERSION.SDK_INT >= 19) - this.getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } -} diff --git a/build/android/app/src/main/java/net/minetest/minetest/MainActivity.java b/build/android/app/src/main/java/net/minetest/minetest/MainActivity.java deleted file mode 100644 index 2aa50d9ad..000000000 --- a/build/android/app/src/main/java/net/minetest/minetest/MainActivity.java +++ /dev/null @@ -1,153 +0,0 @@ -/* -Minetest -Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik -Copyright (C) 2014-2020 ubulem, Bektur Mambetov - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -package net.minetest.minetest; - -import android.Manifest; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Bundle; -import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static net.minetest.minetest.UnzipService.ACTION_FAILURE; -import static net.minetest.minetest.UnzipService.ACTION_PROGRESS; -import static net.minetest.minetest.UnzipService.ACTION_UPDATE; -import static net.minetest.minetest.UnzipService.FAILURE; -import static net.minetest.minetest.UnzipService.SUCCESS; - -public class MainActivity extends AppCompatActivity { - private final static int versionCode = BuildConfig.VERSION_CODE; - private final static int PERMISSIONS = 1; - private static final String[] REQUIRED_SDK_PERMISSIONS = - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; - private static final String SETTINGS = "MinetestSettings"; - private static final String TAG_VERSION_CODE = "versionCode"; - private ProgressBar mProgressBar; - private TextView mTextView; - private SharedPreferences sharedPreferences; - private final BroadcastReceiver myReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - int progress = 0; - if (intent != null) - progress = intent.getIntExtra(ACTION_PROGRESS, 0); - if (progress >= 0) { - if (mProgressBar != null) { - mProgressBar.setVisibility(View.VISIBLE); - mProgressBar.setProgress(progress); - } - mTextView.setVisibility(View.VISIBLE); - } else if (progress == FAILURE) { - Toast.makeText(MainActivity.this, intent.getStringExtra(ACTION_FAILURE), Toast.LENGTH_LONG).show(); - finish(); - } else if (progress == SUCCESS) - startNative(); - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - IntentFilter filter = new IntentFilter(ACTION_UPDATE); - registerReceiver(myReceiver, filter); - mProgressBar = findViewById(R.id.progressBar); - mTextView = findViewById(R.id.textView); - sharedPreferences = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - checkPermission(); - else - checkAppVersion(); - } - - private void checkPermission() { - final List missingPermissions = new ArrayList<>(); - for (final String permission : REQUIRED_SDK_PERMISSIONS) { - final int result = ContextCompat.checkSelfPermission(this, permission); - if (result != PackageManager.PERMISSION_GRANTED) - missingPermissions.add(permission); - } - if (!missingPermissions.isEmpty()) { - final String[] permissions = missingPermissions - .toArray(new String[0]); - ActivityCompat.requestPermissions(this, permissions, PERMISSIONS); - } else { - final int[] grantResults = new int[REQUIRED_SDK_PERMISSIONS.length]; - Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED); - onRequestPermissionsResult(PERMISSIONS, REQUIRED_SDK_PERMISSIONS, grantResults); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, - @NonNull String[] permissions, @NonNull int[] grantResults) { - if (requestCode == PERMISSIONS) { - for (int grantResult : grantResults) { - if (grantResult != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show(); - finish(); - } - } - checkAppVersion(); - } - } - - private void checkAppVersion() { - if (sharedPreferences.getInt(TAG_VERSION_CODE, 0) == versionCode) - startNative(); - else - new CopyZipTask(this).execute(getCacheDir() + "/Minetest.zip"); - } - - private void startNative() { - sharedPreferences.edit().putInt(TAG_VERSION_CODE, versionCode).apply(); - Intent intent = new Intent(this, GameActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); - } - - @Override - public void onBackPressed() { - // Prevent abrupt interruption when copy game files from assets - } - - @Override - protected void onDestroy() { - super.onDestroy(); - unregisterReceiver(myReceiver); - } -} diff --git a/build/android/app/src/main/res/drawable/background.png b/build/android/app/src/main/res/drawable/background.png index 43bd6089ec30a1ebd68f90296cb8d4633c0dde14..55afbb6f4e16ee5f4d9c8b9483a909457b9c3c3d 100644 GIT binary patch literal 8482 zcmV+-A>H1IP)vACr&q zHZK%Al}>DyI&oATkB!nOyY{{c!hTgOj%yQeT$g|ojzw2E#-H6nAUDLGyI%#vm%}y3 z@$zUx$|IjlIek zeA$wW)#6A5xc3s{`*TC#eI0CCl*ey<8E_m>;2}5rr6&WMWzlB9R=E>%u231HdoqEW zCt{HadntIWB$9!Guv;1Pmxm_!a%h4p628|VaC3BJTt#ozHH^`O-b`Ct5 z#F`P?j45OETn4OhFWvu~=B&^J#~dFVR>k3e6!?}eEHQxZO$a7fWeTE5Qc)X^(}qNu zBQj{#eU5ufEP>8Om{AisG$213tE5O~9oi--?-G4S0q5HYkf5oTcSu1vmW!gXUgE?G z$6A?_V!|0SnS%z@rx*eMYv48KBgL#qzz;nS@KbMw$LD?Ea?t=j86 zQX)WxsSu)Qry`CfKEN8ObIcK+A)eU{zSy626~kP&rd`Cy(;Jvz^%Q_JDM*kJMuFV> zeIP?dh!Uekl~<*H=)OcQD z+EhRMjd?iW_d7(|!@WdQfHaBRDfS!n3>9gNf<13S(f#0x>2Q;d1yZ5PzZb7Ukvf?X zYD6j*$N1A*a?Ap2t?DrPcXmPLS@4=I3w4STKa zOt;Vgt$WzeJbWfH^!$)VhORSksG+4Kl#n);=p!Qhe+pkOb8?-icMY}wHzwn=_WRi5 z+5S)0BcnkLPF1i`oD5(6OC0(~C7veqVXm_r(=I1GMe6wPDma_-Zol34XZ(;=O=L!0(peL$wZ!XwZ{{^t;+AbJ6YSST(K14nw$97OcR))1-;ssi`ZMu+ zf0j|=uXzNI2&j9BmB9uq_SPcMn1l76GEBaS#J^SBWqViG7A^4qce!tlpL!`3AUL;J z-Q_?ySb|$iaCZpqB!NP`Pm$y9{_1|6dB^Wr*+*WiXGi|od(AP&98=cLg~66hvd0M6 zWQNyocQz1c6erlj!PmZD*dXNWgHti_cz{roaLj`(v$L1^^^>9h8zEdk1Y5qpwCG8O z*R6qPDSUG6>>6D;wJs&aTwm5#`(OEfDI#bC&ntEy4S@XtW1sKmeG7XSAIi@R_4*3+ zfBfOB6fm=k7o;D0Z_|r=`OJZrA1)xAT=#wrZ(LI7IktXbC_JHdzwmvG!vaDq zCHuPoIKLRZIi3dqPoGbkMSpRBWYLs58_Hi;KHbkbCc-&qB;J25|& zqC`mW+DDA zadv$jw445l&kYIMclaa6KVobiZyrOv4`7f(<^yLTP$pBhnpqlVYNTQ?4Ft#e7I z*TU!+-xw%FX#nG|E>Tb@d!HHa119C>Q-P7ZKCgbab3VG+WDsstRNA_t_N7&GLOYe6 zn*6Cc@gB8}!Y@mV4onk=t<23e0H_NHqrU|#gXlzl_N?^kzA{c&_mI3@M*?mKh%_b) zgq0LWzc5bDS9B@1JnyIb->y%#zgLG>zg1($c1o#rVdwf9Nm!{ofAqm@C3W>e$K=-u ziMhws|8+4p^))s$7EC5q9;gq%1{Cu*@^aEpj9I@xdaL1x*zt(gxumlbq4I^nS#@)8 zTBtWoDzX?JO=)S79z2LQG&VE`1Jnzs&M=|T8ZNA8>Dlkp0KA9+?x_~=b6G;|!BM|B z(qFfCEUpt9`Xi<*>XS1EYV^=hjjZU)Gv@^SIZXtWk(Xmk(nZAVdm0&rVge9?>O?hv zJoqfcTDlBSCvv^H@;Mu+v32w6*1B1Bb^G!<_uL4n_S; z8sv3`ccB1q^#zaT{*NlcgqvpyF_j?k6%x8atGh?&{}jVH%d_gi(EPf+KA^6jUl)g# z*X^TQ>*~;w8eTfRZmyYG_alV2!|)#Q@elEYMgoj}!KRKW zpq8edPr)*BbVuD8-dpFkudNeN@qL4DQ)6k4qFc0pS9OAQD`AE`3LA#l_{Rg20>Bdh zuqTzL=fvPvV|1B7&mxpQBiPv&!wKvsVL(DP3O~C5^I+@TFfMk4_7}IWtmEq!)sf}x zXR6RU*-LNF3*W7)X)2_RJuiGO>FE0fgs#Amd&L;0TnivPFP~ETWA8?px2~*nJJ#0C zle_BEbBAg)@pgLas@k>e<&qtUm;y?gQW$N|&&05BLwx}7v_gtO2a;}mZQgKkoDKBJ zy@0_{bOH0eP8fZ?X{Im&fc80n{5#Et!PDwyG-V_qdM@(<*~Pm1;rzP0X;xi}Q1`^7 zPHk_D_b`JDxmCK8+W{d{=(~X+{tZ8lr=i*1zo2%nc&$#w;A<&eM^EmlvqP&w|5xky z(%GrtCir^*sLkbT8GsQfsiy*%r(3xlg_lNM0Wkb#o)d#To8dx}+S7vr2~{AXy29q8 zRg(#yLMth~Rd~Ycb{nPyoXNmZc^bZOpysWGz)6A=N?%G0(E&pOfY=<`@wA2dsw@mp zSsjYlZCkXU_J`^_2HvZ4J2%wvjmzrf#{N3|eqZwV1Zgp8s{K9V8NrjgVhE!NmwEz7 zjpnH#`nBYb*V(Mm<UPFlZTOcT|6lOx;R>19LaFJX;zkYpqQT<}i>-FnBFO?CQ zX^W!7<{3@!?xv{W5gy&0klFb{m=Y!&N#0i-?s{Wl`8!{$6AVVAGbQo0fFZ1?InM(E z6VQ2RUYe~i0RwgfjD4>(AQ%P&F@0Sh5Fk8XsD8d-mWohT!2)!bT4_OBD*#}UHKY9f zaE$jXCGoBNT)5l>faWK86>3qIQ2}7t5N#lk#Bb=u#|l#{hHuO7FhF`#H1W7%N+E+;pKO?m z-YD(&u8a=N`*=63m@FGGR3&O?0tmLs7GX&=pc`-9-TZuzn}T{;VMR$Ze(?lXMo2@( zPJX97&U#p77Q-K6yr!syNa$%e%JZLPGwOdo=XHw*#q{+ISEbx-!XqUuFn8s?0U*YI zr;g@xw5MYN<1eS27XPDbX4PjK(q3z=<9Rg(sxw^dTa1?ph^4`c(9>HGP@h+k_KdJT z%SMx16+Wu1%mU-WiNy$?{iF1rFNa1}V6cFN#%mAVn zUqxtia1>kSu-wB;zBB$e*~DKDERcC(L<+J9EFcs}M3dlBaLv$#t%G%XXl-5IzA7WL zeOY^IWbZl*NSIBkb4#X_l2}Ry!o`5GGy4>t7USK=EYl$nCiL!ZoE|DE-bWp+D%4DswveflnXlK^X_-jc`sy!j_N$v%GVoQ4f@uxlW zvzfm7!>)PtWj2K&SF?G>x8m#(_u;{0orvkc%XK}C&9(iTYUIGC3{aNV*p|05_?RkO zo6sc$IK;5jJ^bzYgr*wjZ0;rWEh5M(gpZ+?M#$>Cj^0*uq6wm2z+BL90tmlJhLA~2 zg?(OnGVoB1`5YF=D7?TxWw0{^1D6g2`h#hTAC;f9CmqWUbO8&@2G8`z+XGYU)T&gV zn^)AS7(e{MV4drKRu`)X!h&v0PdJ5vx<=-Rv*uwyt_#Xud*btMDk9$~hya4pt^j}> zO~3&0?g|9c7fW`K@1-jRj76~I>@St0fQU$nL4T{h_xFY^*p@mZ_n4Ued}cJG&$BJn zrn86?Pv0w<0)Sd@z#d;VtF+>0(s3SJ^jt>j(U}yoNpUr!qpLNsLu{ATwDbLtIGa9R$4@gtk zgh^_8#-nzO&DT>Xvd@QQts`VSox!Xw{c=J(uY9QNQP$87V}t!@t$)4$b=1%GHTU;( zy5w{0>CQS^NT$T+RBY{`s)I$<(Upvpc#-kapnAV+A+0NU0K*yKGxEK^aUC^e7<$3I z{7w~U11*%sdkvXFSYR0djNNpAyQI5#p6H#4`Wv{QF96I76;hq}G9Ej!Z+X6r$oIZf zzc~0_S)SnlSB5$@9-lO*LeGW?hfz0`1^^MV7)^Yrs3pBnf7Kx{d^V`Z zYnjI8jQ&<)oyUy1>4`#v5URRtjNvwe9m1&_aqUl>dp^1|ToxjT(&B%g?g9b>7;dTL zT*pH3-yV7^C2%_@uqU+dV&39?Ai8?4@cjAE^VPFKg5$$VMF);fR1Ut}W&{Uv9EbdDqf-Bri`hoiU^H8C7OyIMHJaK)d!8k{Q=I6!Rg8UDS-#uvApNd7dBuSJ;MMMsO%yYV_Z*;m1eG| z$#CkUuRfSlnvObMVbjgpQ`MR(O8g>YcOF2Gc__h9aIO;xK zx|9;Bu`6LRf#MaZP1_9w-T;KJHA9$TgS;`fKG7ToAKM{ zev5;F#R<}+9;b=yq5vSZQCEiXwi_`(7Vxzc!eAl>1S4o)D`$VA-VY;GU=X+bjn7>} zcbsR^)p1v7+) zXJU-2`KG@GfTf`VKm;}b>TcBmkTo_6vYLv9>fqdx9ZsgoI{!m&a_G0)t1l|7ZjS#O zO+^HW7XWE`%saHfAmodbqQuT-#P^95FlQE1>DDqI%fY8GyO6LjXa&HP{LXQgmmKr} z!bDeSG2YuH{?_W!G7#)doCB6kJic|#H`A`S(cDkc^x5OlRE)!mo+%kdsP()@BfZ>N zml2Z+(9zP+;<~)~-MV>bQ{CCwpT3^bPQ?a&w?%{$B5p4AVGPQt!jfL*)K9|{jM5(4 z?1gMr&L&FYUsq{S{qAiqA$30~ln4R?0}Nhtal)%h&lu#@9@-+EeYUC@uj%4xVdZLK zWz>%mO7AZ-MmzGhm${xXlV~DHP|$CLhz!iAo9VGXI1?O@-CY zDy>w5+m_Tvr}o#VVMF@utGjaGvGJuE-ST>w9y81hyh_Jogorn!($33nEK@j8l|BC7 zu~*A7eBc!+(90IQ3j`A+EC4JZXpxNLIll^nE-al|*H=tsH1OyZz=eY_#i|BE7|J22 zujjx5AecD$J>@^Yxaks|8P4CkSI;p~yw=9b+Xr%h_+ee#ogO>qdp8bktC1XWE0Y=T zl{ICp?>(IONMC5i@bmeZ(;hl89x4<$EpKCj!KBibA( zLTFCDp3^3}@w}-H<9M9_`^?>TZRPbi^AufX9M~xRbpm`X&jkV@O(v5}8gIg|&0LsP_H57-c?S z>Lj*3*kb(Q!I$ggrX_W0OGa92=Q!GqL2b78llDf5w@6y12>|ZX^W9Vg@*KOyu=dG5asza+(?w5t#^k!s)PiU~yh_&d$OQ~i_cWHJx26t2QS zCQu+@44`^>aT&E3;nanaCwD)AKEi^+qq6amC2nj6SOH>!D+Wkesy7U12Nr8%_|})p zRXSw6wSVBPI+;n%e;Ysg!Ry8AG=vbMsLbS8%ed`NNxAoO2&$=~3o^`C*^_sCbekOu zEaGX;jAv}e0F0Emy!y*Y*n*WkRiAeRo!dz_4k;+9+Fs1+3*3m&01yT`Ib&;vi+{#< zgA*W(25H&h%Z=@QU*0#b2f)GAd23_+(&~&qvuSZ%*!X7M-~M7LX5{qY@ndya0PB7e zPyccMf}8+_Nl|n6vwk131i+6QJBhP;1qeoGSsAFrNepHoxuOm4< zKDIVY$a@4gH@#Ty%Lw5{9~`JS_5E4uiQnunzsrgi5LEjzXW&sx_-(|s%_g4GpXe8W zK%=srQ(PYB2)zLpXh?=!t0=O%YDoY0M(iP~-s1H4D)VP0n76~;51!aH2(UXR6; zuGlyjK!9>0sZFUF<25oSGsa!*M2OBTnpVf&i{LJqo>yq*w)?#C1knqFT<4U<_y=JS zPusAod-AlsiF*bZp%deg2(>|prIzqJAjw!_U%Z<+9L^q;(6ZRQjOTlILxpoGHUN-g zq{P_#Jg`-9YJFD#xMq+LW18`5c==c#&+}&ZyVEkO>n&Q~g$hlN2LtJlzTEph(v#QR zmrfy8-rUrw$p-Z;Sf&2U3a!$!bJIl@Ev3LI~r zgawZ%YXP7rag{#vtl6nHXDG!J+Y61YWdPn(6glZJ>PC0Wv;d)FB%nNtAq;goq$&(} zGZ`s00s+Ha=^FqHW-}35Kf@m?Ctc0y30Nk%a}^Z;P)my$i(#11^#IQGoaV>|m>>f% z*ivs}3qTleb+5WH9H_uRTXVhS{DlE~VL9w{*1#|ToR!8_Ov4sVVz`wwIkBW`2Ja2P zXwAz&aOm+lMgTAO=*~B!Nvr{aB@1{^d#7f@>=J8cdg$XYR0h!9`QM_y9$ZL7It?&Y z5#LU7JXk#H(AvRD?%TcDEw)j=YgEhwY^tTa#QolP>P{v}zdrnf`uxL%CG8SD`r|Y9 zcV)%&bdt&GS=UxhPb)pKuBAjCdhu~F?WJj}+yrLIoZnWtxAg74YIaDX%sRD4`Y zjqLw)ZbhvXEza~jnCHJtzWiMVB8m`AiW?q#H|;Fr8W6mn)nc&4W;(~jt+6vExXLh2 zXc+HrfI`i^-Fahq`jLU@Nrf-h7hC2TbDJG_1Jyf)j@SY~KD)H9w9_Lir_}`~LCa^> zsdt+vdHrgJF$y005&{hVNy3O2bD~gS9~=Cx0iX}ahovqRo)3w$@#AR7r6!+Qj+1_p zDKBH_7Nv3vaoV5|YRBX4 zom+J`TJVmzgCG!c^W1DnD=-uj;@`ii?RgCdPcV-04mWMWvDSZqFbO?dc$gQo*V)A~ zfpp4D6ahd$>5I$)1YQ(=;aL%6?oY+7Z zXw>t2?-)x}4ktR${^td(CZvAHcsk?SO7#2=0BQ~(0MIG1aH8se-m{>1+5a)I#Z`u4 z_^~iex!=tZT@6t%ok(IpsNG6Q=&85;%uMg<`1lz!2wU?~1EAZuGarN4TOBgz;vQ4p-c3)M(vib0pdzHf!!^kLqC+>uAcAb0KpKcEQau^%rUP!ZDzdx zMFc>5lRQii=EiqjSa>I_iH=-+#OQ%vD<1K?p6(y>B1`t$=VTu1nwHgRsZZO?3+%$62dtea|cwVuE=8ns} z+~I`_U`UI!KvSpoXS`I}FeaU{sWU5P*Xd=`>)6|^+XMmuJrnx-nkJ}K z>>We9(`H)sAWTpLoJn>7j(^aU7O{ZwW)6VR=ja=HTF`Dx)Ey*r9U)y{te-oyaGV;s)AufOnk3b+f_;DZ3djR^?^{Q2JB3$}(4Cq{+2s2?6s5-~)iiReyQ_x~A^| zilMGB=(({YFJUK*fKFoRFq+6;3=)Q*?3!5=GA^ z#(%eSArR78PS*f=A*-c7;j`wAy0l|e-8%ANon8G}oy)(xoLf58R2SVXHM|b9y?GWu zDEs9st+dvgQ9KI(^8)$=0RLhuode)>=JeYGdhFcC7 zZ)7pv_kc2IAMWpCyp|V((A3uqmr7IKPPOr%Z^IK!a|t4lyE0m;Oc>B5TL5Tm4gkC_J8{zg2aqr`g!;~s Q(EtDd07*qoM6N<$g3nJ^$^ZZW literal 83 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnL3?x0byx0z;m;-!5Tzhu?;t_A+19G`NT^vIy c;*uqv067a77-dYInSd+?Pgg&ebxsLQ0GX;0bN~PV diff --git a/build/android/app/src/main/res/drawable/custom_dialog_rounded.xml b/build/android/app/src/main/res/drawable/custom_dialog_rounded.xml new file mode 100644 index 000000000..f5e0ff00c --- /dev/null +++ b/build/android/app/src/main/res/drawable/custom_dialog_rounded.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/build/android/app/src/main/res/drawable/custom_edittext_rounded.xml b/build/android/app/src/main/res/drawable/custom_edittext_rounded.xml new file mode 100644 index 000000000..d72c57859 --- /dev/null +++ b/build/android/app/src/main/res/drawable/custom_edittext_rounded.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/build/android/app/src/main/res/drawable/custom_progress_bar.xml b/build/android/app/src/main/res/drawable/custom_progress_bar.xml new file mode 100644 index 000000000..da5ed98b9 --- /dev/null +++ b/build/android/app/src/main/res/drawable/custom_progress_bar.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/build/android/app/src/main/res/drawable/ic_launcher_background.xml b/build/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..fe5102ec9 --- /dev/null +++ b/build/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/android/app/src/main/res/drawable/update.png b/build/android/app/src/main/res/drawable/update.png new file mode 100644 index 0000000000000000000000000000000000000000..7aa64530238f33003c93cdee66a107b0ff270103 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZA`BpB)|k7xlYrjj7PUy95OMUg4AY fRqYzXL8%gkeQaH4C%tu%09oMa>gTe~DWM4f+#D>e literal 0 HcmV?d00001 diff --git a/build/android/app/src/main/res/layout/activity_main.xml b/build/android/app/src/main/res/layout/activity_main.xml index e6f461f14..4778e3ec2 100644 --- a/build/android/app/src/main/res/layout/activity_main.xml +++ b/build/android/app/src/main/res/layout/activity_main.xml @@ -1,30 +1,39 @@ + android:layout_height="match_parent"> + + diff --git a/build/android/app/src/main/res/layout/rate_dialog.xml b/build/android/app/src/main/res/layout/rate_dialog.xml new file mode 100644 index 000000000..e0515207d --- /dev/null +++ b/build/android/app/src/main/res/layout/rate_dialog.xml @@ -0,0 +1,50 @@ + + + + + + + + + +