1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-21 17:17:06 +02:00

[android] cleanup Gradle / Java code

This commit is contained in:
Andrey Filipenkov 2024-03-03 00:24:48 +03:00
parent 8cee8b72a6
commit 51345727f8
97 changed files with 187 additions and 5916 deletions

4
android/.gitignore vendored
View File

@ -1,9 +1,11 @@
*.iml
.gradle
/local.properties
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
# generated by CMake build
/vcmi-app/gradle.properties

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.vcmi.vcmi">
<!-- %%INSERT_PERMISSIONS -->
<uses-permission android:name="android.permission.VIBRATE" />
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS_DISABLED -->
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<!-- %%INSERT_FEATURES -->
<supports-screens
android:largeScreens="true"
android:xlargeScreens="true" />
<application
android:name="org.qtproject.qt5.android.bindings.QtApplication"
android:hardwareAccelerated="true"
android:hasFragileUserData="true"
android:allowBackup="false"
android:installLocation="auto"
android:icon="@mipmap/ic_launcher"
android:label="${applicationLabel}"
android:testOnly="false"
android:supportsRtl="true"
android:usesCleartextTraffic="false">
<activity
android:name=".ActivityLauncher"
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:exported="true"
android:screenOrientation="sensorLandscape">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
"applicationStateChanged(Qt::ApplicationSuspended)"
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="none"/>
<!-- extract android style -->
</activity>
<activity
android:name=".VcmiSDLActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="sensorLandscape" />
<service
android:name=".ServerService"
android:process="eu.vcmi.vcmi.srv"
android:description="@string/server_name"
android:exported="false"/>
</application>
</manifest>

View File

@ -1,9 +0,0 @@
package eu.vcmi.vcmi.util;
/**
* Generated via cmake
*/
public class GeneratedVersion
{
public static final String VCMI_VERSION = "@VCMI_VERSION@";
}

View File

@ -0,0 +1,17 @@
{
"android-min-sdk-version": "@CMAKE_ANDROID_API@",
"android-package-source-directory": "@androidPackageSourceDir@",
"android-target-sdk-version": "@ANDROID_TARGET_SDK_VERSION@",
"application-binary": "vcmiclient",
"architectures": {
"@ANDROID_ABI@": "@ANDROID_SYSROOT_LIB_SUBDIR@"
},
"ndk": "@CMAKE_ANDROID_NDK@",
"ndk-host": "@ANDROID_HOST_TAG@",
"qt": "@qtDir@",
"sdk": "@androidSdkDir@",
"sdkBuildToolsRevision": "31.0.0",
"stdcpp-path": "@ANDROID_TOOLCHAIN_ROOT@/sysroot/usr/lib/",
"tool-prefix": "llvm",
"toolchain-prefix": "llvm"
}

View File

@ -1,4 +1,3 @@
ext {
// these values will be retrieved during gradle build
gitInfoVcmi = "none"
}

View File

@ -7,13 +7,18 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Qt-generated properties

View File

@ -1,8 +0,0 @@
/build
# generated by CMake build
/src/main/assets/internalData.zip
/src/main/assets/internalDataHash.txt
/src/main/java/eu/vcmi/vcmi/util/GeneratedVersion.java
/src/main/jniLibs
/src/main/res/raw/authors.txt

View File

@ -1,20 +1,49 @@
plugins {
id 'com.android.application'
}
apply plugin: 'com.android.application'
android {
compileSdk 33
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qt5AndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
ndkVersion '25.2.9519653'
// Extract native libraries from the APK
packagingOptions.jniLibs.useLegacyPackaging true
defaultConfig {
applicationId "is.xyz.vcmi"
minSdk 19
targetSdk 33
compileSdk = androidCompileSdkVersion.takeAfter("-") as Integer // has "android-" prepended
minSdk = qtMinSdkVersion as Integer
targetSdk = qtTargetSdkVersion as Integer // ANDROID_TARGET_SDK_VERSION in the CMake project
versionCode 1530
versionName "1.5.3"
setProperty("archivesBaseName", "vcmi")
}
sourceSets {
main {
// Qt requires these to be in the android project root
manifest.srcFile '../AndroidManifest.xml'
jniLibs.srcDirs = ['../libs']
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'src/main/res', '../res']
}
}
signingConfigs {
releaseSigning
dailySigning
@ -36,27 +65,18 @@ android {
release {
minifyEnabled false
zipAlignEnabled true
signingConfig signingConfigs.releaseSigning
applicationIdSuffix = project.findProperty('applicationIdSuffix')
signingConfig = signingConfigs[project.findProperty('signingConfig') ?: 'releaseSigning']
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
manifestPlaceholders = [
applicationLabel: '@string/app_name',
applicationLabel: project.findProperty('applicationLabel') ?: 'VCMI',
]
ndk {
debugSymbolLevel 'full'
}
}
daily {
initWith release
applicationIdSuffix '.daily'
signingConfig signingConfigs.dailySigning
manifestPlaceholders = [
applicationLabel: 'VCMI daily',
]
}
}
applicationVariants.all { variant -> RenameOutput(project.archivesBaseName, variant) }
tasks.withType(JavaCompile) {
options.compilerArgs += ["-Xlint:deprecation"]
}
@ -66,30 +86,9 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding true
dataBinding true
}
}
def RenameOutput(final baseName, final variant) {
final def buildTaskId = System.getenv("GITHUB_RUN_ID")
ResolveGitInfo()
def name = baseName + "-" + ext.gitInfoVcmi
if (buildTaskId != null && !buildTaskId.isEmpty()) {
name = buildTaskId + "-" + name
}
if (!variant.buildType.name != "release") {
name += "-" + variant.buildType.name
}
variant.outputs.each { output ->
def oldPath = output.outputFile.getAbsolutePath()
output.outputFileName = name + oldPath.substring(oldPath.lastIndexOf("."))
// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
}
}
@ -111,16 +110,6 @@ def CommandOutput(final cmd, final arguments, final cwd) {
}
}
def ResolveGitInfo() {
if (ext.gitInfoVcmi != "none") {
return
}
ext.gitInfoVcmi =
CommandOutput("git", ["log", "-1", "--pretty=%D", "--decorate-refs=refs/remotes/origin/*"], ".").replace("origin/", "").replace(", HEAD", "").replaceAll("[^a-zA-Z0-9\\-_]", "_") +
"-" +
CommandOutput("git", ["describe", "--match=", "--always", "--abbrev=7"], ".")
}
def SigningPropertiesPath(final basePath, final signingConfigKey) {
return file("${basePath}/${signingConfigKey}.properties")
}
@ -130,9 +119,8 @@ def SigningKeystorePath(final basePath, final keystoreFileName) {
}
def LoadSigningConfig(final signingConfigKey) {
final def projectRoot = "${project.projectDir}/../../CI/android"
final def props = new Properties()
final def propFile = SigningPropertiesPath(projectRoot, signingConfigKey)
final def propFile = SigningPropertiesPath(signingRoot, signingConfigKey)
def signingConfig = android.signingConfigs.getAt(signingConfigKey)
@ -143,7 +131,7 @@ def LoadSigningConfig(final signingConfigKey) {
&& props.containsKey('STORE_FILE')
&& props.containsKey('KEY_ALIAS')) {
signingConfig.storeFile = SigningKeystorePath(projectRoot, props['STORE_FILE'])
signingConfig.storeFile = SigningKeystorePath(signingRoot, props['STORE_FILE'])
signingConfig.storePassword = props['STORE_PASSWORD']
signingConfig.keyAlias = props['KEY_ALIAS']
@ -167,9 +155,6 @@ def LoadSigningConfig(final signingConfigKey) {
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.gms:play-services-base:18.2.0'
implementation 'com.google.android.gms:play-services-basement:18.1.0'
implementation fileTree(dir: '../libs', include: ['*.jar', '*.aar'])
implementation 'androidx.annotation:annotation:1.7.1'
}

View File

@ -1,54 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.vcmi.vcmi">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:extractNativeLibs="true"
android:hardwareAccelerated="true"
android:hasFragileUserData="true"
android:allowBackup="false"
android:installLocation="auto"
android:icon="@mipmap/ic_launcher"
android:label="${applicationLabel}"
android:testOnly="false"
android:supportsRtl="true"
android:theme="@style/Theme.VCMI">
<activity
android:exported="true"
android:name=".ActivityLauncher"
android:screenOrientation="sensorLandscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ActivityError"
android:screenOrientation="sensorLandscape" />
<activity
android:name=".ActivityMods"
android:screenOrientation="sensorLandscape" />
<activity
android:name=".ActivityAbout"
android:screenOrientation="sensorLandscape" />
<activity
android:name=".VcmiSDLActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="sensorLandscape"
android:theme="@style/Theme.VCMI.Full" />
<service
android:name=".ServerService"
android:process="eu.vcmi.vcmi.srv"
android:description="@string/server_name"
android:exported="false"/>
</application>
</manifest>

View File

@ -1,94 +0,0 @@
package eu.vcmi.vcmi;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import eu.vcmi.vcmi.content.DialogAuthors;
import eu.vcmi.vcmi.util.GeneratedVersion;
import eu.vcmi.vcmi.util.Utils;
/**
* @author F
*/
public class ActivityAbout extends ActivityWithToolbar
{
private static final String DIALOG_AUTHORS_TAG = "DIALOG_AUTHORS_TAG";
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
initToolbar(R.string.about_title);
initControl(R.id.about_version_app, getString(R.string.about_version_app, GeneratedVersion.VCMI_VERSION));
initControl(R.id.about_version_launcher, getString(R.string.about_version_launcher, Utils.appVersionName(this)));
initControlUrl(R.id.about_link_portal, R.string.about_links_main, R.string.url_project_page, this::onUrlPressed);
initControlUrl(R.id.about_link_repo_main, R.string.about_links_repo, R.string.url_project_repo, this::onUrlPressed);
initControlUrl(R.id.about_link_repo_launcher, R.string.about_links_repo_launcher, R.string.url_launcher_repo, this::onUrlPressed);
initControlBtn(R.id.about_btn_authors, this::onBtnAuthorsPressed);
initControlUrl(R.id.about_btn_privacy, R.string.about_btn_privacy, R.string.url_launcher_privacy, this::onUrlPressed);
}
private void initControlBtn(final int viewId, final View.OnClickListener callback)
{
findViewById(viewId).setOnClickListener(callback);
}
private void initControlUrl(final int textViewResId, final int baseTextRes, final int urlTextRes, final IInternalUrlCallback callback)
{
final TextView ctrl = (TextView) findViewById(textViewResId);
final String urlText = getString(urlTextRes);
final String fullText = getString(baseTextRes, urlText);
ctrl.setText(decoratedLinkText(fullText, fullText.indexOf(urlText), fullText.length()));
ctrl.setOnClickListener(v -> callback.onPressed(urlText));
}
private Spanned decoratedLinkText(final String rawText, final int start, final int end)
{
final SpannableString spannableString = new SpannableString(rawText);
spannableString.setSpan(new UnderlineSpan(), start, end, 0);
spannableString.setSpan(new ForegroundColorSpan(ContextCompat.getColor(this, R.color.accent)), start, end, 0);
return spannableString;
}
private void initControl(final int textViewResId, final String text)
{
((TextView) findViewById(textViewResId)).setText(text);
}
private void onBtnAuthorsPressed(final View v)
{
final DialogAuthors dialogAuthors = new DialogAuthors();
dialogAuthors.show(getSupportFragmentManager(), DIALOG_AUTHORS_TAG);
}
private void onUrlPressed(final String url)
{
try
{
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
catch (final ActivityNotFoundException ignored)
{
Toast.makeText(this, R.string.about_error_opening_url, Toast.LENGTH_LONG).show();
}
}
private interface IInternalUrlCallback
{
void onPressed(final String link);
}
}

View File

@ -1,58 +0,0 @@
package eu.vcmi.vcmi;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import eu.vcmi.vcmi.util.Log;
import eu.vcmi.vcmi.util.SharedPrefs;
/**
* @author F
*/
public abstract class ActivityBase extends AppCompatActivity
{
protected SharedPrefs mPrefs;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setupExceptionHandler();
mPrefs = new SharedPrefs(this);
}
private void setupExceptionHandler()
{
final Thread.UncaughtExceptionHandler prevHandler = Thread.getDefaultUncaughtExceptionHandler();
if (prevHandler != null && !(prevHandler instanceof VCMIExceptionHandler)) // no need to recreate it if it's already setup
{
Thread.setDefaultUncaughtExceptionHandler(new VCMIExceptionHandler(prevHandler));
}
}
private static class VCMIExceptionHandler implements Thread.UncaughtExceptionHandler
{
private Thread.UncaughtExceptionHandler mPrevHandler;
private VCMIExceptionHandler(final Thread.UncaughtExceptionHandler prevHandler)
{
mPrevHandler = prevHandler;
}
@Override
public void uncaughtException(final Thread thread, final Throwable throwable)
{
Log.e(this, "Unhandled exception", throwable); // to save the exception to file before crashing
if (mPrevHandler != null && !(mPrevHandler instanceof VCMIExceptionHandler))
{
mPrevHandler.uncaughtException(thread, throwable);
}
else
{
System.exit(1);
}
}
}
}

View File

@ -1,48 +0,0 @@
package eu.vcmi.vcmi;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
import android.view.View;
import android.widget.TextView;
/**
* @author F
*/
public class ActivityError extends ActivityWithToolbar
{
public static final String ARG_ERROR_MSG = "ActivityError.msg";
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_error);
initToolbar(R.string.launcher_title);
final View btnTryAgain = findViewById(R.id.error_btn_try_again);
btnTryAgain.setOnClickListener(new OnErrorRetryPressed());
final Bundle extras = getIntent().getExtras();
if (extras != null)
{
final String errorMessage = extras.getString(ARG_ERROR_MSG);
final TextView errorMessageView = (TextView) findViewById(R.id.error_message);
if (errorMessage != null)
{
errorMessageView.setText(errorMessage);
}
}
}
private class OnErrorRetryPressed implements View.OnClickListener
{
@Override
public void onClick(final View v)
{
// basically restarts main activity
startActivity(new Intent(ActivityError.this, ActivityLauncher.class));
finish();
}
}
}

View File

@ -1,307 +1,30 @@
package eu.vcmi.vcmi;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONObject;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.Nullable;
import eu.vcmi.vcmi.content.AsyncLauncherInitialization;
import eu.vcmi.vcmi.settings.AdventureAiController;
import eu.vcmi.vcmi.settings.LanguageSettingController;
import eu.vcmi.vcmi.settings.CopyDataController;
import eu.vcmi.vcmi.settings.ExportDataController;
import eu.vcmi.vcmi.settings.LauncherSettingController;
import eu.vcmi.vcmi.settings.ModsBtnController;
import eu.vcmi.vcmi.settings.MusicSettingController;
import eu.vcmi.vcmi.settings.PointerModeSettingController;
import eu.vcmi.vcmi.settings.PointerMultiplierSettingController;
import eu.vcmi.vcmi.settings.ScreenScaleSettingController;
import eu.vcmi.vcmi.settings.ScreenScaleSettingDialog;
import eu.vcmi.vcmi.settings.SoundSettingController;
import eu.vcmi.vcmi.settings.StartGameController;
import eu.vcmi.vcmi.util.FileUtil;
import eu.vcmi.vcmi.util.Log;
import eu.vcmi.vcmi.util.SharedPrefs;
import eu.vcmi.vcmi.VcmiSDLActivity;
import org.libsdl.app.SDL;
/**
* @author F
*/
public class ActivityLauncher extends ActivityWithToolbar
public class ActivityLauncher extends org.qtproject.qt5.android.bindings.QtActivity
{
public static final int PERMISSIONS_REQ_CODE = 123;
private final List<LauncherSettingController<?, ?>> mActualSettings = new ArrayList<>();
private View mProgress;
private TextView mErrorMessage;
private Config mConfig;
private LauncherSettingController<String, Config> mCtrlLanguage;
private LauncherSettingController<PointerModeSettingController.PointerMode, Config> mCtrlPointerMode;
private LauncherSettingController<Void, Void> mCtrlStart;
private LauncherSettingController<Float, Config> mCtrlPointerMulti;
private LauncherSettingController<ScreenScaleSettingController.ScreenScale, Config> mCtrlScreenScale;
private LauncherSettingController<Integer, Config> mCtrlSoundVol;
private LauncherSettingController<Integer, Config> mCtrlMusicVol;
private LauncherSettingController<String, Config> mAiController;
private CopyDataController mCtrlCopy;
private ExportDataController mCtrlExport;
private final AsyncLauncherInitialization.ILauncherCallbacks mInitCallbacks = new AsyncLauncherInitialization.ILauncherCallbacks()
{
@Override
public Activity ctx()
{
return ActivityLauncher.this;
}
@Override
public SharedPrefs prefs()
{
return mPrefs;
}
@Override
public void onInitSuccess()
{
loadConfigFile();
mCtrlStart.show();
mCtrlCopy.show();
mCtrlExport.show();
for (LauncherSettingController<?, ?> setting: mActualSettings) {
setting.show();
}
mErrorMessage.setVisibility(View.GONE);
mProgress.setVisibility(View.GONE);
}
@Override
public void onInitFailure(final AsyncLauncherInitialization.InitResult result)
{
mCtrlCopy.show();
if (result.mFailSilently)
{
return;
}
ActivityLauncher.this.onInitFailure(result);
}
};
public boolean justLaunched = true;
@Override
public void onCreate(final Bundle savedInstanceState)
public void onCreate(@Nullable final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState == null) // only clear the log if this is initial onCreate and not config change
{
Log.init();
}
Log.i(this, "Starting launcher");
setContentView(R.layout.activity_launcher);
initToolbar(R.string.launcher_title, true);
mProgress = findViewById(R.id.launcher_progress);
mErrorMessage = (TextView) findViewById(R.id.launcher_error);
mErrorMessage.setVisibility(View.GONE);
((TextView) findViewById(R.id.launcher_version_info)).setText(getString(R.string.launcher_version, BuildConfig.VERSION_NAME));
initSettingsGui();
justLaunched = savedInstanceState == null;
SDL.setContext(this);
}
@Override
public void onStart()
public void onLaunchGameBtnPressed()
{
super.onStart();
new AsyncLauncherInitialization(mInitCallbacks).execute((Void) null);
}
@Override
public void onBackPressed()
{
saveConfig();
super.onBackPressed();
}
@Override
public boolean onCreateOptionsMenu(final Menu menu)
{
getMenuInflater().inflate(R.menu.menu_launcher, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item)
{
if (item.getItemId() == R.id.menu_launcher_about)
{
startActivity(new Intent(this, ActivityAbout.class));
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData)
{
if(requestCode == CopyDataController.PICK_EXTERNAL_VCMI_DATA_TO_COPY
&& resultCode == Activity.RESULT_OK)
{
Uri uri;
if (resultData != null)
{
uri = resultData.getData();
mCtrlCopy.copyData(uri);
}
return;
}
if(requestCode == ExportDataController.PICK_DIRECTORY_TO_EXPORT
&& resultCode == Activity.RESULT_OK)
{
Uri uri = null;
if (resultData != null)
{
uri = resultData.getData();
mCtrlExport.copyData(uri);
}
return;
}
super.onActivityResult(requestCode, resultCode, resultData);
}
public void requestStoragePermissions()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
requestPermissions(
new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSIONS_REQ_CODE);
}
}
private void initSettingsGui()
{
mCtrlStart = new StartGameController(this, v -> onLaunchGameBtnPressed()).init(R.id.launcher_btn_start);
(mCtrlCopy = new CopyDataController(this)).init(R.id.launcher_btn_copy);
(mCtrlExport = new ExportDataController(this)).init(R.id.launcher_btn_export);
new ModsBtnController(this, v -> startActivity(new Intent(ActivityLauncher.this, ActivityMods.class))).init(R.id.launcher_btn_mods);
mCtrlLanguage = new LanguageSettingController(this).init(R.id.launcher_btn_cp, mConfig);
mCtrlPointerMode = new PointerModeSettingController(this).init(R.id.launcher_btn_pointer_mode, mConfig);
mCtrlPointerMulti = new PointerMultiplierSettingController(this).init(R.id.launcher_btn_pointer_multi, mConfig);
mCtrlScreenScale = new ScreenScaleSettingController(this).init(R.id.launcher_btn_scale, mConfig);
mCtrlSoundVol = new SoundSettingController(this).init(R.id.launcher_btn_volume_sound, mConfig);
mCtrlMusicVol = new MusicSettingController(this).init(R.id.launcher_btn_volume_music, mConfig);
mAiController = new AdventureAiController(this).init(R.id.launcher_btn_adventure_ai, mConfig);
mActualSettings.clear();
mActualSettings.add(mCtrlLanguage);
mActualSettings.add(mCtrlPointerMode);
mActualSettings.add(mCtrlPointerMulti);
mActualSettings.add(mCtrlScreenScale);
mActualSettings.add(mCtrlSoundVol);
mActualSettings.add(mCtrlMusicVol);
mActualSettings.add(mAiController);
mCtrlStart.hide(); // start is initially hidden, until we confirm that everything is okay via AsyncLauncherInitialization
mCtrlCopy.hide();
mCtrlExport.hide();
}
private void onLaunchGameBtnPressed()
{
saveConfig();
startActivity(new Intent(ActivityLauncher.this, VcmiSDLActivity.class));
}
private void saveConfig()
{
if (mConfig == null)
{
return;
}
try
{
mConfig.save(new File(FileUtil.configFileLocation(Storage.getVcmiDataDir(this))));
}
catch (final Exception e)
{
Toast.makeText(this, getString(R.string.launcher_error_config_saving_failed, e.getMessage()), Toast.LENGTH_LONG).show();
}
}
private void loadConfigFile()
{
try
{
final String settingsFileContent = FileUtil.read(
new File(FileUtil.configFileLocation(Storage.getVcmiDataDir(this))));
mConfig = Config.load(new JSONObject(settingsFileContent));
}
catch (final Exception e)
{
Log.e(this, "Could not load config file", e);
mConfig = new Config();
}
onConfigUpdated();
}
private void onConfigUpdated()
{
if(mConfig.mScreenScale == -1)
mConfig.updateScreenScale(ScreenScaleSettingDialog.getSupportedScalingRange(ActivityLauncher.this)[1]);
updateCtrlConfig(mCtrlLanguage, mConfig);
updateCtrlConfig(mCtrlPointerMode, mConfig);
updateCtrlConfig(mCtrlPointerMulti, mConfig);
updateCtrlConfig(mCtrlScreenScale, mConfig);
updateCtrlConfig(mCtrlSoundVol, mConfig);
updateCtrlConfig(mCtrlMusicVol, mConfig);
updateCtrlConfig(mAiController, mConfig);
}
private <TSetting, TConf> void updateCtrlConfig(
final LauncherSettingController<TSetting, TConf> ctrl,
final TConf config)
{
if (ctrl != null)
{
ctrl.updateConfig(config);
}
}
private void onInitFailure(final AsyncLauncherInitialization.InitResult initResult)
{
Log.d(this, "Init failed with " + initResult);
mProgress.setVisibility(View.GONE);
mCtrlStart.hide();
for (LauncherSettingController<?, ?> setting: mActualSettings)
{
setting.hide();
}
mErrorMessage.setVisibility(View.VISIBLE);
mErrorMessage.setText(initResult.mMessage);
}
}

View File

@ -1,351 +0,0 @@
package eu.vcmi.vcmi;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.security.ProviderInstaller;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import eu.vcmi.vcmi.content.ModBaseViewHolder;
import eu.vcmi.vcmi.content.ModsAdapter;
import eu.vcmi.vcmi.mods.VCMIMod;
import eu.vcmi.vcmi.mods.VCMIModContainer;
import eu.vcmi.vcmi.mods.VCMIModsRepo;
import eu.vcmi.vcmi.util.InstallModAsync;
import eu.vcmi.vcmi.util.FileUtil;
import eu.vcmi.vcmi.util.Log;
import eu.vcmi.vcmi.util.ServerResponse;
/**
* @author F
*/
public class ActivityMods extends ActivityWithToolbar
{
private static final boolean ENABLE_REPO_DOWNLOADING = true;
private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.5.json";
private VCMIModsRepo mRepo;
private RecyclerView mRecycler;
private VCMIModContainer mModContainer;
private TextView mErrorMessage;
private View mProgress;
private ModsAdapter mModsAdapter;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mods);
initToolbar(R.string.mods_title);
mRepo = new VCMIModsRepo();
mProgress = findViewById(R.id.mods_progress);
mErrorMessage = (TextView) findViewById(R.id.mods_error_text);
mErrorMessage.setVisibility(View.GONE);
mRecycler = (RecyclerView) findViewById(R.id.mods_recycler);
mRecycler.setItemAnimator(new DefaultItemAnimator());
mRecycler.setLayoutManager(new LinearLayoutManager(this));
mRecycler.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
mRecycler.setVisibility(View.GONE);
mModsAdapter = new ModsAdapter(new OnAdapterItemAction());
mRecycler.setAdapter(mModsAdapter);
new AsyncLoadLocalMods().execute((Void) null);
try {
ProviderInstaller.installIfNeeded(this);
} catch (GooglePlayServicesRepairableException e) {
GooglePlayServicesUtil.getErrorDialog(e.getConnectionStatusCode(), this, 0);
} catch (GooglePlayServicesNotAvailableException e) {
Log.e("SecurityException", "Google Play Services not available.");
}
}
private void loadLocalModData() throws IOException, JSONException
{
final File dataRoot = Storage.getVcmiDataDir(this);
final String internalDataRoot = getFilesDir() + "/" + Const.VCMI_DATA_ROOT_FOLDER_NAME;
final File modsRoot = new File(dataRoot,"/Mods");
final File internalModsRoot = new File(internalDataRoot + "/Mods");
if (!modsRoot.exists() && !internalModsRoot.exists())
{
Log.w(this, "We don't have mods folders");
return;
}
final File[] modsFiles = modsRoot.listFiles();
final File[] internalModsFiles = internalModsRoot.listFiles();
final List<File> topLevelModsFolders = new ArrayList<>();
if (modsFiles != null && modsFiles.length > 0)
{
Collections.addAll(topLevelModsFolders, modsFiles);
}
if (internalModsFiles != null && internalModsFiles.length > 0)
{
Collections.addAll(topLevelModsFolders, internalModsFiles);
}
mModContainer = VCMIModContainer.createContainer(topLevelModsFolders);
final File modConfigFile = new File(dataRoot, "config/modSettings.json");
if (!modConfigFile.exists())
{
Log.w(this, "We don't have mods config");
return;
}
JSONObject rootConfigObj = new JSONObject(FileUtil.read(modConfigFile));
JSONObject activeMods = rootConfigObj.getJSONObject("activeMods");
mModContainer.updateContainerFromConfigJson(activeMods, rootConfigObj.optJSONObject("core"));
Log.i(this, "Loaded mods: " + mModContainer);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu)
{
final MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.menu_mods, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item)
{
if (item.getItemId() == R.id.menu_mods_download_repo)
{
Log.i(this, "Should download repo now...");
if (ENABLE_REPO_DOWNLOADING)
{
mProgress.setVisibility(View.VISIBLE);
mRepo.init(REPO_URL, new OnModsRepoInitialized()); // disabled because the json is broken anyway
}
else
{
Snackbar.make(findViewById(R.id.mods_data_root), "Loading repo is disabled for now, because .json can't be parsed anyway",
Snackbar.LENGTH_LONG).show();
}
}
return super.onOptionsItemSelected(item);
}
private void handleNoData()
{
mProgress.setVisibility(View.GONE);
mRecycler.setVisibility(View.GONE);
mErrorMessage.setVisibility(View.VISIBLE);
mErrorMessage.setText("Could not load local mods list");
}
private void saveModSettingsToFile()
{
mModContainer.saveToFile(
new File(
Storage.getVcmiDataDir(this),
"config/modSettings.json"));
}
private class OnModsRepoInitialized implements VCMIModsRepo.IOnModsRepoDownloaded
{
@Override
public void onSuccess(ServerResponse<List<VCMIMod>> response)
{
Log.i(this, "Initialized mods repo");
if (mModContainer == null)
{
handleNoData();
}
else
{
mModContainer.updateFromRepo(response.mContent);
mModsAdapter.updateModsList(mModContainer.submods());
mProgress.setVisibility(View.GONE);
}
}
@Override
public void onError(final int code)
{
Log.i(this, "Mods repo error: " + code);
}
}
private class AsyncLoadLocalMods extends AsyncTask<Void, Void, Void>
{
@Override
protected void onPreExecute()
{
mProgress.setVisibility(View.VISIBLE);
}
@Override
protected Void doInBackground(final Void... params)
{
try
{
loadLocalModData();
}
catch (IOException e)
{
Log.e(this, "Loading local mod data failed", e);
}
catch (JSONException e)
{
Log.e(this, "Parsing local mod data failed", e);
}
return null;
}
@Override
protected void onPostExecute(final Void aVoid)
{
if (mModContainer == null || !mModContainer.hasSubmods())
{
handleNoData();
}
else
{
mProgress.setVisibility(View.GONE);
mRecycler.setVisibility(View.VISIBLE);
mModsAdapter.updateModsList(mModContainer.submods());
}
}
}
private class OnAdapterItemAction implements ModsAdapter.IOnItemAction
{
@Override
public void onItemPressed(final ModsAdapter.ModItem mod, final RecyclerView.ViewHolder vh)
{
Log.i(this, "Mod pressed: " + mod);
if (mod.mMod.hasSubmods())
{
if (mod.mExpanded)
{
mModsAdapter.detachSubmods(mod, vh);
}
else
{
mModsAdapter.attachSubmods(mod, vh);
mRecycler.scrollToPosition(vh.getAdapterPosition() + 1);
}
mod.mExpanded = !mod.mExpanded;
}
}
@Override
public void onDownloadPressed(final ModsAdapter.ModItem mod, final RecyclerView.ViewHolder vh)
{
Log.i(this, "Mod download pressed: " + mod);
mModsAdapter.downloadProgress(mod, "0%");
installModAsync(mod);
}
@Override
public void onTogglePressed(final ModsAdapter.ModItem item, final ModBaseViewHolder holder)
{
if(!item.mMod.mSystem && item.mMod.mInstalled)
{
item.mMod.mActive = !item.mMod.mActive;
mModsAdapter.notifyItemChanged(holder.getAdapterPosition());
saveModSettingsToFile();
}
}
@Override
public void onUninstall(ModsAdapter.ModItem item, ModBaseViewHolder holder)
{
File installationFolder = item.mMod.installationFolder;
ActivityMods activity = ActivityMods.this;
if(installationFolder != null){
new AlertDialog.Builder(activity)
.setTitle(activity.getString(R.string.mods_removal_title, item.mMod.mName))
.setMessage(activity.getString(R.string.mods_removal_confirmation, item.mMod.mName))
.setIcon(android.R.drawable.ic_dialog_alert)
.setNegativeButton(android.R.string.no, null)
.setPositiveButton(android.R.string.yes, (dialog, whichButton) ->
{
FileUtil.clearDirectory(installationFolder);
installationFolder.delete();
mModsAdapter.modRemoved(item);
})
.show();
}
}
}
private void installModAsync(ModsAdapter.ModItem mod){
File dataDir = Storage.getVcmiDataDir(this);
File modFolder = new File(
new File(dataDir, "Mods"),
mod.mMod.mId.toLowerCase(Locale.US));
InstallModAsync modInstaller = new InstallModAsync(
modFolder,
this,
new InstallModCallback(mod)
);
modInstaller.execute(mod.mMod.mArchiveUrl);
}
public class InstallModCallback implements InstallModAsync.PostDownload
{
private ModsAdapter.ModItem mod;
public InstallModCallback(ModsAdapter.ModItem mod)
{
this.mod = mod;
}
@Override
public void downloadDone(Boolean succeed, File modFolder)
{
if(succeed){
mModsAdapter.modInstalled(mod, modFolder);
}
}
@Override
public void downloadProgress(String... progress)
{
if(progress.length > 0)
{
mModsAdapter.downloadProgress(mod, progress[0]);
}
}
}
}

View File

@ -1,53 +0,0 @@
package eu.vcmi.vcmi;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import android.view.MenuItem;
import android.view.ViewStub;
/**
* @author F
*/
public abstract class ActivityWithToolbar extends ActivityBase
{
@Override
public void setContentView(final int layoutResId)
{
super.setContentView(R.layout.activity_toolbar_wrapper);
final ViewStub contentStub = (ViewStub) findViewById(R.id.toolbar_wrapper_content_stub);
contentStub.setLayoutResource(layoutResId);
contentStub.inflate();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item)
{
if (item.getItemId() == android.R.id.home)
{
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
protected void initToolbar(final int textResId)
{
initToolbar(textResId, false);
}
protected void initToolbar(final int textResId, final boolean isTopLevelActivity)
{
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setTitle(textResId);
if (!isTopLevelActivity)
{
final ActionBar bar = getSupportActionBar();
if (bar != null)
{
bar.setDisplayHomeAsUpEnabled(true);
}
}
}
}

View File

@ -1,217 +0,0 @@
package eu.vcmi.vcmi;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import eu.vcmi.vcmi.util.FileUtil;
import eu.vcmi.vcmi.util.Log;
/**
* @author F
*/
public class Config
{
public static final String DEFAULT_LANGUAGE = "english";
public static final int DEFAULT_MUSIC_VALUE = 5;
public static final int DEFAULT_SOUND_VALUE = 5;
public String mLanguage;
public int mScreenScale;
public int mVolumeSound;
public int mVolumeMusic;
private String adventureAi;
private double mPointerSpeedMultiplier;
private boolean mUseRelativePointer;
private JSONObject mRawObject;
private boolean mIsModified;
private static JSONObject accessNode(final JSONObject baseObj, String type)
{
if (baseObj == null)
{
return null;
}
return baseObj.optJSONObject(type);
}
private static JSONObject accessResolutionNode(final JSONObject baseObj)
{
if (baseObj == null)
{
return null;
}
final JSONObject video = baseObj.optJSONObject("video");
if (video != null)
{
return video.optJSONObject("resolution");
}
return null;
}
private static double loadDouble(final JSONObject node, final String key, final double fallback)
{
if (node == null)
{
return fallback;
}
return node.optDouble(key, fallback);
}
@SuppressWarnings("unchecked")
private static <T> T loadEntry(final JSONObject node, final String key, final T fallback)
{
if (node == null)
{
return fallback;
}
final Object value = node.opt(key);
return value == null ? fallback : (T) value;
}
public static Config load(final JSONObject obj)
{
Log.v("loading config from json: " + obj.toString());
final Config config = new Config();
final JSONObject general = accessNode(obj, "general");
final JSONObject server = accessNode(obj, "server");
final JSONObject resolution = accessResolutionNode(obj);
config.mLanguage = loadEntry(general, "language", DEFAULT_LANGUAGE);
config.mScreenScale = loadEntry(resolution, "scaling", -1);
config.mVolumeSound = loadEntry(general, "sound", DEFAULT_SOUND_VALUE);
config.mVolumeMusic = loadEntry(general, "music", DEFAULT_MUSIC_VALUE);
config.adventureAi = loadEntry(server, "playerAI", "Nullkiller");
config.mUseRelativePointer = loadEntry(general, "userRelativePointer", false);
config.mPointerSpeedMultiplier = loadDouble(general, "relativePointerSpeedMultiplier", 1.0);
config.mRawObject = obj;
return config;
}
public void updateLanguage(final String s)
{
mLanguage = s;
mIsModified = true;
}
public void updateScreenScale(final int scale)
{
mScreenScale = scale;
mIsModified = true;
}
public void updateSound(final int i)
{
mVolumeSound = i;
mIsModified = true;
}
public void updateMusic(final int i)
{
mVolumeMusic = i;
mIsModified = true;
}
public void setAdventureAi(String ai)
{
adventureAi = ai;
mIsModified = true;
}
public String getAdventureAi()
{
return this.adventureAi == null ? "Nullkiller" : this.adventureAi;
}
public void setPointerSpeedMultiplier(float speedMultiplier)
{
mPointerSpeedMultiplier = speedMultiplier;
mIsModified = true;
}
public float getPointerSpeedMultiplier()
{
return (float)mPointerSpeedMultiplier;
}
public void setPointerMode(boolean isRelative)
{
mUseRelativePointer = isRelative;
mIsModified = true;
}
public boolean getPointerModeIsRelative()
{
return mUseRelativePointer;
}
public void save(final File location) throws IOException, JSONException
{
if (!needsSaving(location))
{
Log.d(this, "Config doesn't need saving");
return;
}
try
{
final String configString = toJson();
FileUtil.write(location, configString);
Log.v(this, "Saved config: " + configString);
}
catch (final Exception e)
{
Log.e(this, "Could not save config", e);
throw e;
}
}
private boolean needsSaving(final File location)
{
return mIsModified || !location.exists();
}
private String toJson() throws JSONException
{
final JSONObject generalNode = accessNode(mRawObject, "general");
final JSONObject serverNode = accessNode(mRawObject, "server");
final JSONObject resolutionNode = accessResolutionNode(mRawObject);
final JSONObject root = mRawObject == null ? new JSONObject() : mRawObject;
final JSONObject general = generalNode == null ? new JSONObject() : generalNode;
final JSONObject video = new JSONObject();
final JSONObject resolution = resolutionNode == null ? new JSONObject() : resolutionNode;
final JSONObject server = serverNode == null ? new JSONObject() : serverNode;
if (mLanguage != null)
{
general.put("language", mLanguage);
}
general.put("music", mVolumeMusic);
general.put("sound", mVolumeSound);
general.put("userRelativePointer", mUseRelativePointer);
general.put("relativePointerSpeedMultiplier", mPointerSpeedMultiplier);
root.put("general", general);
if(this.adventureAi != null)
{
server.put("playerAI", this.adventureAi);
root.put("server", server);
}
if (mScreenScale > 0)
{
resolution.put("scaling", mScreenScale);
video.put("resolution", resolution);
root.put("video", video);
}
return root.toString();
}
}

View File

@ -1,15 +1,6 @@
package eu.vcmi.vcmi;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
/**
* @author F

View File

@ -1,14 +1,8 @@
package eu.vcmi.vcmi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.VibrationEffect;
import android.os.Vibrator;
@ -17,9 +11,6 @@ import org.libsdl.app.SDLActivity;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.Locale;
import java.text.SimpleDateFormat;
import eu.vcmi.vcmi.util.Log;

View File

@ -1,33 +1,14 @@
package eu.vcmi.vcmi;
import android.content.Context;
import java.io.File;
import java.io.IOException;
import eu.vcmi.vcmi.util.FileUtil;
public class Storage
{
public static File getVcmiDataDir(Context context)
{
File root = context.getExternalFilesDir(null);
return new File(root, Const.VCMI_DATA_ROOT_FOLDER_NAME);
}
public static boolean testH3DataFolder(Context context)
{
return testH3DataFolder(getVcmiDataDir(context));
}
public static boolean testH3DataFolder(final File baseDir)
{
final File testH3Data = new File(baseDir, "Data");
final File testH3data = new File(baseDir, "data");
final File testH3DATA = new File(baseDir, "DATA");
return testH3Data.exists() || testH3data.exists() || testH3DATA.exists();
}
public static String getH3DataFolder(Context context){
return getVcmiDataDir(context).getAbsolutePath();
}
}

View File

@ -84,9 +84,7 @@ public class VcmiSDLActivity extends SDLActivity
@Override
protected String getMainSharedObject() {
String library = "libvcmiclient.so";
return getContext().getApplicationInfo().nativeLibraryDir + "/" + library;
return String.format("%s/lib%s.so", getContext().getApplicationInfo().nativeLibraryDir, LibsLoader.CLIENT_LIB);
}
@Override
@ -100,9 +98,6 @@ public class VcmiSDLActivity extends SDLActivity
{
super.onCreate(savedInstanceState);
if(mBrokenLibraries)
return;
final View outerLayout = getLayoutInflater().inflate(R.layout.activity_game, null, false);
final ViewGroup layout = (ViewGroup) outerLayout.findViewById(R.id.game_outer_frame);
mProgressBar = outerLayout.findViewById(R.id.game_progress);
@ -182,4 +177,4 @@ public class VcmiSDLActivity extends SDLActivity
mCallback = callback;
}
}
}
}

View File

@ -1,171 +0,0 @@
package eu.vcmi.vcmi.content;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import androidx.core.app.ActivityCompat;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import eu.vcmi.vcmi.Const;
import eu.vcmi.vcmi.R;
import eu.vcmi.vcmi.Storage;
import eu.vcmi.vcmi.util.FileUtil;
import eu.vcmi.vcmi.util.Log;
import eu.vcmi.vcmi.util.SharedPrefs;
/**
* @author F
*/
public class AsyncLauncherInitialization extends AsyncTask<Void, Void, AsyncLauncherInitialization.InitResult>
{
private final WeakReference<ILauncherCallbacks> mCallbackRef;
public AsyncLauncherInitialization(final ILauncherCallbacks callback)
{
mCallbackRef = new WeakReference<>(callback);
}
private InitResult init()
{
InitResult initResult = handleDataFoldersInitialization();
if (!initResult.mSuccess)
{
return initResult;
}
Log.d(this, "Folders check passed");
return initResult;
}
private InitResult handleDataFoldersInitialization()
{
final ILauncherCallbacks callbacks = mCallbackRef.get();
if (callbacks == null)
{
return InitResult.failure("Internal error");
}
final Context ctx = callbacks.ctx();
final File vcmiDir = Storage.getVcmiDataDir(ctx);
final File internalDir = ctx.getFilesDir();
final File vcmiInternalDir = new File(internalDir, Const.VCMI_DATA_ROOT_FOLDER_NAME);
Log.i(this, "Using " + vcmiDir.getAbsolutePath() + " as root vcmi dir");
if(!vcmiInternalDir.exists()) vcmiInternalDir.mkdir();
if(!vcmiDir.exists()) vcmiDir.mkdir();
if (!Storage.testH3DataFolder(ctx))
{
// no h3 data present -> instruct user where to put it
return InitResult.failure(
ctx.getString(
R.string.launcher_error_h3_data_missing,
Storage.getVcmiDataDir(ctx)));
}
final File testVcmiData = new File(vcmiInternalDir, "Mods/vcmi/mod.json");
final boolean internalVcmiDataExisted = testVcmiData.exists();
if (!internalVcmiDataExisted && !FileUtil.unpackVcmiDataToInternalDir(vcmiInternalDir, ctx.getAssets()))
{
// no h3 data present -> instruct user where to put it
return InitResult.failure(ctx.getString(R.string.launcher_error_vcmi_data_internal_missing));
}
final String previousInternalDataHash = callbacks.prefs().load(SharedPrefs.KEY_CURRENT_INTERNAL_ASSET_HASH, null);
final String currentInternalDataHash = FileUtil.readAssetsStream(ctx.getAssets(), "internalDataHash.txt");
if (currentInternalDataHash == null || previousInternalDataHash == null || !currentInternalDataHash.equals(previousInternalDataHash))
{
// we should update the data only if it existed previously (hash is bound to be empty if we have just created the data)
if (internalVcmiDataExisted)
{
Log.i(this, "Internal data needs to be created/updated; old hash=" + previousInternalDataHash
+ ", new hash=" + currentInternalDataHash);
if (!FileUtil.reloadVcmiDataToInternalDir(vcmiInternalDir, ctx.getAssets()))
{
return InitResult.failure(ctx.getString(R.string.launcher_error_vcmi_data_internal_update));
}
}
callbacks.prefs().save(SharedPrefs.KEY_CURRENT_INTERNAL_ASSET_HASH, currentInternalDataHash);
}
return InitResult.success();
}
@Override
protected InitResult doInBackground(final Void... params)
{
return init();
}
@Override
protected void onPostExecute(final InitResult initResult)
{
final ILauncherCallbacks callbacks = mCallbackRef.get();
if (callbacks == null)
{
return;
}
if (initResult.mSuccess)
{
callbacks.onInitSuccess();
}
else
{
callbacks.onInitFailure(initResult);
}
}
public interface ILauncherCallbacks
{
Activity ctx();
SharedPrefs prefs();
void onInitSuccess();
void onInitFailure(InitResult result);
}
public static final class InitResult
{
public final boolean mSuccess;
public final String mMessage;
public boolean mFailSilently;
public InitResult(final boolean success, final String message)
{
mSuccess = success;
mMessage = message;
}
@Override
public String toString()
{
return String.format("success: %s (%s)", mSuccess, mMessage);
}
public static InitResult failure(String message)
{
return new InitResult(false, message);
}
public static InitResult success()
{
return new InitResult(true, "");
}
}
}

View File

@ -1,52 +0,0 @@
package eu.vcmi.vcmi.content;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.appcompat.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import java.io.IOException;
import eu.vcmi.vcmi.R;
import eu.vcmi.vcmi.util.FileUtil;
import eu.vcmi.vcmi.util.Log;
/**
* @author F
*/
public class DialogAuthors extends DialogFragment
{
@NonNull
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState)
{
final LayoutInflater inflater = LayoutInflater.from(getActivity());
@SuppressLint("InflateParams") final View inflated = inflater.inflate(R.layout.dialog_authors, null, false);
final TextView vcmiAuthorsView = (TextView) inflated.findViewById(R.id.dialog_authors_vcmi);
final TextView launcherAuthorsView = (TextView) inflated.findViewById(R.id.dialog_authors_launcher);
loadAuthorsContent(vcmiAuthorsView, launcherAuthorsView);
return new AlertDialog.Builder(getActivity())
.setView(inflated)
.create();
}
private void loadAuthorsContent(final TextView vcmiAuthorsView, final TextView launcherAuthorsView)
{
try
{
// to be checked if this should be converted to async load (not really a file operation so it should be okay)
final String authorsContent = "See ingame credits";
vcmiAuthorsView.setText(authorsContent);
launcherAuthorsView.setText("Fay"); // TODO hardcoded for now
}
catch (final Exception e)
{
Log.e(this, "Could not load authors content", e);
}
}
}

View File

@ -1,36 +0,0 @@
package eu.vcmi.vcmi.content;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class ModBaseViewHolder extends RecyclerView.ViewHolder
{
final View mModNesting;
final TextView mModName;
ModBaseViewHolder(final View parentView)
{
this(
LayoutInflater.from(parentView.getContext()).inflate(
R.layout.mod_base_adapter_item,
(ViewGroup) parentView,
false),
true);
}
protected ModBaseViewHolder(final View v, final boolean internal)
{
super(v);
mModNesting = itemView.findViewById(R.id.mods_adapter_item_nesting);
mModName = (TextView) itemView.findViewById(R.id.mods_adapter_item_name);
}
}

View File

@ -1,254 +0,0 @@
package eu.vcmi.vcmi.content;
import android.content.Context;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import eu.vcmi.vcmi.R;
import eu.vcmi.vcmi.mods.VCMIMod;
import eu.vcmi.vcmi.util.Log;
/**
* @author F
*/
public class ModsAdapter extends RecyclerView.Adapter<ModBaseViewHolder>
{
private static final int NESTING_WIDTH_PER_LEVEL = 16;
private static final int VIEWTYPE_MOD = 0;
private static final int VIEWTYPE_FAILED_MOD = 1;
private final List<ModItem> mDataset = new ArrayList<>();
private final IOnItemAction mItemListener;
public ModsAdapter(final IOnItemAction itemListener)
{
mItemListener = itemListener;
}
@Override
public ModBaseViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType)
{
switch (viewType)
{
case VIEWTYPE_MOD:
return new ModsViewHolder(parent);
case VIEWTYPE_FAILED_MOD:
return new ModBaseViewHolder(parent);
default:
Log.e(this, "Unhandled view type: " + viewType);
return null;
}
}
@Override
public void onBindViewHolder(final ModBaseViewHolder holder, final int position)
{
final ModItem item = mDataset.get(position);
final int viewType = getItemViewType(position);
final Context ctx = holder.itemView.getContext();
holder.mModNesting.getLayoutParams().width = item.mNestingLevel * NESTING_WIDTH_PER_LEVEL;
switch (viewType)
{
case VIEWTYPE_MOD:
final ModsViewHolder modHolder = (ModsViewHolder) holder;
modHolder.mModName.setText(item.mMod.mName + ", " + item.mMod.mVersion);
modHolder.mModType.setText(item.mMod.mModType);
if (item.mMod.mSize > 0)
{
modHolder.mModSize.setVisibility(View.VISIBLE);
// TODO unit conversion
modHolder.mModSize.setText(String.format(Locale.getDefault(), "%.1f kB", item.mMod.mSize / 1024.0f));
}
else
{
modHolder.mModSize.setVisibility(View.GONE);
}
modHolder.mModAuthor.setText(ctx.getString(R.string.mods_item_author_template, item.mMod.mAuthor));
modHolder.mStatusIcon.setImageResource(selectModStatusIcon(item.mMod.mActive));
modHolder.mDownloadBtn.setVisibility(View.GONE);
modHolder.mDownloadProgress.setVisibility(View.GONE);
modHolder.mUninstall.setVisibility(View.GONE);
if(!item.mMod.mSystem)
{
if (item.mDownloadProgress != null)
{
modHolder.mDownloadProgress.setText(item.mDownloadProgress);
modHolder.mDownloadProgress.setVisibility(View.VISIBLE);
}
else if (!item.mMod.mInstalled)
{
modHolder.mDownloadBtn.setVisibility(View.VISIBLE);
}
else if (item.mMod.installationFolder != null)
{
modHolder.mUninstall.setVisibility(View.VISIBLE);
}
modHolder.itemView.setOnClickListener(v -> mItemListener.onItemPressed(item, holder));
modHolder.mStatusIcon.setOnClickListener(v -> mItemListener.onTogglePressed(item, holder));
modHolder.mDownloadBtn.setOnClickListener(v -> mItemListener.onDownloadPressed(item, holder));
modHolder.mUninstall.setOnClickListener(v -> mItemListener.onUninstall(item, holder));
}
break;
case VIEWTYPE_FAILED_MOD:
holder.mModName.setText(ctx.getString(R.string.mods_failed_mod_loading, item.mMod.mName));
break;
default:
Log.e(this, "Unhandled view type: " + viewType);
break;
}
}
private int selectModStatusIcon(final boolean active)
{
// TODO distinguishing mods that aren't downloaded or have an update available
if (active)
{
return R.drawable.ic_star_full;
}
return R.drawable.ic_star_empty;
}
@Override
public int getItemViewType(final int position)
{
return mDataset.get(position).mMod.mLoadedCorrectly ? VIEWTYPE_MOD : VIEWTYPE_FAILED_MOD;
}
@Override
public int getItemCount()
{
return mDataset.size();
}
public void attachSubmods(final ModItem mod, final RecyclerView.ViewHolder vh)
{
int adapterPosition = vh.getAdapterPosition();
final List<ModItem> submods = new ArrayList<>();
for (VCMIMod v : mod.mMod.submods())
{
ModItem modItem = new ModItem(v, mod.mNestingLevel + 1);
submods.add(modItem);
}
mDataset.addAll(adapterPosition + 1, submods);
notifyItemRangeInserted(adapterPosition + 1, submods.size());
}
public void detachSubmods(final ModItem mod, final RecyclerView.ViewHolder vh)
{
final int adapterPosition = vh.getAdapterPosition();
final int checkedPosition = adapterPosition + 1;
int detachedElements = 0;
while (checkedPosition < mDataset.size() && mDataset.get(checkedPosition).mNestingLevel > mod.mNestingLevel)
{
++detachedElements;
mDataset.remove(checkedPosition);
}
notifyItemRangeRemoved(checkedPosition, detachedElements);
}
public void updateModsList(List<VCMIMod> mods)
{
mDataset.clear();
List<ModItem> list = new ArrayList<>();
for (VCMIMod mod : mods)
{
ModItem modItem = new ModItem(mod);
list.add(modItem);
}
mDataset.addAll(list);
notifyDataSetChanged();
}
public void modInstalled(ModItem mod, File modFolder)
{
try
{
mod.mMod.updateFromModInfo(modFolder);
mod.mMod.mLoadedCorrectly = true;
mod.mMod.mActive = true; // active by default
mod.mMod.mInstalled = true;
mod.mMod.installationFolder = modFolder;
mod.mDownloadProgress = null;
notifyItemChanged(mDataset.indexOf(mod));
}
catch (Exception ex)
{
Log.e("Failed to install mod", ex);
}
}
public void downloadProgress(ModItem mod, String progress)
{
mod.mDownloadProgress = progress;
notifyItemChanged(mDataset.indexOf(mod));
}
public void modRemoved(ModItem item)
{
int itemIndex = mDataset.indexOf(item);
if(item.mMod.mArchiveUrl != null && item.mMod.mArchiveUrl != "")
{
item.mMod.mInstalled = false;
item.mMod.installationFolder = null;
notifyItemChanged(itemIndex);
}
else
{
mDataset.remove(item);
notifyItemRemoved(itemIndex);
}
}
public interface IOnItemAction
{
void onItemPressed(final ModItem mod, final RecyclerView.ViewHolder vh);
void onDownloadPressed(final ModItem mod, final RecyclerView.ViewHolder vh);
void onTogglePressed(ModItem item, ModBaseViewHolder holder);
void onUninstall(ModItem item, ModBaseViewHolder holder);
}
public static class ModItem
{
public final VCMIMod mMod;
public int mNestingLevel;
public boolean mExpanded;
public String mDownloadProgress;
public ModItem(final VCMIMod mod)
{
this(mod, 0);
}
public ModItem(final VCMIMod mod, final int nestingLevel)
{
mMod = mod;
mNestingLevel = nestingLevel;
mExpanded = false;
}
@Override
public String toString()
{
return mMod.toString();
}
}
}

View File

@ -1,35 +0,0 @@
package eu.vcmi.vcmi.content;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class ModsViewHolder extends ModBaseViewHolder
{
final TextView mModAuthor;
final TextView mModType;
final TextView mModSize;
final ImageView mStatusIcon;
final View mDownloadBtn;
final TextView mDownloadProgress;
final View mUninstall;
ModsViewHolder(final View parentView)
{
super(LayoutInflater.from(parentView.getContext()).inflate(R.layout.mods_adapter_item, (ViewGroup) parentView, false), true);
mModAuthor = (TextView) itemView.findViewById(R.id.mods_adapter_item_author);
mModType = (TextView) itemView.findViewById(R.id.mods_adapter_item_modtype);
mModSize = (TextView) itemView.findViewById(R.id.mods_adapter_item_size);
mDownloadBtn = itemView.findViewById(R.id.mods_adapter_item_btn_download);
mStatusIcon = (ImageView) itemView.findViewById(R.id.mods_adapter_item_status);
mDownloadProgress = (TextView) itemView.findViewById(R.id.mods_adapter_item_install_progress);
mUninstall = itemView.findViewById(R.id.mods_adapter_item_btn_uninstall);
}
}

View File

@ -1,258 +0,0 @@
package eu.vcmi.vcmi.mods;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import eu.vcmi.vcmi.BuildConfig;
import eu.vcmi.vcmi.util.FileUtil;
import eu.vcmi.vcmi.util.Log;
/**
* @author F
*/
public class VCMIMod
{
protected final Map<String, VCMIMod> mSubmods;
public String mId;
public String mName;
public String mDesc;
public String mVersion;
public String mAuthor;
public String mContact;
public String mModType;
public String mArchiveUrl;
public long mSize;
public File installationFolder;
// config values
public boolean mActive;
public boolean mInstalled;
public boolean mValidated;
public String mChecksum;
// internal
public boolean mLoadedCorrectly;
public boolean mSystem;
protected VCMIMod()
{
mSubmods = new HashMap<>();
}
public static VCMIMod buildFromRepoJson(final String id,
final JSONObject obj,
JSONObject modDownloadData)
{
final VCMIMod mod = new VCMIMod();
mod.mId = id.toLowerCase(Locale.US);
mod.mName = obj.optString("name");
mod.mDesc = obj.optString("description");
mod.mVersion = obj.optString("version");
mod.mAuthor = obj.optString("author");
mod.mContact = obj.optString("contact");
mod.mModType = obj.optString("modType");
mod.mArchiveUrl = modDownloadData.optString("download");
mod.mSize = obj.optLong("size");
mod.mLoadedCorrectly = true;
return mod;
}
public static VCMIMod buildFromConfigJson(final String id, final JSONObject obj) throws JSONException
{
final VCMIMod mod = new VCMIMod();
mod.updateFromConfigJson(id, obj);
mod.mInstalled = true;
return mod;
}
public static VCMIMod buildFromModInfo(final File modPath) throws IOException, JSONException
{
final VCMIMod mod = new VCMIMod();
if (!mod.updateFromModInfo(modPath))
{
return mod;
}
mod.mLoadedCorrectly = true;
mod.mActive = true; // active by default
mod.mInstalled = true;
mod.installationFolder = modPath;
return mod;
}
protected static Map<String, VCMIMod> loadSubmods(final List<File> modsList) throws IOException, JSONException
{
final Map<String, VCMIMod> submods = new HashMap<>();
for (final File f : modsList)
{
if (!f.isDirectory())
{
Log.w("VCMI", "Non-directory encountered in mods dir: " + f.getName());
continue;
}
final VCMIMod submod = buildFromModInfo(f);
if (submod == null)
{
Log.w(null, "Could not build mod in folder " + f + "; ignoring");
continue;
}
submods.put(submod.mId, submod);
}
return submods;
}
public void updateFromConfigJson(final String id, final JSONObject obj) throws JSONException
{
if(mSystem)
{
return;
}
mId = id.toLowerCase(Locale.US);
mActive = obj.optBoolean("active");
mValidated = obj.optBoolean("validated");
mChecksum = obj.optString("checksum");
final JSONObject submods = obj.optJSONObject("mods");
if (submods != null)
{
updateChildrenFromConfigJson(submods);
}
}
protected void updateChildrenFromConfigJson(final JSONObject submods) throws JSONException
{
final JSONArray names = submods.names();
for (int i = 0; i < names.length(); ++i)
{
final String modId = names.getString(i);
final String normalizedModId = modId.toLowerCase(Locale.US);
if (!mSubmods.containsKey(normalizedModId))
{
Log.w(this, "Mod present in config but not found in /Mods; ignoring: " + modId);
continue;
}
mSubmods.get(normalizedModId).updateFromConfigJson(modId, submods.getJSONObject(modId));
}
}
public boolean updateFromModInfo(final File modPath) throws IOException, JSONException
{
final File modInfoFile = new File(modPath, "mod.json");
if (!modInfoFile.exists())
{
Log.w(this, "Mod info doesn't exist");
mName = modPath.getAbsolutePath();
return false;
}
try
{
final JSONObject modInfoContent = new JSONObject(FileUtil.read(modInfoFile));
mId = modPath.getName().toLowerCase(Locale.US);
mName = modInfoContent.optString("name");
mDesc = modInfoContent.optString("description");
mVersion = modInfoContent.optString("version");
mAuthor = modInfoContent.optString("author");
mContact = modInfoContent.optString("contact");
mModType = modInfoContent.optString("modType");
mSystem = mId.equals("vcmi");
final File submodsDir = new File(modPath, "Mods");
if (submodsDir.exists())
{
final List<File> submodsFiles = new ArrayList<>();
Collections.addAll(submodsFiles, submodsDir.listFiles());
mSubmods.putAll(loadSubmods(submodsFiles));
}
return true;
}
catch (final JSONException ex)
{
mName = modPath.getAbsolutePath();
return false;
}
}
@Override
public String toString()
{
if (!BuildConfig.DEBUG)
{
return "";
}
return String.format("mod:[id:%s,active:%s,submods:[%s]]", mId, mActive, TextUtils.join(",", mSubmods.values()));
}
protected void submodsToJson(final JSONObject modsRoot) throws JSONException
{
for (final VCMIMod submod : mSubmods.values())
{
final JSONObject submodEntry = new JSONObject();
submod.toJsonInternal(submodEntry);
modsRoot.put(submod.mId, submodEntry);
}
}
protected void toJsonInternal(final JSONObject root) throws JSONException
{
root.put("active", mActive);
root.put("validated", mValidated);
if (!TextUtils.isEmpty(mChecksum))
{
root.put("checksum", mChecksum);
}
if (!mSubmods.isEmpty())
{
JSONObject submods = new JSONObject();
submodsToJson(submods);
root.put("mods", submods);
}
}
public boolean hasSubmods()
{
return !mSubmods.isEmpty();
}
public List<VCMIMod> submods()
{
final ArrayList<VCMIMod> ret = new ArrayList<>();
ret.addAll(mSubmods.values());
Collections.sort(ret, new Comparator<VCMIMod>()
{
@Override
public int compare(VCMIMod left, VCMIMod right)
{
return left.mName.compareTo(right.mName);
}
});
return ret;
}
protected void updateFrom(VCMIMod other)
{
this.mModType = other.mModType;
this.mAuthor = other.mAuthor;
this.mDesc = other.mDesc;
this.mArchiveUrl = other.mArchiveUrl;
}
}

View File

@ -1,106 +0,0 @@
package eu.vcmi.vcmi.mods;
import android.text.TextUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import eu.vcmi.vcmi.BuildConfig;
import eu.vcmi.vcmi.util.FileUtil;
import eu.vcmi.vcmi.util.Log;
/**
* @author F
*/
public class VCMIModContainer extends VCMIMod
{
private VCMIMod mCoreStatus; // kept here to correctly save core object to modSettings
private VCMIModContainer()
{
}
public static VCMIModContainer createContainer(final List<File> modsList) throws IOException, JSONException
{
final VCMIModContainer mod = new VCMIModContainer();
mod.mSubmods.putAll(loadSubmods(modsList));
return mod;
}
public void updateContainerFromConfigJson(final JSONObject modsList, final JSONObject coreStatus) throws JSONException
{
updateChildrenFromConfigJson(modsList);
if (coreStatus != null)
{
mCoreStatus = VCMIMod.buildFromConfigJson("core", coreStatus);
}
}
public void updateFromRepo(List<VCMIMod> repoMods){
for (VCMIMod mod : repoMods)
{
final String normalizedModId = mod.mId.toLowerCase(Locale.US);
if(mSubmods.containsKey(normalizedModId)){
VCMIMod existing = mSubmods.get(normalizedModId);
existing.updateFrom(mod);
}
else{
mSubmods.put(normalizedModId, mod);
}
}
}
@Override
public String toString()
{
if (!BuildConfig.DEBUG)
{
return "";
}
return String.format("mods:[%s]", TextUtils.join(",", mSubmods.values()));
}
public void saveToFile(final File location)
{
try
{
FileUtil.write(location, toJson());
}
catch (Exception e)
{
Log.e(this, "Could not save mod settings", e);
}
}
protected String toJson() throws JSONException
{
final JSONObject root = new JSONObject();
final JSONObject activeMods = new JSONObject();
final JSONObject coreStatus = new JSONObject();
root.put("activeMods", activeMods);
submodsToJson(activeMods);
coreStatusToJson(coreStatus);
root.put("core", coreStatus);
return root.toString();
}
private void coreStatusToJson(final JSONObject coreStatus) throws JSONException
{
if (mCoreStatus == null)
{
mCoreStatus = new VCMIMod();
mCoreStatus.mId = "core";
mCoreStatus.mActive = true;
}
mCoreStatus.toJsonInternal(coreStatus);
}
}

View File

@ -1,108 +0,0 @@
package eu.vcmi.vcmi.mods;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import eu.vcmi.vcmi.util.AsyncRequest;
import eu.vcmi.vcmi.util.Log;
import eu.vcmi.vcmi.util.ServerResponse;
/**
* @author F
*/
public class VCMIModsRepo
{
private final List<VCMIMod> mModsList;
private IOnModsRepoDownloaded mCallback;
public VCMIModsRepo()
{
mModsList = new ArrayList<>();
}
public void init(final String url, final IOnModsRepoDownloaded callback)
{
mCallback = callback;
new AsyncLoadRepo().execute(url);
}
public interface IOnModsRepoDownloaded
{
void onSuccess(ServerResponse<List<VCMIMod>> response);
void onError(final int code);
}
private class AsyncLoadRepo extends AsyncRequest<List<VCMIMod>>
{
@Override
protected ServerResponse<List<VCMIMod>> doInBackground(final String... params)
{
ServerResponse<List<VCMIMod>> serverResponse = sendRequest(params[0]);
if (serverResponse.isValid())
{
final List<VCMIMod> mods = new ArrayList<>();
try
{
JSONObject jsonContent = new JSONObject(serverResponse.mRawContent);
final JSONArray names = jsonContent.names();
for (int i = 0; i < names.length(); ++i)
{
try
{
String name = names.getString(i);
JSONObject modDownloadData = jsonContent.getJSONObject(name);
if(modDownloadData.has("mod"))
{
String modFileAddress = modDownloadData.getString("mod");
ServerResponse<List<VCMIMod>> modFile = sendRequest(modFileAddress);
if (!modFile.isValid())
{
continue;
}
JSONObject modJson = new JSONObject(modFile.mRawContent);
mods.add(VCMIMod.buildFromRepoJson(name, modJson, modDownloadData));
}
else
{
mods.add(VCMIMod.buildFromRepoJson(name, modDownloadData, modDownloadData));
}
}
catch (JSONException e)
{
Log.e(this, "Could not parse the response as json", e);
}
}
serverResponse.mContent = mods;
}
catch (JSONException e)
{
Log.e(this, "Could not parse the response as json", e);
serverResponse.mCode = ServerResponse.LOCAL_ERROR_PARSING;
}
}
return serverResponse;
}
@Override
protected void onPostExecute(final ServerResponse<List<VCMIMod>> response)
{
if (response.isValid())
{
mModsList.clear();
mModsList.addAll(response.mContent);
mCallback.onSuccess(response);
}
else
{
mCallback.onError(response.mCode);
}
}
}
}

View File

@ -1,46 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import eu.vcmi.vcmi.Config;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class AdventureAiController extends LauncherSettingWithDialogController<String, Config>
{
public AdventureAiController(final AppCompatActivity activity)
{
super(activity);
}
@Override
protected LauncherSettingDialog<String> dialog()
{
return new AdventureAiSelectionDialog();
}
@Override
public void onItemChosen(final String item)
{
mConfig.setAdventureAi(item);
updateContent();
}
@Override
protected String mainText()
{
return mActivity.getString(R.string.launcher_btn_adventure_ai_title);
}
@Override
protected String subText()
{
if (mConfig == null)
{
return "";
}
return mConfig.getAdventureAi();
}
}

View File

@ -1,37 +0,0 @@
package eu.vcmi.vcmi.settings;
import java.util.ArrayList;
import java.util.List;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class AdventureAiSelectionDialog extends LauncherSettingDialog<String>
{
private static final List<String> AVAILABLE_AI = new ArrayList<>();
static
{
AVAILABLE_AI.add("VCAI");
AVAILABLE_AI.add("Nullkiller");
}
public AdventureAiSelectionDialog()
{
super(AVAILABLE_AI);
}
@Override
protected int dialogTitleResId()
{
return R.string.launcher_btn_adventure_ai_title;
}
@Override
protected CharSequence itemName(final String item)
{
return item;
}
}

View File

@ -1,193 +0,0 @@
package eu.vcmi.vcmi.settings;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.view.View;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import androidx.appcompat.app.AppCompatActivity;
import androidx.documentfile.provider.DocumentFile;
import androidx.loader.content.AsyncTaskLoader;
import eu.vcmi.vcmi.R;
import eu.vcmi.vcmi.Storage;
import eu.vcmi.vcmi.util.FileUtil;
public class CopyDataController extends LauncherSettingController<Void, Void>
{
public static final int PICK_EXTERNAL_VCMI_DATA_TO_COPY = 3;
private String progress;
public CopyDataController(final AppCompatActivity act)
{
super(act);
}
@Override
protected String mainText()
{
return mActivity.getString(R.string.launcher_btn_import_title);
}
@Override
protected String subText()
{
if (progress != null)
{
return progress;
}
return mActivity.getString(R.string.launcher_btn_import_description);
}
@Override
public void onClick(final View v)
{
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.putExtra(
DocumentsContract.EXTRA_INITIAL_URI,
Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "vcmi-data")));
mActivity.startActivityForResult(intent, PICK_EXTERNAL_VCMI_DATA_TO_COPY);
}
public void copyData(Uri folderToCopy)
{
AsyncCopyData copyTask = new AsyncCopyData(mActivity, folderToCopy);
copyTask.execute();
}
private class AsyncCopyData extends AsyncTask<String, String, Boolean>
{
private Activity owner;
private Uri folderToCopy;
public AsyncCopyData(Activity owner, Uri folderToCopy)
{
this.owner = owner;
this.folderToCopy = folderToCopy;
}
@Override
protected Boolean doInBackground(final String... params)
{
File targetDir = Storage.getVcmiDataDir(owner);
DocumentFile sourceDir = DocumentFile.fromTreeUri(owner, folderToCopy);
ArrayList<String> allowedFolders = new ArrayList<String>();
allowedFolders.add("Data");
allowedFolders.add("Mp3");
allowedFolders.add("Maps");
allowedFolders.add("Saves");
allowedFolders.add("Mods");
allowedFolders.add("config");
return copyDirectory(targetDir, sourceDir, allowedFolders);
}
@Override
protected void onPostExecute(Boolean result)
{
super.onPostExecute(result);
if (result)
{
CopyDataController.this.progress = null;
CopyDataController.this.updateContent();
}
}
@Override
protected void onProgressUpdate(String... values)
{
CopyDataController.this.progress = values[0];
CopyDataController.this.updateContent();
}
private boolean copyDirectory(File targetDir, DocumentFile sourceDir, List<String> allowed)
{
if (!targetDir.exists())
{
targetDir.mkdir();
}
for (DocumentFile child : sourceDir.listFiles())
{
if (allowed != null)
{
boolean fileAllowed = false;
for (String str : allowed)
{
if (str.equalsIgnoreCase(child.getName()))
{
fileAllowed = true;
break;
}
}
if (!fileAllowed)
continue;
}
File exported = new File(targetDir, child.getName());
if (child.isFile())
{
publishProgress(owner.getString(R.string.launcher_progress_copy,
child.getName()));
if (!exported.exists())
{
try
{
exported.createNewFile();
}
catch (IOException e)
{
publishProgress("Failed to copy file " + child.getName());
return false;
}
}
try (
final OutputStream targetStream = new FileOutputStream(exported, false);
final InputStream sourceStream = owner.getContentResolver()
.openInputStream(child.getUri()))
{
FileUtil.copyStream(sourceStream, targetStream);
}
catch (IOException e)
{
publishProgress("Failed to copy file " + child.getName());
return false;
}
}
if (child.isDirectory() && !copyDirectory(exported, child, null))
{
return false;
}
}
return true;
}
}
}

View File

@ -1,19 +0,0 @@
package eu.vcmi.vcmi.settings;
import eu.vcmi.vcmi.Config;
import eu.vcmi.vcmi.util.SharedPrefs;
/**
* @author F
*/
public class DoubleConfig
{
public final Config mConfig;
public final SharedPrefs mPrefs;
public DoubleConfig(final Config config, final SharedPrefs prefs)
{
mConfig = config;
mPrefs = prefs;
}
}

View File

@ -1,174 +0,0 @@
package eu.vcmi.vcmi.settings;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.view.View;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import androidx.appcompat.app.AppCompatActivity;
import androidx.documentfile.provider.DocumentFile;
import eu.vcmi.vcmi.R;
import eu.vcmi.vcmi.Storage;
import eu.vcmi.vcmi.util.FileUtil;
public class ExportDataController extends LauncherSettingController<Void, Void>
{
public static final int PICK_DIRECTORY_TO_EXPORT = 4;
private String progress;
public ExportDataController(final AppCompatActivity act)
{
super(act);
}
@Override
protected String mainText()
{
return mActivity.getString(R.string.launcher_btn_export_title);
}
@Override
protected String subText()
{
if (progress != null)
{
return progress;
}
return mActivity.getString(R.string.launcher_btn_export_description);
}
@Override
public void onClick(final View v)
{
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.putExtra(
DocumentsContract.EXTRA_INITIAL_URI,
Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "vcmi-data")));
mActivity.startActivityForResult(intent, PICK_DIRECTORY_TO_EXPORT);
}
public void copyData(Uri targetFolder)
{
AsyncCopyData copyTask = new AsyncCopyData(mActivity, targetFolder);
copyTask.execute();
}
private class AsyncCopyData extends AsyncTask<String, String, Boolean>
{
private Activity owner;
private Uri targetFolder;
public AsyncCopyData(Activity owner, Uri targetFolder)
{
this.owner = owner;
this.targetFolder = targetFolder;
}
@Override
protected Boolean doInBackground(final String... params)
{
File targetDir = Storage.getVcmiDataDir(owner);
DocumentFile sourceDir = DocumentFile.fromTreeUri(owner, targetFolder);
return copyDirectory(targetDir, sourceDir);
}
@Override
protected void onPostExecute(Boolean result)
{
super.onPostExecute(result);
if (result)
{
ExportDataController.this.progress = null;
ExportDataController.this.updateContent();
}
}
@Override
protected void onProgressUpdate(String... values)
{
ExportDataController.this.progress = values[0];
ExportDataController.this.updateContent();
}
private boolean copyDirectory(File sourceDir, DocumentFile targetDir)
{
for (File child : sourceDir.listFiles())
{
DocumentFile exported = targetDir.findFile(child.getName());
if (child.isFile())
{
publishProgress(owner.getString(R.string.launcher_progress_copy,
child.getName()));
if (exported == null)
{
try
{
exported = targetDir.createFile(
"application/octet-stream",
child.getName());
}
catch (UnsupportedOperationException e)
{
publishProgress("Failed to copy file " + child.getName());
return false;
}
}
if (exported == null)
{
publishProgress("Failed to copy file " + child.getName());
return false;
}
try(
final OutputStream targetStream = owner.getContentResolver()
.openOutputStream(exported.getUri());
final InputStream sourceStream = new FileInputStream(child))
{
FileUtil.copyStream(sourceStream, targetStream);
}
catch (IOException e)
{
publishProgress("Failed to copy file " + child.getName());
return false;
}
}
if (child.isDirectory())
{
if (exported == null)
{
exported = targetDir.createDirectory(child.getName());
}
if(!copyDirectory(child, exported))
{
return false;
}
}
}
return true;
}
}
}

View File

@ -1,48 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import eu.vcmi.vcmi.Config;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class LanguageSettingController extends LauncherSettingWithDialogController<String, Config>
{
public LanguageSettingController(final AppCompatActivity activity)
{
super(activity);
}
@Override
protected LauncherSettingDialog<String> dialog()
{
return new LanguageSettingDialog();
}
@Override
public void onItemChosen(final String item)
{
mConfig.updateLanguage(item);
updateContent();
}
@Override
protected String mainText()
{
return mActivity.getString(R.string.launcher_btn_language_title);
}
@Override
protected String subText()
{
if (mConfig == null)
{
return "";
}
return mConfig.mLanguage == null || mConfig.mLanguage.isEmpty()
? mActivity.getString(R.string.launcher_btn_language_subtitle_unknown)
: mActivity.getString(R.string.launcher_btn_language_subtitle, mConfig.mLanguage);
}
}

View File

@ -1,55 +0,0 @@
package eu.vcmi.vcmi.settings;
import java.util.ArrayList;
import java.util.List;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class LanguageSettingDialog extends LauncherSettingDialog<String>
{
private static final List<String> AVAILABLE_LANGUAGES = new ArrayList<>();
static
{
AVAILABLE_LANGUAGES.add("english");
AVAILABLE_LANGUAGES.add("czech");
AVAILABLE_LANGUAGES.add("chinese");
AVAILABLE_LANGUAGES.add("finnish");
AVAILABLE_LANGUAGES.add("french");
AVAILABLE_LANGUAGES.add("german");
AVAILABLE_LANGUAGES.add("hungarian");
AVAILABLE_LANGUAGES.add("italian");
AVAILABLE_LANGUAGES.add("korean");
AVAILABLE_LANGUAGES.add("polish");
AVAILABLE_LANGUAGES.add("portuguese");
AVAILABLE_LANGUAGES.add("russian");
AVAILABLE_LANGUAGES.add("spanish");
AVAILABLE_LANGUAGES.add("swedish");
AVAILABLE_LANGUAGES.add("turkish");
AVAILABLE_LANGUAGES.add("ukrainian");
AVAILABLE_LANGUAGES.add("vietnamese");
AVAILABLE_LANGUAGES.add("other_cp1250");
AVAILABLE_LANGUAGES.add("other_cp1251");
AVAILABLE_LANGUAGES.add("other_cp1252");
}
public LanguageSettingDialog()
{
super(AVAILABLE_LANGUAGES);
}
@Override
protected int dialogTitleResId()
{
return R.string.launcher_btn_language_title;
}
@Override
protected CharSequence itemName(final String item)
{
return item;
}
}

View File

@ -1,75 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public abstract class LauncherSettingController<TSetting, TConf> implements View.OnClickListener
{
protected AppCompatActivity mActivity;
protected TConf mConfig;
private View mSettingViewRoot;
private TextView mSettingsTextMain;
private TextView mSettingsTextSub;
LauncherSettingController(final AppCompatActivity act)
{
mActivity = act;
}
public final LauncherSettingController<TSetting, TConf> init(final int rootViewResId)
{
return init(rootViewResId, null);
}
public final LauncherSettingController<TSetting, TConf> init(final int rootViewResId, final TConf config)
{
mSettingViewRoot = mActivity.findViewById(rootViewResId);
mSettingViewRoot.setOnClickListener(this);
mSettingsTextMain = (TextView) mSettingViewRoot.findViewById(R.id.inc_launcher_btn_main);
mSettingsTextSub = (TextView) mSettingViewRoot.findViewById(R.id.inc_launcher_btn_sub);
childrenInit(mSettingViewRoot);
updateConfig(config);
updateContent();
return this;
}
protected void childrenInit(final View root)
{
}
public void updateConfig(final TConf conf)
{
mConfig = conf;
updateContent();
}
public void updateContent()
{
mSettingsTextMain.setText(mainText());
if (mSettingsTextSub != null)
{
mSettingsTextSub.setText(subText());
}
}
protected abstract String mainText();
protected abstract String subText();
public void show()
{
mSettingViewRoot.setVisibility(View.VISIBLE);
}
public void hide()
{
mSettingViewRoot.setVisibility(View.GONE);
}
}

View File

@ -1,73 +0,0 @@
package eu.vcmi.vcmi.settings;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.appcompat.app.AlertDialog;
import java.util.ArrayList;
import java.util.List;
import eu.vcmi.vcmi.util.Log;
/**
* @author F
*/
public abstract class LauncherSettingDialog<T> extends DialogFragment
{
protected final List<T> mDataset;
private IOnItemChosen<T> mObserver;
protected LauncherSettingDialog(final List<T> dataset)
{
mDataset = dataset;
}
public void observe(final IOnItemChosen<T> observer)
{
mObserver = observer;
}
protected abstract CharSequence itemName(T item);
protected abstract int dialogTitleResId();
@NonNull
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState)
{
List<CharSequence> list = new ArrayList<>();
for (T t : mDataset)
{
CharSequence charSequence = itemName(t);
list.add(charSequence);
}
return new AlertDialog.Builder(getActivity())
.setTitle(dialogTitleResId())
.setItems(
list.toArray(new CharSequence[0]),
this::onItemChosenInternal)
.create();
}
private void onItemChosenInternal(final DialogInterface dialog, final int index)
{
final T chosenItem = mDataset.get(index);
Log.d(this, "Chosen item: " + chosenItem);
dialog.dismiss();
if (mObserver != null)
{
mObserver.onItemChosen(chosenItem);
}
}
public interface IOnItemChosen<V>
{
void onItemChosen(V item);
}
}

View File

@ -1,31 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import eu.vcmi.vcmi.util.Log;
/**
* @author F
*/
public abstract class LauncherSettingWithDialogController<T, Conf> extends LauncherSettingController<T, Conf>
implements LauncherSettingDialog.IOnItemChosen<T>
{
public static final String SETTING_DIALOG_ID = "settings.dialog";
protected LauncherSettingWithDialogController(final AppCompatActivity act)
{
super(act);
}
@Override
public void onClick(final View v)
{
Log.i(this, "Showing dialog");
final LauncherSettingDialog<T> dialog = dialog();
dialog.observe(this); // TODO rebinding dialogs on activity config changes
dialog.show(mActivity.getSupportFragmentManager(), SETTING_DIALOG_ID);
}
protected abstract LauncherSettingDialog<T> dialog();
}

View File

@ -1,83 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatSeekBar;
import android.view.View;
import android.widget.SeekBar;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public abstract class LauncherSettingWithSliderController<T, Conf> extends LauncherSettingController<T, Conf>
{
private AppCompatSeekBar mSlider;
private final int mSliderMin;
private final int mSliderMax;
protected LauncherSettingWithSliderController(final AppCompatActivity act, final int min, final int max)
{
super(act);
mSliderMin = min;
mSliderMax = max;
}
@Override
protected void childrenInit(final View root)
{
mSlider = (AppCompatSeekBar) root.findViewById(R.id.inc_launcher_btn_slider);
if (mSliderMax <= mSliderMin)
{
throw new IllegalArgumentException("slider min>=max");
}
mSlider.setMax(mSliderMax - mSliderMin);
mSlider.setOnSeekBarChangeListener(new OnValueChangedListener());
}
protected abstract void onValueChanged(final int v);
protected abstract int currentValue();
@Override
public void updateContent()
{
super.updateContent();
mSlider.setProgress(currentValue() + mSliderMin);
}
@Override
protected String subText()
{
return null; // not used with slider settings
}
@Override
public void onClick(final View v)
{
// not used with slider settings
}
private class OnValueChangedListener implements SeekBar.OnSeekBarChangeListener
{
@Override
public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser)
{
if (fromUser)
{
onValueChanged(progress);
}
}
@Override
public void onStartTrackingTouch(final SeekBar seekBar)
{
}
@Override
public void onStopTrackingTouch(final SeekBar seekBar)
{
}
}
}

View File

@ -1,38 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class ModsBtnController extends LauncherSettingController<Void, Void>
{
private View.OnClickListener mOnSelectedAction;
public ModsBtnController(final AppCompatActivity act, final View.OnClickListener onSelectedAction)
{
super(act);
mOnSelectedAction = onSelectedAction;
}
@Override
protected String mainText()
{
return mActivity.getString(R.string.launcher_btn_mods_title);
}
@Override
protected String subText()
{
return mActivity.getString(R.string.launcher_btn_mods_subtitle);
}
@Override
public void onClick(final View v)
{
mOnSelectedAction.onClick(v);
}
}

View File

@ -1,40 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import eu.vcmi.vcmi.Config;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class MusicSettingController extends LauncherSettingWithSliderController<Integer, Config>
{
public MusicSettingController(final AppCompatActivity act)
{
super(act, 0, 100);
}
@Override
protected void onValueChanged(final int v)
{
mConfig.updateMusic(v);
updateContent();
}
@Override
protected int currentValue()
{
if (mConfig == null)
{
return Config.DEFAULT_MUSIC_VALUE;
}
return mConfig.mVolumeMusic;
}
@Override
protected String mainText()
{
return mActivity.getString(R.string.launcher_btn_music_title);
}
}

View File

@ -1,63 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import eu.vcmi.vcmi.Config;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class PointerModeSettingController
extends LauncherSettingWithDialogController<PointerModeSettingController.PointerMode, Config>
{
public PointerModeSettingController(final AppCompatActivity activity)
{
super(activity);
}
@Override
protected LauncherSettingDialog<PointerMode> dialog()
{
return new PointerModeSettingDialog();
}
@Override
public void onItemChosen(final PointerMode item)
{
mConfig.setPointerMode(item == PointerMode.RELATIVE);
updateContent();
}
@Override
protected String mainText()
{
return mActivity.getString(R.string.launcher_btn_pointermode_title);
}
@Override
protected String subText()
{
if (mConfig == null)
{
return "";
}
return mActivity.getString(R.string.launcher_btn_pointermode_subtitle,
PointerModeSettingDialog.pointerModeToUserString(mActivity, getPointerMode()));
}
private PointerMode getPointerMode()
{
if(mConfig.getPointerModeIsRelative())
{
return PointerMode.RELATIVE;
}
return PointerMode.NORMAL;
}
public enum PointerMode
{
NORMAL,
RELATIVE;
}
}

View File

@ -1,55 +0,0 @@
package eu.vcmi.vcmi.settings;
import android.content.Context;
import java.util.ArrayList;
import java.util.List;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class PointerModeSettingDialog extends LauncherSettingDialog<PointerModeSettingController.PointerMode>
{
private static final List<PointerModeSettingController.PointerMode> POINTER_MODES = new ArrayList<>();
static
{
POINTER_MODES.add(PointerModeSettingController.PointerMode.NORMAL);
POINTER_MODES.add(PointerModeSettingController.PointerMode.RELATIVE);
}
public PointerModeSettingDialog()
{
super(POINTER_MODES);
}
public static String pointerModeToUserString(
final Context ctx,
final PointerModeSettingController.PointerMode pointerMode)
{
switch (pointerMode)
{
default:
return "";
case NORMAL:
return ctx.getString(R.string.misc_pointermode_normal);
case RELATIVE:
return ctx.getString(R.string.misc_pointermode_relative);
}
}
@Override
protected int dialogTitleResId()
{
return R.string.launcher_btn_pointermode_title;
}
@Override
protected CharSequence itemName(final PointerModeSettingController.PointerMode item)
{
return pointerModeToUserString(getContext(), item);
}
}

View File

@ -1,51 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import eu.vcmi.vcmi.Config;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class PointerMultiplierSettingController
extends LauncherSettingWithDialogController<Float, Config>
{
public PointerMultiplierSettingController(final AppCompatActivity activity)
{
super(activity);
}
@Override
protected LauncherSettingDialog<Float> dialog()
{
return new PointerMultiplierSettingDialog();
}
@Override
public void onItemChosen(final Float item)
{
mConfig.setPointerSpeedMultiplier(item);
updateContent();
}
@Override
protected String mainText()
{
return mActivity.getString(R.string.launcher_btn_pointermulti_title);
}
@Override
protected String subText()
{
if (mConfig == null)
{
return "";
}
String pointerModeString = PointerMultiplierSettingDialog.pointerMultiplierToUserString(
mConfig.getPointerSpeedMultiplier());
return mActivity.getString(R.string.launcher_btn_pointermulti_subtitle, pointerModeString);
}
}

View File

@ -1,48 +0,0 @@
package eu.vcmi.vcmi.settings;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class PointerMultiplierSettingDialog extends LauncherSettingDialog<Float>
{
private static final List<Float> AVAILABLE_MULTIPLIERS = new ArrayList<>();
static
{
AVAILABLE_MULTIPLIERS.add(1.0f);
AVAILABLE_MULTIPLIERS.add(1.25f);
AVAILABLE_MULTIPLIERS.add(1.5f);
AVAILABLE_MULTIPLIERS.add(1.75f);
AVAILABLE_MULTIPLIERS.add(2.0f);
AVAILABLE_MULTIPLIERS.add(2.5f);
AVAILABLE_MULTIPLIERS.add(3.0f);
}
public PointerMultiplierSettingDialog()
{
super(AVAILABLE_MULTIPLIERS);
}
@Override
protected int dialogTitleResId()
{
return R.string.launcher_btn_pointermode_title;
}
@Override
protected CharSequence itemName(final Float item)
{
return pointerMultiplierToUserString(item);
}
public static String pointerMultiplierToUserString(final float multiplier)
{
return String.format(Locale.US, "%.2fx", multiplier);
}
}

View File

@ -1,64 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import eu.vcmi.vcmi.Config;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class ScreenScaleSettingController extends LauncherSettingWithDialogController<ScreenScaleSettingController.ScreenScale, Config>
{
public ScreenScaleSettingController(final AppCompatActivity activity)
{
super(activity);
}
@Override
protected LauncherSettingDialog<ScreenScale> dialog()
{
return new ScreenScaleSettingDialog(mActivity);
}
@Override
public void onItemChosen(final ScreenScale item)
{
mConfig.updateScreenScale(item.mScreenScale);
updateContent();
}
@Override
protected String mainText()
{
return mActivity.getString(R.string.launcher_btn_scale_title);
}
@Override
protected String subText()
{
if (mConfig == null)
{
return "";
}
return mConfig.mScreenScale <= 0
? mActivity.getString(R.string.launcher_btn_scale_subtitle_unknown)
: mActivity.getString(R.string.launcher_btn_scale_subtitle, mConfig.mScreenScale);
}
public static class ScreenScale
{
public int mScreenScale;
public ScreenScale(final int scale)
{
mScreenScale = scale;
}
@Override
public String toString()
{
return mScreenScale + "%";
}
}
}

View File

@ -1,98 +0,0 @@
package eu.vcmi.vcmi.settings;
import android.app.Activity;
import android.graphics.Point;
import android.view.WindowMetrics;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import eu.vcmi.vcmi.R;
import eu.vcmi.vcmi.Storage;
import eu.vcmi.vcmi.util.FileUtil;
/**
* @author F
*/
public class ScreenScaleSettingDialog extends LauncherSettingDialog<ScreenScaleSettingController.ScreenScale>
{
public ScreenScaleSettingDialog(Activity mActivity)
{
super(loadScales(mActivity));
}
@Override
protected int dialogTitleResId()
{
return R.string.launcher_btn_scale_title;
}
@Override
protected CharSequence itemName(final ScreenScaleSettingController.ScreenScale item)
{
return item.toString();
}
public static int[] getSupportedScalingRange(Activity activity) {
Point screenRealSize = new Point();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();
screenRealSize.x = windowMetrics.getBounds().width();
screenRealSize.y = windowMetrics.getBounds().height();
} else {
activity.getWindowManager().getDefaultDisplay().getRealSize(screenRealSize);
}
if (screenRealSize.x < screenRealSize.y) {
int tmp = screenRealSize.x;
screenRealSize.x = screenRealSize.y;
screenRealSize.y = tmp;
}
// H3 resolution, any resolution smaller than that is not correctly supported
Point minResolution = new Point(800, 600);
// arbitrary limit on *downscaling*. Allow some downscaling, if requested by user. Should be generally limited to 100+ for all but few devices
double minimalScaling = 50;
Point renderResolution = screenRealSize;
double maximalScalingWidth = 100.0 * renderResolution.x / minResolution.x;
double maximalScalingHeight = 100.0 * renderResolution.y / minResolution.y;
double maximalScaling = Math.min(maximalScalingWidth, maximalScalingHeight);
return new int[] { (int)minimalScaling, (int)maximalScaling };
}
private static List<ScreenScaleSettingController.ScreenScale> loadScales(Activity activity)
{
List<ScreenScaleSettingController.ScreenScale> availableScales = new ArrayList<>();
try
{
int[] supportedScalingRange = getSupportedScalingRange(activity);
for (int i = 0; i <= supportedScalingRange[1] + 10 - 1; i += 10)
{
if (i >= supportedScalingRange[0])
availableScales.add(new ScreenScaleSettingController.ScreenScale(i));
}
if(availableScales.isEmpty())
{
availableScales.add(new ScreenScaleSettingController.ScreenScale(100));
}
}
catch(Exception ex)
{
ex.printStackTrace();
availableScales.clear();
availableScales.add(new ScreenScaleSettingController.ScreenScale(100));
}
return availableScales;
}
}

View File

@ -1,40 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import eu.vcmi.vcmi.Config;
import eu.vcmi.vcmi.R;
/**
* @author F
*/
public class SoundSettingController extends LauncherSettingWithSliderController<Integer, Config>
{
public SoundSettingController(final AppCompatActivity act)
{
super(act, 0, 100);
}
@Override
protected void onValueChanged(final int v)
{
mConfig.updateSound(v);
updateContent();
}
@Override
protected int currentValue()
{
if (mConfig == null)
{
return Config.DEFAULT_SOUND_VALUE;
}
return mConfig.mVolumeSound;
}
@Override
protected String mainText()
{
return mActivity.getString(R.string.launcher_btn_sound_title);
}
}

View File

@ -1,39 +0,0 @@
package eu.vcmi.vcmi.settings;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import eu.vcmi.vcmi.R;
import eu.vcmi.vcmi.util.GeneratedVersion;
/**
* @author F
*/
public class StartGameController extends LauncherSettingController<Void, Void>
{
private View.OnClickListener mOnSelectedAction;
public StartGameController(final AppCompatActivity act, final View.OnClickListener onSelectedAction)
{
super(act);
mOnSelectedAction = onSelectedAction;
}
@Override
protected String mainText()
{
return mActivity.getString(R.string.launcher_btn_start_title);
}
@Override
protected String subText()
{
return mActivity.getString(R.string.launcher_btn_start_subtitle, GeneratedVersion.VCMI_VERSION);
}
@Override
public void onClick(final View v)
{
mOnSelectedAction.onClick(v);
}
}

View File

@ -1,49 +0,0 @@
package eu.vcmi.vcmi.util;
import android.annotation.TargetApi;
import android.os.AsyncTask;
import android.os.Build;
import androidx.annotation.RequiresApi;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Scanner;
import eu.vcmi.vcmi.Const;
/**
* @author F
*/
public abstract class AsyncRequest<T> extends AsyncTask<String, Void, ServerResponse<T>>
{
@TargetApi(Const.SUPPRESS_TRY_WITH_RESOURCES_WARNING)
protected ServerResponse<T> sendRequest(final String url)
{
try
{
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
final int responseCode = conn.getResponseCode();
if (!ServerResponse.isResponseCodeValid(responseCode))
{
return new ServerResponse<>(responseCode, null);
}
try (Scanner s = new java.util.Scanner(conn.getInputStream()).useDelimiter("\\A"))
{
final String response = s.hasNext() ? s.next() : "";
return new ServerResponse<>(responseCode, response);
}
catch (final Exception e)
{
Log.e(this, "Request failed: ", e);
}
}
catch (final Exception e)
{
Log.e(this, "Request failed: ", e);
}
return new ServerResponse<>(ServerResponse.LOCAL_ERROR_IO, null);
}
}

View File

@ -1,350 +0,0 @@
package eu.vcmi.vcmi.util;
import android.annotation.TargetApi;
import android.content.res.AssetManager;
import android.os.Environment;
import android.text.TextUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import eu.vcmi.vcmi.Const;
/**
* @author F
*/
@TargetApi(Const.SUPPRESS_TRY_WITH_RESOURCES_WARNING)
public class FileUtil
{
private static final int BUFFER_SIZE = 4096;
public static String read(final InputStream stream) throws IOException
{
try (InputStreamReader reader = new InputStreamReader(stream))
{
return readInternal(reader);
}
}
public static String read(final File file) throws IOException
{
try (FileReader reader = new FileReader(file))
{
return readInternal(reader);
}
catch (final FileNotFoundException ignored)
{
Log.w("Could not load file: " + file);
return null;
}
}
private static String readInternal(final InputStreamReader reader) throws IOException
{
final char[] buffer = new char[BUFFER_SIZE];
int currentRead;
final StringBuilder content = new StringBuilder();
while ((currentRead = reader.read(buffer, 0, BUFFER_SIZE)) >= 0)
{
content.append(buffer, 0, currentRead);
}
return content.toString();
}
public static void write(final File file, final String data) throws IOException
{
if (!ensureWriteable(file))
{
Log.e("Couldn't write " + data + " to " + file);
return;
}
try (final FileWriter fw = new FileWriter(file, false))
{
Log.v(null, "Saving data: " + data + " to " + file.getAbsolutePath());
fw.write(data);
}
}
private static boolean ensureWriteable(final File file)
{
if (file == null)
{
Log.e("Broken path given to fileutil::ensureWriteable");
return false;
}
final File dir = file.getParentFile();
if (dir.exists() || dir.mkdirs())
{
return true;
}
Log.e("Couldn't create dir " + dir);
return false;
}
public static boolean clearDirectory(final File dir)
{
if (dir == null || dir.listFiles() == null)
{
Log.e("Broken path given to fileutil::clearDirectory");
return false;
}
for (final File f : dir.listFiles())
{
if (f.isDirectory() && !clearDirectory(f))
{
return false;
}
if (!f.delete())
{
return false;
}
}
return true;
}
public static void copyDir(final File srcFile, final File dstFile)
{
File[] files = srcFile.listFiles();
if(!dstFile.exists()) dstFile.mkdir();
if(files == null)
return;
for (File child : files){
File childTarget = new File(dstFile, child.getName());
if(child.isDirectory()){
copyDir(child, childTarget);
}
else{
copyFile(child, childTarget);
}
}
}
public static boolean copyFile(final File srcFile, final File dstFile)
{
if (!srcFile.exists())
{
return false;
}
final File dstDir = dstFile.getParentFile();
if (!dstDir.exists())
{
if (!dstDir.mkdirs())
{
Log.w("Couldn't create dir to copy file: " + dstFile);
return false;
}
}
try (final FileInputStream input = new FileInputStream(srcFile);
final FileOutputStream output = new FileOutputStream(dstFile))
{
copyStream(input, output);
return true;
}
catch (final Exception ex)
{
Log.e("Couldn't copy " + srcFile + " to " + dstFile, ex);
return false;
}
}
public static void copyStream(InputStream source, OutputStream target) throws IOException
{
final byte[] buffer = new byte[BUFFER_SIZE];
int read;
while ((read = source.read(buffer)) != -1)
{
target.write(buffer, 0, read);
}
}
// (when internal data have changed)
public static boolean reloadVcmiDataToInternalDir(final File vcmiInternalDir, final AssetManager assets)
{
return clearDirectory(vcmiInternalDir) && unpackVcmiDataToInternalDir(vcmiInternalDir, assets);
}
public static boolean unpackVcmiDataToInternalDir(final File vcmiInternalDir, final AssetManager assets)
{
try
{
final InputStream inputStream = assets.open("internalData.zip");
final boolean success = unpackZipFile(inputStream, vcmiInternalDir, null);
inputStream.close();
return success;
}
catch (final Exception e)
{
Log.e("Couldn't extract vcmi data to internal dir", e);
return false;
}
}
public static boolean unpackZipFile(
final File inputFile,
final File destDir,
IZipProgressReporter progressReporter)
{
try
{
final InputStream inputStream = new FileInputStream(inputFile);
final boolean success = unpackZipFile(
inputStream,
destDir,
progressReporter);
inputStream.close();
return success;
}
catch (final Exception e)
{
Log.e("Couldn't extract file to " + destDir, e);
return false;
}
}
public static int countFilesInZip(final File zipFile)
{
int totalEntries = 0;
try
{
final InputStream inputStream = new FileInputStream(zipFile);
ZipInputStream is = new ZipInputStream(inputStream);
ZipEntry zipEntry;
while ((zipEntry = is.getNextEntry()) != null)
{
totalEntries++;
}
is.closeEntry();
is.close();
inputStream.close();
}
catch (final Exception e)
{
Log.e("Couldn't count items in zip", e);
}
return totalEntries;
}
public static boolean unpackZipFile(
final InputStream inputStream,
final File destDir,
final IZipProgressReporter progressReporter)
{
try
{
int unpackedEntries = 0;
final byte[] buffer = new byte[BUFFER_SIZE];
ZipInputStream is = new ZipInputStream(inputStream);
ZipEntry zipEntry;
while ((zipEntry = is.getNextEntry()) != null)
{
final String fileName = zipEntry.getName();
final File newFile = new File(destDir, fileName);
if (newFile.exists())
{
Log.d("Already exists: " + newFile.getName());
continue;
}
else if (zipEntry.isDirectory())
{
Log.v("Creating new dir: " + zipEntry);
if (!newFile.mkdirs())
{
Log.e("Couldn't create directory " + newFile.getAbsolutePath());
return false;
}
continue;
}
final File parentFile = new File(newFile.getParent());
if (!parentFile.exists() && !parentFile.mkdirs())
{
Log.e("Couldn't create directory " + parentFile.getAbsolutePath());
return false;
}
final FileOutputStream fos = new FileOutputStream(newFile, false);
int currentRead;
while ((currentRead = is.read(buffer)) > 0)
{
fos.write(buffer, 0, currentRead);
}
fos.flush();
fos.close();
++unpackedEntries;
if(progressReporter != null)
{
progressReporter.onUnpacked(newFile);
}
}
Log.d("Unpacked data (" + unpackedEntries + " entries)");
is.closeEntry();
is.close();
return true;
}
catch (final Exception e)
{
Log.e("Couldn't extract vcmi data to " + destDir, e);
return false;
}
}
public static String configFileLocation(File filesDir)
{
return filesDir + "/config/settings.json";
}
public static String readAssetsStream(final AssetManager assets, final String assetPath)
{
if (assets == null || TextUtils.isEmpty(assetPath))
{
return null;
}
try (java.util.Scanner s = new java.util.Scanner(assets.open(assetPath), "UTF-8").useDelimiter("\\A"))
{
return s.hasNext() ? s.next() : null;
}
catch (final IOException e)
{
Log.e("Couldn't read stream data", e);
return null;
}
}
}

View File

@ -1,8 +0,0 @@
package eu.vcmi.vcmi.util;
import java.io.File;
public interface IZipProgressReporter
{
void onUnpacked(File newFile);
}

View File

@ -1,198 +0,0 @@
package eu.vcmi.vcmi.util;
import android.content.Context;
import android.nfc.FormatException;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
public class InstallModAsync
extends AsyncTask<String, String, Boolean>
implements IZipProgressReporter
{
private static final String TAG = "DOWNLOADFILE";
private static final int DOWNLOAD_PERCENT = 70;
private PostDownload callback;
private File downloadLocation;
private File extractLocation;
private Context context;
private int totalFiles;
private int unpackedFiles;
public InstallModAsync(File extractLocation, Context context, PostDownload callback)
{
this.context = context;
this.callback = callback;
this.extractLocation = extractLocation;
}
@Override
protected Boolean doInBackground(String... args)
{
int count;
try
{
File modsFolder = extractLocation.getParentFile();
if (!modsFolder.exists()) modsFolder.mkdir();
this.downloadLocation = File.createTempFile("tmp", ".zip", modsFolder);
URL url = new URL(args[0]);
URLConnection connection = url.openConnection();
connection.connect();
long lengthOfFile = -1;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
{
lengthOfFile = connection.getContentLengthLong();
}
if(lengthOfFile == -1)
{
try
{
lengthOfFile = Long.parseLong(connection.getHeaderField("Content-Length"));
Log.d(TAG, "Length of the file: " + lengthOfFile);
} catch (NumberFormatException ex)
{
Log.d(TAG, "Failed to parse content length", ex);
}
}
if(lengthOfFile == -1)
{
lengthOfFile = 100000000;
Log.d(TAG, "Using dummy length of file");
}
InputStream input = new BufferedInputStream(url.openStream());
FileOutputStream output = new FileOutputStream(downloadLocation); //context.openFileOutput("content.zip", Context.MODE_PRIVATE);
Log.d(TAG, "file saved at " + downloadLocation.getAbsolutePath());
byte data[] = new byte[1024];
long total = 0;
while ((count = input.read(data)) != -1)
{
total += count;
output.write(data, 0, count);
this.publishProgress((int) ((total * DOWNLOAD_PERCENT) / lengthOfFile) + "%");
}
output.flush();
output.close();
input.close();
File tempDir = File.createTempFile("tmp", "", modsFolder);
tempDir.delete();
tempDir.mkdir();
if (!extractLocation.exists()) extractLocation.mkdir();
try
{
totalFiles = FileUtil.countFilesInZip(downloadLocation);
unpackedFiles = 0;
FileUtil.unpackZipFile(downloadLocation, tempDir, this);
return moveModToExtractLocation(tempDir);
}
finally
{
downloadLocation.delete();
FileUtil.clearDirectory(tempDir);
tempDir.delete();
}
} catch (Exception e)
{
Log.e(TAG, "Unhandled exception while installing mod", e);
}
return false;
}
@Override
protected void onProgressUpdate(String... values)
{
callback.downloadProgress(values);
}
@Override
protected void onPostExecute(Boolean result)
{
if (callback != null) callback.downloadDone(result, extractLocation);
}
private boolean moveModToExtractLocation(File tempDir)
{
return moveModToExtractLocation(tempDir, 0);
}
private boolean moveModToExtractLocation(File tempDir, int level)
{
File[] modJson = tempDir.listFiles(new FileFilter()
{
@Override
public boolean accept(File file)
{
return file.getName().equalsIgnoreCase("Mod.json");
}
});
if (modJson != null && modJson.length > 0)
{
File modFolder = modJson[0].getParentFile();
if (!modFolder.renameTo(extractLocation))
{
FileUtil.copyDir(modFolder, extractLocation);
}
return true;
}
if (level <= 1)
{
for (File child : tempDir.listFiles())
{
if (child.isDirectory() && moveModToExtractLocation(child, level + 1))
{
return true;
}
}
}
return false;
}
@Override
public void onUnpacked(File newFile)
{
unpackedFiles++;
int progress = DOWNLOAD_PERCENT
+ (unpackedFiles * (100 - DOWNLOAD_PERCENT) / totalFiles);
publishProgress(progress + "%");
}
public interface PostDownload
{
void downloadDone(Boolean succeed, File modFolder);
void downloadProgress(String... progress);
}
}

View File

@ -12,9 +12,12 @@ import eu.vcmi.vcmi.NativeMethods;
*/
public final class LibsLoader
{
public static final String CLIENT_LIB = "vcmiclient_"
+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? Build.SUPPORTED_ABIS[0] : Build.CPU_ABI);
public static void loadClientLibs(Context ctx)
{
SDL.loadLibrary("vcmiclient");
SDL.loadLibrary(CLIENT_LIB);
SDL.setContext(ctx);
}

View File

@ -1,15 +1,6 @@
package eu.vcmi.vcmi.util;
import android.os.Environment;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Date;
import eu.vcmi.vcmi.BuildConfig;
import eu.vcmi.vcmi.Const;
/**
* @author F
@ -18,8 +9,6 @@ import eu.vcmi.vcmi.Const;
public class Log
{
private static final boolean LOGGING_ENABLED_CONSOLE = BuildConfig.DEBUG;
private static final boolean LOGGING_ENABLED_FILE = true;
private static final String FILELOG_PATH = "/" + Const.VCMI_DATA_ROOT_FOLDER_NAME + "/cache/VCMI_launcher.log";
private static final String TAG_PREFIX = "VCMI/";
private static final String STATIC_TAG = "static";
@ -34,19 +23,6 @@ public class Log
{
android.util.Log.println(priority, TAG_PREFIX + tagString, msg);
}
if (LOGGING_ENABLED_FILE) // this is probably very inefficient, but should be enough for now...
{
try
{
final BufferedWriter fileWriter = new BufferedWriter(new FileWriter(Environment.getExternalStorageDirectory() + FILELOG_PATH, true));
fileWriter.write(String.format("[%s] %s: %s\n", formatPriority(priority), tagString, msg));
fileWriter.flush();
fileWriter.close();
}
catch (IOException ignored)
{
}
}
}
private static String formatPriority(final int priority)
@ -77,23 +53,6 @@ public class Log
return obj.getClass().getSimpleName();
}
public static void init()
{
if (LOGGING_ENABLED_FILE) // clear previous log
{
try
{
final BufferedWriter fileWriter = new BufferedWriter(new FileWriter(Environment.getExternalStorageDirectory() + FILELOG_PATH, false));
fileWriter.write("Starting VCMI launcher log, " + DateFormat.getDateTimeInstance().format(new Date()) + "\n");
fileWriter.flush();
fileWriter.close();
}
catch (IOException ignored)
{
}
}
}
public static void v(final String msg)
{
logInternal(android.util.Log.VERBOSE, STATIC_TAG, msg);

View File

@ -1,30 +0,0 @@
package eu.vcmi.vcmi.util;
/**
* @author F
*/
public class ServerResponse<T>
{
public static final int LOCAL_ERROR_IO = -1;
public static final int LOCAL_ERROR_PARSING = -2;
public int mCode;
public String mRawContent;
public T mContent;
public ServerResponse(final int code, final String content)
{
mCode = code;
mRawContent = content;
}
public static boolean isResponseCodeValid(final int responseCode)
{
return responseCode >= 200 && responseCode < 300;
}
public boolean isValid()
{
return isResponseCodeValid(mCode);
}
}

View File

@ -1,92 +0,0 @@
package eu.vcmi.vcmi.util;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
/**
* simple shared preferences wrapper
*
* @author F
*/
public class SharedPrefs
{
public static final String KEY_CURRENT_INTERNAL_ASSET_HASH = "KEY_CURRENT_INTERNAL_ASSET_HASH"; // [string]
private static final String VCMI_PREFS_NAME = "VCMIPrefs";
private final SharedPreferences mPrefs;
public SharedPrefs(final Context ctx)
{
mPrefs = ctx.getSharedPreferences(VCMI_PREFS_NAME, Context.MODE_PRIVATE);
}
public void save(final String name, final String value)
{
mPrefs.edit().putString(name, value).apply();
log(name, value, true);
}
public String load(final String name, final String defaultValue)
{
return log(name, mPrefs.getString(name, defaultValue), false);
}
public void save(final String name, final int value)
{
mPrefs.edit().putInt(name, value).apply();
log(name, value, true);
}
public int load(final String name, final int defaultValue)
{
return log(name, mPrefs.getInt(name, defaultValue), false);
}
public void save(final String name, final float value)
{
mPrefs.edit().putFloat(name, value).apply();
log(name, value, true);
}
public float load(final String name, final float defaultValue)
{
return log(name, mPrefs.getFloat(name, defaultValue), false);
}
public void save(final String name, final boolean value)
{
mPrefs.edit().putBoolean(name, value).apply();
log(name, value, true);
}
public boolean load(final String name, final boolean defaultValue)
{
return log(name, mPrefs.getBoolean(name, defaultValue), false);
}
public <T extends Enum<T>> void saveEnum(final String name, final T value)
{
mPrefs.edit().putInt(name, value.ordinal()).apply();
log(name, value, true);
}
@SuppressWarnings("unchecked")
public <T extends Enum<T>> T loadEnum(final String name, @NonNull final T defaultValue)
{
final int rawValue = mPrefs.getInt(name, defaultValue.ordinal());
return (T) log(name, defaultValue.getClass().getEnumConstants()[rawValue], false);
}
private <T> T log(final String key, final T value, final boolean saving)
{
if (saving)
{
Log.v(this, "[prefs saving] " + key + " => " + value);
}
else
{
Log.v(this, "[prefs loading] " + key + " => " + value);
}
return value;
}
}

View File

@ -1,58 +0,0 @@
package eu.vcmi.vcmi.util;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.util.DisplayMetrics;
/**
* @author F
*/
public final class Utils
{
private static String sAppVersionCache;
private Utils()
{
}
public static String appVersionName(final Context ctx)
{
if (sAppVersionCache == null)
{
final PackageManager pm = ctx.getPackageManager();
try
{
final PackageInfo info = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_META_DATA);
return sAppVersionCache = info.versionName;
}
catch (final PackageManager.NameNotFoundException e)
{
Log.e(ctx, "Couldn't resolve app version", e);
}
}
return sAppVersionCache;
}
public static float convertDpToPx(final Context ctx, final float dp)
{
return convertDpToPx(ctx.getResources(), dp);
}
public static float convertDpToPx(final Resources res, final float dp)
{
return dp * res.getDisplayMetrics().density;
}
public static float convertPxToDp(final Context ctx, final float px)
{
return convertPxToDp(ctx.getResources(), px);
}
public static float convertPxToDp(final Resources res, final float px)
{
return px / res.getDisplayMetrics().density;
}
}

View File

@ -1,52 +0,0 @@
package eu.vcmi.vcmi.viewmodels;
import android.view.View;
import androidx.lifecycle.ViewModel;
import androidx.databinding.PropertyChangeRegistry;
import androidx.databinding.Observable;
/**
* @author F
*/
public class ObservableViewModel extends ViewModel implements Observable
{
private PropertyChangeRegistry callbacks = new PropertyChangeRegistry();
@Override
public void addOnPropertyChangedCallback(
Observable.OnPropertyChangedCallback callback)
{
callbacks.add(callback);
}
@Override
public void removeOnPropertyChangedCallback(
Observable.OnPropertyChangedCallback callback)
{
callbacks.remove(callback);
}
public int visible(boolean isVisible)
{
return isVisible ? View.VISIBLE : View.GONE;
}
/**
* Notifies observers that all properties of this instance have changed.
*/
void notifyChange() {
callbacks.notifyCallbacks(this, 0, null);
}
/**
* Notifies observers that a specific property has changed. The getter for the
* property that changes should be marked with the @Bindable annotation to
* generate a field in the BR class to be used as the fieldId parameter.
*
* @param fieldId The generated BR id for the Bindable field.
*/
void notifyPropertyChanged(int fieldId) {
callbacks.notifyCallbacks(this, fieldId, null);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 B

View File

@ -1,30 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient android:startColor="#000000" android:endColor="#00000000" android:angle="270"/>
</shape>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z" />
</vector>

View File

@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4V6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z" />
</vector>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="4dp" />
<solid android:color="#A0000000" />
<stroke android:color="@color/accent" android:width="1dp" />
</shape>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:width="1px"
android:height="1px" />
<solid android:color="@color/accent" />
</shape>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<com.google.android.material.appbar.AppBarLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/bgMain">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/bgMain"
android:elevation="6dp"
app:elevation="6dp"
app:title="@string/launcher_title" />
</com.google.android.material.appbar.AppBarLayout>
</layout>

View File

@ -1,71 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/toolbar_include">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/side_margin">
<androidx.appcompat.widget.AppCompatTextView
style="@style/VCMI.Text.Header"
android:text="@string/app_name" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/about_version_app"
style="@style/VCMI.Text"
android:text="@string/about_version_app" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/about_version_launcher"
style="@style/VCMI.Text"
android:text="@string/about_version_launcher" />
</LinearLayout>
<include layout="@layout/inc_separator" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/VCMI.Text.LauncherSection"
android:text="@string/about_section_project" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/about_link_portal"
style="@style/VCMI.Entry.Clickable.AboutSimpleEntry"
android:text="@string/about_links_main" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/about_link_repo_main"
style="@style/VCMI.Entry.Clickable.AboutSimpleEntry"
android:text="@string/about_links_repo" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/about_link_repo_launcher"
style="@style/VCMI.Entry.Clickable.AboutSimpleEntry"
android:text="@string/about_links_repo_launcher" />
<include layout="@layout/inc_separator" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/VCMI.Text.LauncherSection"
android:text="@string/about_section_legal" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/about_btn_authors"
style="@style/VCMI.Entry.Clickable.AboutSimpleEntry"
android:text="@string/about_btn_authors" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/about_btn_privacy"
style="@style/VCMI.Entry.Clickable.AboutSimpleEntry"
android:text="@string/about_btn_privacy" />
</LinearLayout>
</ScrollView>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/error_message"
style="@style/VCMI.Text"
android:layout_margin="@dimen/side_margin"
android:text="@string/app_name" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/error_btn_try_again"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_margin="@dimen/side_margin"
android:layout_marginTop="20dp"
android:text="@string/misc_try_again" />
</LinearLayout>

View File

@ -16,4 +16,4 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
</FrameLayout>

View File

@ -1,106 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/toolbar_include"
android:clipChildren="false"
android:clipToPadding="false">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/launcher_version_info"
style="@style/VCMI.Text"
android:padding="@dimen/side_margin"
android:text="@string/app_name" />
<include layout="@layout/inc_separator" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/VCMI.Text.LauncherSection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="2dp"
android:text="@string/launcher_section_init"
app:elevation="2dp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="@+id/launcher_btn_start"
layout="@layout/inc_launcher_btn" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/launcher_error"
style="@style/VCMI.Text"
android:drawableLeft="@drawable/ic_error"
android:drawablePadding="10dp"
android:gravity="center_vertical"
android:minHeight="80dp"
android:padding="@dimen/side_margin"
android:text="@string/app_name" />
<ProgressBar
android:id="@+id/launcher_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
<include
android:id="@+id/launcher_btn_copy"
layout="@layout/inc_launcher_btn" />
<include
android:id="@+id/launcher_btn_export"
layout="@layout/inc_launcher_btn" />
<include layout="@layout/inc_separator" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/VCMI.Text.LauncherSection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/launcher_section_settings" />
<include
android:id="@+id/launcher_btn_mods"
layout="@layout/inc_launcher_btn" />
<include
android:id="@+id/launcher_btn_scale"
layout="@layout/inc_launcher_btn" />
<include
android:id="@+id/launcher_btn_adventure_ai"
layout="@layout/inc_launcher_btn" />
<include
android:id="@+id/launcher_btn_cp"
layout="@layout/inc_launcher_btn" />
<include
android:id="@+id/launcher_btn_pointer_mode"
layout="@layout/inc_launcher_btn" />
<include
android:id="@+id/launcher_btn_pointer_multi"
layout="@layout/inc_launcher_btn" />
<include
android:id="@+id/launcher_btn_volume_sound"
layout="@layout/inc_launcher_slider" />
<include
android:id="@+id/launcher_btn_volume_music"
layout="@layout/inc_launcher_slider" />
</LinearLayout>
</ScrollView>

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="@+id/mods_data_root"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/toolbar_include"
android:clipChildren="false"
android:clipToPadding="false">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mods_recycler"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/mods_adapter_item" />
<TextView
android:id="@+id/mods_error_text"
style="@style/VCMI.Text"
android:layout_marginTop="30dp"
android:gravity="center" />
<ProgressBar
android:id="@+id/mods_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/toolbar_include"
layout="@layout/inc_toolbar" />
<ViewStub
android:id="@+id/toolbar_wrapper_content_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/toolbar_include" />
</RelativeLayout>
</layout>

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
style="@style/VCMI.Text.LauncherSection"
android:text="@string/dialog_authors_vcmi" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/dialog_authors_vcmi"
style="@style/VCMI.Text"
android:padding="@dimen/side_margin" />
<include layout="@layout/inc_separator" />
<!-- TODO should this be separate or just merged with vcmi authors? -->
<androidx.appcompat.widget.AppCompatTextView
style="@style/VCMI.Text.LauncherSection"
android:text="@string/dialog_authors_launcher" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/dialog_authors_launcher"
style="@style/VCMI.Text"
android:padding="@dimen/side_margin" />
</LinearLayout>
</ScrollView>

View File

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="title"
type="java.lang.String" />
<variable
name="description"
type="java.lang.String" />
</data>
<RelativeLayout
style="@style/VCMI.Entry.Clickable"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/inc_launcher_btn_main"
style="@style/VCMI.Text.LauncherEntry"
android:text="@{title}" />
<TextView
android:id="@+id/inc_launcher_btn_sub"
style="@style/VCMI.Text.LauncherEntry.Sub"
android:text="@{description}" />
</LinearLayout>
</RelativeLayout>
</layout>

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
style="@style/VCMI.Entry"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/inc_launcher_btn_main"
style="@style/VCMI.Text.LauncherEntry"
android:text="@string/app_name" />
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/inc_launcher_btn_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp" />
</LinearLayout>
</RelativeLayout>
</layout>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/separator" />
</layout>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:clipChildren="false"
android:clipToPadding="false">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/bgMain"
android:elevation="6dp"
app:elevation="6dp"
app:title="@string/launcher_title" />
<ImageView
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_alignParentBottom="true"
android:layout_marginBottom="-4dp"
android:background="@drawable/compat_toolbar_shadow" />
</RelativeLayout>
</layout>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal">
<View
android:id="@+id/mods_adapter_item_nesting"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@color/accent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mods_adapter_item_name"
style="@style/VCMI.Text"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_marginLeft="8dp"
android:drawableLeft="@drawable/ic_error"
android:drawablePadding="18dp"
android:gravity="center_vertical"
android:padding="@dimen/side_margin"
android:text="@string/mods_failed_mod_loading" />
</LinearLayout>

View File

@ -1,101 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?attr/selectableItemBackground"
android:minHeight="@dimen/entry_min_height"
android:orientation="horizontal">
<View
android:id="@+id/mods_adapter_item_nesting"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@color/accent" />
<LinearLayout
style="@style/VCMI.Entry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:elevation="4dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="5dp"
android:paddingLeft="@dimen/side_margin"
android:paddingTop="5dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/mods_adapter_item_status"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginRight="10dp"
android:src="@drawable/ic_star_full" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mods_adapter_item_name"
style="@style/VCMI.Text.ModName"
android:ellipsize="end"
android:lines="1"
android:text="mod name, v1.0" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mods_adapter_item_author"
style="@style/VCMI.Text.ModAuthor"
android:ellipsize="end"
android:lines="1"
android:text="by mod author" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mods_adapter_item_modtype"
style="@style/VCMI.Text.ModType"
android:layout_width="wrap_content"
android:layout_gravity="right"
android:text="tools" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mods_adapter_item_size"
style="@style/VCMI.Text.ModSize"
android:layout_width="wrap_content"
android:layout_gravity="right"
android:text="1000 MB" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/mods_adapter_item_btn_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/side_margin"
android:src="@android:drawable/stat_sys_download" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/mods_adapter_item_btn_uninstall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/side_margin"
android:src="@android:drawable/ic_menu_delete" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/mods_adapter_item_install_progress"
android:padding="@dimen/side_margin" />
</LinearLayout>
</LinearLayout>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_launcher_about"
android:icon="@drawable/ic_info"
android:title="@string/menu_launcher_about"
app:showAsAction="always" />
</menu>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_mods_download_repo"
android:icon="@android:drawable/stat_sys_download"
android:title="@string/menu_mods_download_repo"
app:showAsAction="ifRoom" />
</menu>

View File

@ -1,71 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="url_project_page" translatable="false">https://vcmi.eu</string>
<string name="url_project_repo" translatable="false">https://github.com/vcmi/vcmi</string>
<string name="url_launcher_repo" translatable="false">https://github.com/vcmi/vcmi-android</string>
<string name="url_launcher_privacy" translatable="false">https://github.com/vcmi/vcmi/blob/master/docs/players/Privacy_Policy.md</string>
<string name="app_name">VCMI</string>
<string name="server_name">Server VCMI</string>
<string name="launcher_title">Spouštěč VCMI</string>
<string name="launcher_btn_scale_title">Škálování herního rozlišení</string>
<string name="launcher_btn_scale_subtitle_unknown">Současné: neznámé</string>
<string name="launcher_btn_scale_subtitle">Současné: %1$d%%</string>
<string name="launcher_btn_start_title">Spustit VCMI</string>
<string name="launcher_btn_start_subtitle">Současná verze VCMI: %1$s</string>
<string name="launcher_btn_mods_title">Modifikace</string>
<string name="launcher_btn_mods_subtitle">Nainstalovat nové frakce, přeměty a bonusy</string>
<string name="launcher_btn_language_title">Jazyk</string>
<string name="launcher_btn_language_subtitle_unknown">Současný: neznámý</string>
<string name="launcher_btn_language_subtitle">Současný: %1$s</string>
<string name="launcher_btn_pointermode_title">Změnit režim ukazatele</string>
<string name="launcher_btn_pointermode_subtitle">Současný: %1$s</string>
<string name="launcher_btn_pointermulti_title">Násobitel rychlosti relativního ukazatele</string>
<string name="launcher_btn_pointermulti_subtitle">Současný: %1$s</string>
<string name="launcher_btn_sound_title">Hlasitost zvuků</string>
<string name="launcher_btn_music_title">Hlasitost hudby</string>
<string name="launcher_btn_adventure_ai">AI světa</string>
<string name="launcher_btn_adventure_ai_title">Změnit AI světa</string>
<string name="launcher_btn_import_title">Importovat data VCMI</string>
<string name="launcher_btn_import_description">Zkopírovat soubury VCMI do vestavěného úložiště. Můžete importovat starou složku dat vcmi z vydání 0.99 nebo soubory HOMM3</string>
<string name="launcher_btn_export_title">Exportovat data VCMI</string>
<string name="launcher_btn_export_description">Udělat kopii dat VCMI před odinstalací nebo pro synchronizaci s desktopovou verzí. Též můžete přímo přistoupit k interním datům.</string>
<string name="launcher_progress_copy">Kopírování %1$s</string>
<string name="launcher_version">Současná verze spouštěče: %1$s</string>
<string name="launcher_error_vcmi_data_root_failed">Nelze vytvořit datovou složku VCMI v %1$s.</string>
<string name="launcher_error_h3_data_missing">Nelze najít datovou složku v \'%1$s\'. Vložte do ní své datové soubory HoMM3 nebo použijte tlačítko níže. Možná budete muset také restartovat aplikaci.</string>
<string name="launcher_error_vcmi_data_internal_missing">Nelze najít nebo rozbalit data vcmi ze zdrojů aplikace. Zkuste přeinstalovat aplikaci.</string>
<string name="launcher_error_vcmi_data_internal_update">Nelze aktualizovat data vcmi ze zdrojů aplikace. Zkuste přeinstalovat aplikaci.</string>
<string name="launcher_error_permissions">Tato aplikace potřebuje oprávnění k zápisu pro použití obsahu na externím úložišti</string>
<string name="launcher_error_permission_broken">Nelze správně vyřešit oprávnění</string>
<string name="mods_item_author_template">od %1$s</string>
<string name="misc_try_again">Zkusit znovu</string>
<string name="launcher_section_init">Inicializae hry</string>
<string name="launcher_section_settings">Nastavení</string>
<string name="menu_mods_download_repo">Stáhnout data repozitáře</string>
<string name="misc_pointermode_normal">Normální</string>
<string name="misc_pointermode_relative">Relativní</string>
<string name="menu_launcher_about">O spouštěči</string>
<string name="mods_title">Nalezené modifikace</string>
<string name="mods_failed_mod_loading">Nelze načíst modifikaci ve složce \'%1$s\'</string>
<string name="mods_removal_title">Odebírání %1$s</string>
<string name="mods_removal_confirmation">Jste si jisti odebráním %1$s?</string>
<string name="about_title">O aplikaci</string>
<string name="about_version_app">Verze aplikace: %1$s</string>
<string name="about_version_launcher">Verze spouštěče: %1$s</string>
<string name="about_section_project">Projekt</string>
<string name="about_section_legal">Právní záležitosti</string>
<string name="about_links_main">Hlavní stránka: %1$s</string>
<string name="about_links_repo">Repozitář projektu: %1$s</string>
<string name="about_links_repo_launcher">Repozitář spouštěče: %1$s</string>
<string name="about_btn_authors">Autoři</string>
<string name="about_btn_privacy">Zásady ochrany osobních údajů: %1$s</string>
<string name="about_error_opening_url">Nebylo možné otevřít webovou stránku (nenalezena patřičná aplikace)</string>
<string name="dialog_authors_vcmi">Autoři VCMI</string>
<string name="dialog_authors_launcher">Autoři spouštěče</string>
<string name="launcher_error_config_saving_failed">Nelze uložit konfigurační soubor VCMI; důvod: %1$s</string>
</resources>

View File

@ -1,62 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">VCMI</string>
<string name="launcher_btn_start_title">VCMI starten</string>
<string name="launcher_title">VCMI-Starter</string>
<string name="launcher_btn_scale_title">Skalierung der Spielauflösung</string>
<string name="launcher_btn_scale_subtitle_unknown">Aktuell: unbekannt</string>
<string name="launcher_btn_scale_subtitle">Aktuell: %1$d%%</string>
<string name="server_name">VCMI-Server</string>
<string name="launcher_btn_start_subtitle">Aktuelle VCMI-Version: %1$s</string>
<string name="launcher_btn_mods_title">Mods</string>
<string name="launcher_btn_mods_subtitle">Neue Burgen, Kreaturen, Objekte und Erweiterungen hinzufügen</string>
<string name="launcher_btn_language_title">Sprache</string>
<string name="launcher_btn_language_subtitle_unknown">Aktuell: unbekannt</string>
<string name="launcher_btn_language_subtitle">Aktuell: %1$s</string>
<string name="launcher_btn_pointermode_title">Zeigermodus ändern</string>
<string name="launcher_btn_pointermode_subtitle">Aktuell: %1$s</string>
<string name="launcher_btn_pointermulti_subtitle">Aktuell: %1$s</string>
<string name="launcher_version">Aktuelle Version des Starters: %1$s</string>
<string name="launcher_error_h3_data_missing">Der Datenordner in \'%1$s\' konnte nicht gefunden werden. Legen Sie Ihre HoMM3-Datendateien dort ab oder verwenden Sie die Schaltfläche unten, um diese zu importieren. Möglicherweise müssen Sie die Anwendung neu starten.</string>
<string name="launcher_btn_pointermulti_title">Multiplikator der relativen Zeigergeschwindigkeit</string>
<string name="launcher_btn_sound_title">Lautstärke der Geräusche</string>
<string name="launcher_btn_music_title">Lautstärke der Musik</string>
<string name="launcher_btn_adventure_ai">Abenteuer KI</string>
<string name="launcher_btn_adventure_ai_title">Abenteuer KI ändern</string>
<string name="launcher_error_vcmi_data_root_failed">VCMI-Datenordner in %1$s konnte nicht erstellt werden.</string>
<string name="launcher_error_vcmi_data_internal_missing">Ressourcendateien konnten nicht extrahiert werden. Versuchen Sie, die Anwendung neu zu installieren.</string>
<string name="launcher_error_vcmi_data_internal_update">Die Ressourcendateien konnten nicht aktualisiert werden. Versuchen Sie, die Anwendung neu zu installieren.</string>
<string name="launcher_error_permissions">Die Anwendung benötigt Rechte, um Inhalte auf externen Speicher zu schreiben.</string>
<string name="launcher_error_permission_broken">Keine Berechtigung erhalten.</string>
<string name="launcher_error_config_saving_failed">Einstellungen konnten nicht gespeichert werden; Grund: %1$s</string>
<string name="mods_item_author_template">Autor %1$s</string>
<string name="misc_try_again">Erneut versuchen</string>
<string name="launcher_section_init">Initialisierung</string>
<string name="launcher_section_settings">Einstellungen</string>
<string name="menu_mods_download_repo">Liste der Mods herunterladen</string>
<string name="misc_pointermode_normal">Normal</string>
<string name="misc_pointermode_relative">Relativ</string>
<string name="menu_launcher_about">Über</string>
<string name="mods_title">Installierte Mods</string>
<string name="mods_failed_mod_loading">Die Mod in dem Ordner \'%1$s\' kann nicht geladen werden.</string>
<string name="mods_removal_title">%1$s löschen</string>
<string name="mods_removal_confirmation">Sind Sie sicher, dass Sie %1$s löschen wollen?</string>
<string name="about_title">Über die Anwendung</string>
<string name="about_version_app">Version der Anwendung: %1$s</string>
<string name="about_version_launcher">Version des Startprogramms: %1$s</string>
<string name="about_section_project">Projekt</string>
<string name="about_section_legal">Rechtliches</string>
<string name="about_links_main">Website: %1$s</string>
<string name="about_links_repo">Projekt-Repository: %1$s</string>
<string name="about_links_repo_launcher">Starter-Repository: %1$s</string>
<string name="about_btn_authors">Autoren</string>
<string name="about_error_opening_url">Die Seite kann nicht geöffnet werden (wahrscheinlich konnte kein Browser gefunden werden)</string>
<string name="dialog_authors_vcmi">VCMI-Autoren</string>
<string name="dialog_authors_launcher">Autoren des Starters</string>
<string name="about_btn_privacy">Datenschutzbestimmungen: %1$s</string>
<string name="launcher_btn_export_title">VCMI-Daten exportieren</string>
<string name="launcher_btn_export_description">Erstellen Sie eine Kopie der internen VCMI-Daten vor der Deinstallation oder zur Synchronisierung der Speicherstände mit der Desktop-Version. Sie können auch direkt auf den internen Speicher zugreifen.</string>
<string name="launcher_btn_import_title">VCMI-Daten importieren</string>
<string name="launcher_btn_import_description">Kopieren Sie VCMI-Dateien in den internen Speicher. Sie können den alten vcmi-data-Ordner von Version 0.99 oder HoMM3-Dateien importieren</string>
<string name="launcher_progress_copy">Kopiere %1$s</string>
</resources>

View File

@ -1,71 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="url_project_page" translatable="false">https://vcmi.eu</string>
<string name="url_project_repo" translatable="false">https://github.com/vcmi/vcmi</string>
<string name="url_launcher_repo" translatable="false">https://github.com/vcmi/vcmi-android</string>
<string name="url_launcher_privacy" translatable="false">https://github.com/vcmi/vcmi/blob/master/docs/players/Privacy_Policy.md</string>
<string name="app_name">VCMI</string>
<string name="server_name">VCMI Servidor</string>
<string name="launcher_title">VCMI Lanzador</string>
<string name="launcher_btn_scale_title">Escalado de resolución</string>
<string name="launcher_btn_scale_subtitle_unknown">Seleccionado: desconocido</string>
<string name="launcher_btn_scale_subtitle">Seleccionado: %1$d%%</string>
<string name="launcher_btn_start_title">Iniciar VCMI</string>
<string name="launcher_btn_start_subtitle">version actual VCMI: %1$s</string>
<string name="launcher_btn_mods_title">Mods</string>
<string name="launcher_btn_mods_subtitle">Instalar nuevas facciones, Objectos, extras</string>
<string name="launcher_btn_language_title">Idioma</string>
<string name="launcher_btn_language_subtitle_unknown">Seleccionado: desconocido</string>
<string name="launcher_btn_language_subtitle">Seleccionado: %1$s</string>
<string name="launcher_btn_pointermode_title">Cambiar modo del puntero</string>
<string name="launcher_btn_pointermode_subtitle">Seleccionado: %1$s</string>
<string name="launcher_btn_pointermulti_title">Múltiplo de velocidad relativa del puntero</string>
<string name="launcher_btn_pointermulti_subtitle">Seleccionado: %1$s</string>
<string name="launcher_btn_sound_title">Sonido</string>
<string name="launcher_btn_music_title">Música</string>
<string name="launcher_btn_adventure_ai">Adventure AI</string>
<string name="launcher_btn_adventure_ai_title">Change adventure AI</string>
<string name="launcher_btn_import_title">Importar datos VCMI</string>
<string name="launcher_btn_import_description">Copiar ficheros VCMI al almacenamiento interno. Puedes importar datos antiguos a partir de la version 0.99 de VCMI o los ficheros de HOMM3.</string>
<string name="launcher_btn_export_title">Exportar datos VCMI</string>
<string name="launcher_btn_export_description">Hacer una copia interna de los datos VCMI antes de una desinstalación o sincronizar las partidas de la versión de escritorio. Tambien puedes acceder al almacenamiento interno directamente.</string>
<string name="launcher_progress_copy">Copiando %1$s</string>
<string name="launcher_version">Version del lanzador: %1$s</string>
<string name="launcher_error_vcmi_data_root_failed">No se puede crear la carpeta de datos VCMI en %1$s.</string>
<string name="launcher_error_h3_data_missing">No se encuentra la carpeta de datos en \'%1$s\'. Coloca tus ficheros de datos de HoMM3 en la carpeta o utiliza el boton de abajo para que se haga automáticamente. Quizás sea necesario reiniciar la aplicación.</string>
<string name="launcher_error_vcmi_data_internal_missing">No se pudieron encontrar ni extraer datos vcmi de los recursos de la aplicación. Intente reinstalar la aplicación.</string>
<string name="launcher_error_vcmi_data_internal_update">No se pudieron actualizar los datos de vcmi desde los recursos de la aplicación. Intente reinstalar la aplicación.</string>
<string name="launcher_error_permissions">Esta aplicación necesita permisos de escritura para utilizar el contenido almacenado en un almacenamiento externo.</string>
<string name="launcher_error_permission_broken">Los permisos no se pueden resolver correctamente</string>
<string name="mods_item_author_template">por %1$s</string>
<string name="misc_try_again">Volver a intentar</string>
<string name="launcher_section_init">Iniciar juego</string>
<string name="launcher_section_settings">Configuración</string>
<string name="menu_mods_download_repo">Descargar datos del repositorio</string>
<string name="misc_pointermode_normal">Normal</string>
<string name="misc_pointermode_relative">Relativo</string>
<string name="menu_launcher_about">Acerca</string>
<string name="mods_title">Mods detectados</string>
<string name="mods_failed_mod_loading">No se puede cargar el Mod en la carpeta \'%1$s\' </string>
<string name="mods_removal_title">Eliminando %1$s</string>
<string name="mods_removal_confirmation">Quieres eliminar %1$s</string>
<string name="about_title">Acerca de la applicación</string>
<string name="about_version_app">App versión: %1$s</string>
<string name="about_version_launcher">Versión del lanzador: %1$s</string>
<string name="about_section_project">Proyecto</string>
<string name="about_section_legal">Legal</string>
<string name="about_links_main">Página principal: %1$s</string>
<string name="about_links_repo">Repositorio del Proyecto: %1$s</string>
<string name="about_links_repo_launcher">Repositorio del lanzador: %1$s</string>
<string name="about_btn_authors">Autores</string>
<string name="about_btn_privacy">Política de privacidad: %1$s</string>
<string name="about_error_opening_url">No se pudo abrir la página web (no se encontró una aplicacion adecuada)</string>
<string name="dialog_authors_vcmi">VCMI autores</string>
<string name="dialog_authors_launcher">Lanzador autores</string>
<string name="launcher_error_config_saving_failed">No se puede guardar la configuración de VCMI; motivo: %1$s</string>
</resources>

View File

@ -1,64 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">VCMI</string>
<string name="server_name">VCMI Serwer</string>
<string name="launcher_title">VCMI Launcher</string>
<string name="launcher_btn_start_title">Włącz VCMI</string>
<string name="launcher_btn_start_subtitle">Obecna wersja VCMI: %1$s</string>
<string name="launcher_btn_mods_title">Mody</string>
<string name="launcher_btn_mods_subtitle">Zainstaluj nowe frakcje, obiekty, dodatki</string>
<string name="launcher_btn_language_title">Język</string>
<string name="launcher_btn_language_subtitle_unknown">Obecnie: nieznane</string>
<string name="launcher_btn_language_subtitle">Obecnie: %1$s</string>
<string name="launcher_btn_pointermode_title">Zmień tryb kursora</string>
<string name="launcher_btn_pointermode_subtitle">Obecnie: %1$s</string>
<string name="launcher_btn_pointermulti_title">Mnożnik prędkości kursora</string>
<string name="launcher_btn_pointermulti_subtitle">Obecnie: %1$s</string>
<string name="launcher_btn_sound_title">Głośność efektów dźwiękowych</string>
<string name="launcher_btn_music_title">Głośność muzyki</string>
<string name="launcher_btn_adventure_ai">Algorytm AI mapy przygody</string>
<string name="launcher_btn_adventure_ai_title">Zmień AI mapy przygody</string>
<string name="launcher_version">Obecna wersja launchera: %1$s</string>
<string name="launcher_error_vcmi_data_root_failed">Nie udało się stworzyć folderu na dane w %1$s.</string>
<string name="launcher_error_h3_data_missing">Nie znaleziono danych w \'%1$s\'. Przenieś tam pliki Heroes 3 (by mieć tam foldery %1$s/Data i %1$s/Mp3 itp) i uruchom jeszcze raz aplikację.</string>
<string name="launcher_error_vcmi_data_internal_missing">Nie udało się znaleźć lub wyodrębnić danych VCMI. Spróbuj zainstalować od nowa aplikację.</string>
<string name="launcher_error_vcmi_data_internal_update">Nie udało się zaktualizować danych VCMI. Spróbuj zainstalować od nowa aplikację.</string>
<string name="launcher_error_permissions">Ta aplikacja potrzebuje uprawnień do zapisu by mieć dostęp do magazynu zewnętrznego</string>
<string name="launcher_error_permission_broken">Nie udało się poprawnie obsłużyć uprawnień</string>
<string name="mods_item_author_template">przez %1$s</string>
<string name="misc_try_again">Spróbuj ponownie</string>
<string name="launcher_section_init">Inicjalizacja gry</string>
<string name="launcher_section_settings">Ustawienia</string>
<string name="menu_mods_download_repo">Pobierz dane modów z repozytorium</string>
<string name="misc_pointermode_normal">Normalny</string>
<string name="misc_pointermode_relative">Relatywny</string>
<string name="menu_launcher_about">O programie</string>
<string name="mods_title">Wykryte mody</string>
<string name="mods_failed_mod_loading">Nie udało się załadować moda w folderze \'%1$s\'</string>
<string name="mods_removal_title">Usuwanie %1$s</string>
<string name="mods_removal_confirmation">Czy na pewno chcesz usunąć %1$s</string>
<string name="about_title">O aplikacji</string>
<string name="about_version_app">Wersja aplikacji: %1$s</string>
<string name="about_version_launcher">Wersja launchera: %1$s</string>
<string name="about_section_project">Projekt</string>
<string name="about_section_legal">Kwestie prawne</string>
<string name="about_links_main">Strona główna: %1$s</string>
<string name="about_links_repo">Repozytorium projektu: %1$s</string>
<string name="about_links_repo_launcher">Repozytorium launchera: %1$s</string>
<string name="about_btn_authors">Autorzy</string>
<string name="about_btn_privacy" translatable="false">Polityka prywatności: %1$s</string>
<string name="about_error_opening_url">Nie udało się otworzyć witryny (nie znaleziono odpowiedniej aplikacji)</string>
<string name="dialog_authors_vcmi">Autorzy VCMI</string>
<string name="dialog_authors_launcher">Autorzy launchera</string>
<string name="launcher_error_config_saving_failed">Nie udało się zapisać konfiguracji VCMI; powód: %1$s</string>
<string name="launcher_btn_export_title">Eksportuj dane VCMI</string>
<string name="launcher_btn_export_description">Stwórz kopię wewnętrznych danych VCMI przed odinstalowaniem lub by zachować zapisane gry do uruchomienia na innym urządzeniu. Możesz też mieć bezpośrednio operować na elementach wewnętrznego magazynu.</string>
<string name="launcher_btn_import_title">Importuj dane VCMI</string>
<string name="launcher_btn_import_description">Skopiuj dane VCMI do wewnętrznego magazynu. Możesz zaimportować stary folder vcmi-data z wersji 0.99 lub pliki HOMM3.</string>
<string name="launcher_progress_copy">Kopiowanie %1$s</string>
</resources>

View File

@ -1,59 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">VCMI</string>
<string name="launcher_btn_start_title">Запустить VCMI</string>
<string name="launcher_title">VCMI лаунчер</string>
<string name="server_name">VCMI сервер</string>
<string name="launcher_btn_start_subtitle">Текущая версия VCMI: %1$s</string>
<string name="launcher_btn_mods_title">Моды</string>
<string name="launcher_btn_mods_subtitle">Добавить новые замки, существа, объекты, расширения</string>
<string name="launcher_btn_language_title">Язык</string>
<string name="launcher_btn_language_subtitle_unknown">Текущая: неизвестно</string>
<string name="launcher_btn_language_subtitle">Текущая: %1$s</string>
<string name="launcher_btn_pointermode_title">Изменить режим управления указателем</string>
<string name="launcher_btn_pointermode_subtitle">Currently: %1$s</string>
<string name="launcher_btn_pointermulti_subtitle">Текущая: %1$s</string>
<string name="launcher_version">Текущая версия лаунчера: %1$s</string>
<string name="launcher_error_h3_data_missing">Не удалось найти файлы Героев в \'%1$s\'. Скопируйте ваши файлы Героев 3 туда вручную или воспользуйтесь кнопкой копирования и перезапустите игру.</string>
<string name="launcher_btn_pointermulti_title">Относительная скорость управления указателем</string>
<string name="launcher_btn_sound_title">Громкость звуков</string>
<string name="launcher_btn_music_title">Громкость музыки</string>
<string name="launcher_btn_adventure_ai">ИИ карты</string>
<string name="launcher_btn_adventure_ai_title">Изменить ИИ карты</string>
<string name="launcher_error_vcmi_data_root_failed">Не удалось создать папку VCMI с данными в %1$s.</string>
<string name="launcher_error_vcmi_data_internal_missing">Не удалось извлечь файлы ресурсов. Попробуйте переустановить приложение.</string>
<string name="launcher_error_vcmi_data_internal_update">Не удалось обновить файлы ресурсов. Попробуйте переустановить приложение.</string>
<string name="launcher_error_permissions">Прилодению необходимы права для записи контента во внешнее хранилище.</string>
<string name="launcher_error_permission_broken">Не удалось получить права.</string>
<string name="launcher_error_config_saving_failed">Не удалось сохранить настройки; причина: %1$s</string>
<string name="mods_item_author_template">автор %1$s</string>
<string name="misc_try_again">Попробуйте еще раз</string>
<string name="launcher_section_init">Инициализация</string>
<string name="launcher_section_settings">Настройки</string>
<string name="menu_mods_download_repo">Скачать список модов</string>
<string name="misc_pointermode_normal">Обычное</string>
<string name="misc_pointermode_relative">Относительное</string>
<string name="menu_launcher_about">О приложении</string>
<string name="mods_title">Установленные моды</string>
<string name="mods_failed_mod_loading">Не удалось загрузить мод в папку \'%1$s\'</string>
<string name="mods_removal_title">Удаление %1$s</string>
<string name="mods_removal_confirmation">Вы уверены что хотите удалить %1$s</string>
<string name="about_title">О приложении</string>
<string name="about_version_app">Версия приложения: %1$s</string>
<string name="about_version_launcher">Версия лаунчера: %1$s</string>
<string name="about_section_project">Проект</string>
<string name="about_section_legal">Legal</string>
<string name="about_links_main">Сайт: %1$s</string>
<string name="about_links_repo">Репозиторий проекта: %1$s</string>
<string name="about_links_repo_launcher">Репозиторий лаунчера: %1$s</string>
<string name="about_btn_authors">Авторы</string>
<string name="about_error_opening_url">Не удалось открыть страничку (вероятно не удалось найти браузер)</string>
<string name="dialog_authors_vcmi">Авторы VCMI</string>
<string name="dialog_authors_launcher">Авторы лаунчера</string>
<string name="about_btn_privacy" translatable="false">Политика конфиденциальности: %1$s</string>
<string name="launcher_btn_export_title">Экспортировать данные VCMI</string>
<string name="launcher_btn_export_description">Сделать копию данных VCMI перед удалением игры или чтобы перенести файлы сохранений на версию для других платформ</string>
<string name="launcher_btn_import_title">Загрузить данные VCMI во внутреннее хранилище</string>
<string name="launcher_btn_import_description">Скопировать данные VCMI во внутреннее хранилище. Вы можете загрузить старую папку vcmi-data от версии 0.99 или файлы Героев</string>
<string name="launcher_progress_copy">Копируем %1$s</string>
</resources>

View File

@ -1,59 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">VCMI</string>
<string name="launcher_btn_start_title">Запустити VCMI</string>
<string name="launcher_title">VCMI лаунчер</string>
<string name="server_name">VCMI сервер</string>
<string name="launcher_btn_start_subtitle">Поточна версія VCMI: %1$s</string>
<string name="launcher_btn_mods_title">Моди</string>
<string name="launcher_btn_mods_subtitle">Додати нові замки, істоти, об’єкти, розширення</string>
<string name="launcher_btn_language_title">Мова</string>
<string name="launcher_btn_language_subtitle_unknown">Поточна: невідомо</string>
<string name="launcher_btn_language_subtitle">Поточна: %1$s</string>
<string name="launcher_btn_pointermode_title">Змінити режим керування курсором</string>
<string name="launcher_btn_pointermode_subtitle">Поточна: %1$s</string>
<string name="launcher_btn_pointermulti_subtitle">Поточна: %1$s</string>
<string name="launcher_version">Поточна версія лаунчера: %1$s</string>
<string name="launcher_error_h3_data_missing">Не вдалося знайти файли Героїв у \'%1$s\'. Скопіюйте файли Героїв 3 туди вручну або скористайтеся кнопкою копіювання і перезапустіть гру.</string>
<string name="launcher_btn_pointermulti_title">Відносна швидкість керування курсором</string>
<string name="launcher_btn_sound_title">Гучність звуків</string>
<string name="launcher_btn_music_title">Гучність музики</string>
<string name="launcher_btn_adventure_ai">Комп’ютерний гравець</string>
<string name="launcher_btn_adventure_ai_title">Змінити програму комп’ютерного гравця</string>
<string name="launcher_error_vcmi_data_root_failed">Не вдалося створити теку VCMI з даними в %1$s.</string>
<string name="launcher_error_vcmi_data_internal_missing">Не вдалося розпакувати файли ресурсів. Спробуйте перевстановити програму.</string>
<string name="launcher_error_vcmi_data_internal_update">Не вдалося оновити файли ресурсів. Спробуйте перевстановити програму.</string>
<string name="launcher_error_permissions">VCMI необхідні права для запису контенту до зовнішнього сховища.</string>
<string name="launcher_error_permission_broken">Не вдалося отримати права.</string>
<string name="launcher_error_config_saving_failed">Не вдалося зберегти налаштування; причина: %1$s</string>
<string name="mods_item_author_template">автор %1$s</string>
<string name="misc_try_again">Спробуйте ще раз</string>
<string name="launcher_section_init">Ініціалізація</string>
<string name="launcher_section_settings">Налаштування</string>
<string name="menu_mods_download_repo">Завантажити список модів</string>
<string name="misc_pointermode_normal">Звичайне</string>
<string name="misc_pointermode_relative">Відносне</string>
<string name="menu_launcher_about">Про VCMI</string>
<string name="mods_title">Встановлені моди</string>
<string name="mods_failed_mod_loading">Не вдалося завантажити мод в теку \'%1$s\'</string>
<string name="mods_removal_title">Видалення %1$s</string>
<string name="mods_removal_confirmation">Ви впевнені, що хочете видалити %1$s</string>
<string name="about_title">Про VCMI</string>
<string name="about_version_app">Версія VCMI: %1$s</string>
<string name="about_version_launcher">Версія лаунчера: %1$s</string>
<string name="about_section_project">Проєкт</string>
<string name="about_section_legal">Legal</string>
<string name="about_links_main">Сайт: %1$s</string>
<string name="about_links_repo">Репозиторій проєкту: %1$s</string>
<string name="about_links_repo_launcher">Репозиторій лаунчера: %1$s</string>
<string name="about_btn_authors">Автори</string>
<string name="about_error_opening_url">Не вдалося відкрити сторінку (ймовірно, не вдалося знайти браузер)</string>
<string name="dialog_authors_vcmi">Автори VCMI</string>
<string name="dialog_authors_launcher">Автори лаунчера</string>
<string name="about_btn_privacy">Політика конфіденційності: %1$s</string>
<string name="launcher_btn_export_title">Експортувати дані VCMI</string>
<string name="launcher_btn_export_description">Зробити копію даних VCMI перед видаленням гри або перенести сейв-файли на версію для інших платформ</string>
<string name="launcher_btn_import_title">Завантажити дані VCMI у внутрішнє сховище</string>
<string name="launcher_btn_import_description">Копіювати дані VCMI у внутрішнє сховище. Ви можете завантажити стару теку vcmi-data від версії 0.99 чи файли героїв</string>
<string name="launcher_progress_copy">Копіюємо %1$s</string>
</resources>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.VCMI" parent="@style/Base.Theme.VCMI">
<item name="android:listDivider">@drawable/recycler_divider_drawable</item>
</style>
</resources>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="textMain">#FFFFFF</color>
<color name="textSecondary">#AAAAAA</color>
<color name="bgMain">#2C332C</color>
<color name="bgDark">#1F221F</color>
<color name="accent">#BBBB55</color>
<color name="separator">@color/accent</color>
<color name="splash">#00000000</color>
</resources>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="side_margin">16dp</dimen>
<dimen name="entry_min_height">80dp</dimen>
<dimen name="entry_about_min_height">48dp</dimen>
<dimen name="font_normal">14sp</dimen>
<dimen name="font_big">18sp</dimen>
<dimen name="font_header">22sp</dimen>
</resources>

View File

@ -1,71 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="url_project_page" translatable="false">https://vcmi.eu</string>
<string name="url_project_repo" translatable="false">https://github.com/vcmi/vcmi</string>
<string name="url_launcher_repo" translatable="false">https://github.com/vcmi/vcmi-android</string>
<string name="url_launcher_privacy" translatable="false">https://github.com/vcmi/vcmi/blob/master/docs/players/Privacy_Policy.md</string>
<string name="app_name">VCMI</string>
<string name="server_name">VCMI Server</string>
<string name="launcher_title">VCMI Launcher</string>
<string name="launcher_btn_scale_title">Game resolution scale</string>
<string name="launcher_btn_scale_subtitle_unknown">Currently: unknown</string>
<string name="launcher_btn_scale_subtitle">Currently: %1$d%%</string>
<string name="launcher_btn_start_title">Start VCMI</string>
<string name="launcher_btn_start_subtitle">Current VCMI version: %1$s</string>
<string name="launcher_btn_mods_title">Mods</string>
<string name="launcher_btn_mods_subtitle">Install new factions, objects, extras</string>
<string name="launcher_btn_language_title">Language</string>
<string name="launcher_btn_language_subtitle_unknown">Currently: unknown</string>
<string name="launcher_btn_language_subtitle">Currently: %1$s</string>
<string name="launcher_btn_pointermode_title">Change pointer mode</string>
<string name="launcher_btn_pointermode_subtitle">Currently: %1$s</string>
<string name="launcher_btn_pointermulti_title">Relative pointer speed multiplier</string>
<string name="launcher_btn_pointermulti_subtitle">Currently: %1$s</string>
<string name="launcher_btn_sound_title">Sound volume</string>
<string name="launcher_btn_music_title">Music volume</string>
<string name="launcher_btn_adventure_ai">Adventure AI</string>
<string name="launcher_btn_adventure_ai_title">Change adventure AI</string>
<string name="launcher_btn_import_title">Import VCMI data</string>
<string name="launcher_btn_import_description">Copy VCMI files into internal storage. You can import old vcmi-data folder from 0.99 release or HOMM3 files</string>
<string name="launcher_btn_export_title">Export VCMI data</string>
<string name="launcher_btn_export_description">Make a copy of internal VCMI data before uninstallation or to synchronize saves with desktop version. You can also access the internal storage directly.</string>
<string name="launcher_progress_copy">Copying %1$s</string>
<string name="launcher_version">Current launcher version: %1$s</string>
<string name="launcher_error_vcmi_data_root_failed">Could not create VCMI data folder in %1$s.</string>
<string name="launcher_error_h3_data_missing">Could not find data folder in \'%1$s\'. Place your HoMM3 data files there or use a button below to do this for you. You may need to restart the application also.</string>
<string name="launcher_error_vcmi_data_internal_missing">Could not find or extract vcmi data from app resources. Try to reinstall the app.</string>
<string name="launcher_error_vcmi_data_internal_update">Could not update vcmi data from app resources. Try to reinstall the app.</string>
<string name="launcher_error_permissions">This application needs write permissions to use the content stored in external storage</string>
<string name="launcher_error_permission_broken">Could not resolve permissions correctly</string>
<string name="mods_item_author_template">by %1$s</string>
<string name="misc_try_again">Try again</string>
<string name="launcher_section_init">Game init</string>
<string name="launcher_section_settings">Settings</string>
<string name="menu_mods_download_repo">Download repository data</string>
<string name="misc_pointermode_normal">Normal</string>
<string name="misc_pointermode_relative">Relative</string>
<string name="menu_launcher_about">About</string>
<string name="mods_title">Detected mods</string>
<string name="mods_failed_mod_loading">Could not load the mod in \'%1$s\' folder</string>
<string name="mods_removal_title">Removing %1$s</string>
<string name="mods_removal_confirmation">Are you sure you want to remove %1$s</string>
<string name="about_title">About application</string>
<string name="about_version_app">App version: %1$s</string>
<string name="about_version_launcher">Launcher version: %1$s</string>
<string name="about_section_project">Project</string>
<string name="about_section_legal">Legal</string>
<string name="about_links_main">Main page: %1$s</string>
<string name="about_links_repo">Project repository: %1$s</string>
<string name="about_links_repo_launcher">Launcher repository: %1$s</string>
<string name="about_btn_authors">Authors</string>
<string name="about_btn_privacy">Privacy policy: %1$s</string>
<string name="about_error_opening_url">Could not open the webpage (no suitable application found)</string>
<string name="dialog_authors_vcmi">VCMI authors</string>
<string name="dialog_authors_launcher">Launcher authors</string>
<string name="launcher_error_config_saving_failed">Could not save vcmi config; reason: %1$s</string>
</resources>

View File

@ -1,101 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Base.Theme.VCMI" parent="@style/Theme.AppCompat.NoActionBar">
<item name="colorPrimary">@color/bgMain</item>
<item name="colorPrimaryDark">@color/bgDark</item>
<item name="colorAccent">@color/accent</item>
<item name="colorControlHighlight">@color/accent</item>
<item name="android:textViewStyle">@style/VCMI.Text</item>
</style>
<style name="Theme.VCMI" parent="@style/Base.Theme.VCMI">
<item name="android:listDivider">@drawable/divider_compat</item>
</style>
<style name="Theme.VCMI.Full">
<item name="windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<style name="VCMI" />
<style name="VCMI.Text">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">@dimen/font_normal</item>
<item name="android:textColor">@color/textMain</item>
</style>
<style name="VCMI.Text.Header">
<item name="android:textSize">@dimen/font_header</item>
</style>
<style name="VCMI.Text.LauncherSection">
<item name="android:textColor">?attr/colorAccent</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">@dimen/font_normal</item>
<item name="android:paddingLeft">@dimen/side_margin</item>
<item name="android:paddingRight">@dimen/side_margin</item>
<item name="android:paddingTop">10dp</item>
<item name="android:paddingBottom">10dp</item>
</style>
<style name="VCMI.Text.LauncherEntry">
<item name="android:textSize">@dimen/font_big</item>
</style>
<style name="VCMI.Text.LauncherEntry.Sub">
<item name="android:textColor">@color/textSecondary</item>
<item name="android:textSize">@dimen/font_normal</item>
</style>
<style name="VCMI.Text.ModName">
<item name="android:textSize">@dimen/font_big</item>
</style>
<style name="VCMI.Text.ModAuthor" />
<style name="VCMI.Text.ModSize">
<item name="android:textColor">@color/textSecondary</item>
</style>
<style name="VCMI.Text.ModType">
<item name="android:textColor">@color/textSecondary</item>
</style>
<style name="VCMI.OverlayButton">
<item name="android:layout_width">36dp</item>
<item name="android:layout_height">36dp</item>
<item name="android:layout_margin">8dp</item>
<item name="android:elevation">2dp</item>
<item name="android:gravity">center</item>
</style>
<style name="VCMI.Entry">
<item name="android:minHeight">@dimen/entry_min_height</item>
<item name="android:paddingBottom">5dp</item>
<item name="android:paddingTop">5dp</item>
<item name="android:paddingLeft">@dimen/side_margin</item>
<item name="android:paddingRight">@dimen/side_margin</item>
</style>
<style name="VCMI.Entry.Clickable">
<item name="android:background">?attr/selectableItemBackground</item>
</style>
<style name="VCMI.Entry.Clickable.AboutSimpleEntry">
<item name="android:minHeight">@dimen/entry_about_min_height</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">@dimen/font_normal</item>
<item name="android:textColor">@color/textMain</item>
<item name="android:gravity">center_vertical</item>
<item name="android:paddingBottom">10dp</item>
<item name="android:paddingTop">10dp</item>
</style>
</resources>