mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-22 22:13:35 +02:00
Merge branch 'vcmi/beta' into 'vcmi/develop'
This commit is contained in:
commit
b4c6906471
51
.github/workflows/github.yml
vendored
51
.github/workflows/github.yml
vendored
@ -88,16 +88,16 @@ jobs:
|
||||
preset: windows-mingw-conan-linux
|
||||
conan_profile: mingw32-linux.jinja
|
||||
- platform: android-32
|
||||
os: ubuntu-22.04
|
||||
os: macos-14
|
||||
extension: apk
|
||||
preset: android-conan-ninja-release
|
||||
preset: android-daily-release
|
||||
conan_profile: android-32
|
||||
conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
|
||||
artifact_platform: armeabi-v7a
|
||||
- platform: android-64
|
||||
os: ubuntu-22.04
|
||||
os: macos-14
|
||||
extension: apk
|
||||
preset: android-conan-ninja-release
|
||||
preset: android-daily-release
|
||||
conan_profile: android-64
|
||||
conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
|
||||
artifact_platform: arm64-v8a
|
||||
@ -187,6 +187,12 @@ jobs:
|
||||
env:
|
||||
GENERATE_ONLY_BUILT_CONFIG: 1
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
|
||||
- name: Build Number
|
||||
run: |
|
||||
source '${{github.workspace}}/CI/get_package_name.sh'
|
||||
@ -215,7 +221,7 @@ jobs:
|
||||
run: |
|
||||
ctest --preset ${{matrix.preset}}
|
||||
|
||||
- name: Kill XProtect to work around CPack issue on macOS
|
||||
- name: Kill XProtect to work around CPack issue on macOS
|
||||
if: ${{ startsWith(matrix.platform, 'mac') }}
|
||||
run: |
|
||||
# Cf. https://github.com/actions/runner-images/issues/7522#issuecomment-1556766641
|
||||
@ -234,13 +240,6 @@ jobs:
|
||||
&& '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' '${{github.workspace}}' "$(ls '${{ env.VCMI_PACKAGE_FILE_NAME }}'.*)"
|
||||
rm -rf _CPack_Packages
|
||||
|
||||
- name: Create Android package
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
run: |
|
||||
cd android
|
||||
./gradlew assembleDaily --info
|
||||
echo ANDROID_APK_PATH="$(ls ${{ github.workspace }}/android/vcmi-app/build/outputs/apk/daily/*.${{ matrix.extension }})" >> $GITHUB_ENV
|
||||
|
||||
- name: Additional logs
|
||||
if: ${{ failure() && steps.cpack.outcome == 'failure' && matrix.platform == 'msvc' }}
|
||||
run: |
|
||||
@ -254,7 +253,14 @@ jobs:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
|
||||
path: |
|
||||
${{github.workspace}}/out/build/${{matrix.preset}}/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }}
|
||||
|
||||
|
||||
- name: Find Android package
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
run: |
|
||||
builtApkPath="$(ls ${{ github.workspace }}/out/build/${{ matrix.preset }}/android-build/vcmi-app/build/outputs/apk/release/*.${{ matrix.extension }})"
|
||||
ANDROID_APK_PATH="${{ github.workspace }}/$VCMI_PACKAGE_FILE_NAME.${{ matrix.extension }}"
|
||||
mv "$builtApkPath" "$ANDROID_APK_PATH"
|
||||
echo "ANDROID_APK_PATH=$ANDROID_APK_PATH" >> $GITHUB_ENV
|
||||
- name: Android artifacts
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
@ -262,7 +268,7 @@ jobs:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
|
||||
path: |
|
||||
${{ env.ANDROID_APK_PATH }}
|
||||
|
||||
|
||||
- name: Symbols
|
||||
if: ${{ matrix.platform == 'msvc' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
@ -283,19 +289,17 @@ jobs:
|
||||
if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' && matrix.platform != 'mingw-32' }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if cd '${{github.workspace}}/android/vcmi-app/build/outputs/apk/daily' ; then
|
||||
mv '${{ env.ANDROID_APK_PATH }}' "$VCMI_PACKAGE_FILE_NAME.${{ matrix.extension }}"
|
||||
else
|
||||
if [ -z '${{ env.ANDROID_APK_PATH }}' ] ; then
|
||||
cd '${{github.workspace}}/out/build/${{matrix.preset}}'
|
||||
fi
|
||||
source '${{github.workspace}}/CI/upload_package.sh'
|
||||
env:
|
||||
DEPLOY_RSA: ${{ secrets.DEPLOY_RSA }}
|
||||
PACKAGE_EXTENSION: ${{ matrix.extension }}
|
||||
|
||||
|
||||
# copy-pasted mostly
|
||||
bundle_release:
|
||||
|
||||
|
||||
needs: build
|
||||
if: always() && github.ref == 'refs/heads/master'
|
||||
strategy:
|
||||
@ -303,7 +307,6 @@ jobs:
|
||||
include:
|
||||
- platform: android-32
|
||||
os: ubuntu-22.04
|
||||
extension: aab
|
||||
preset: android-conan-ninja-release
|
||||
conan_profile: android-32
|
||||
conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
|
||||
@ -367,13 +370,13 @@ jobs:
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: Android JNI android-64
|
||||
path: ${{ github.workspace }}/android/vcmi-app/src/main/jniLibs/
|
||||
|
||||
path: ${{ github.workspace }}/out/build/${{ matrix.preset }}/android-build/libs
|
||||
|
||||
- name: Create Android package
|
||||
run: |
|
||||
cd android
|
||||
cd out/build/${{ matrix.preset }}/android-build
|
||||
./gradlew bundleRelease --info
|
||||
echo ANDROID_APK_PATH="$(ls ${{ github.workspace }}/android/vcmi-app/build/outputs/bundle/release/*.aab)" >> $GITHUB_ENV
|
||||
echo ANDROID_APK_PATH="$(ls ${{ github.workspace }}/out/build/${{ matrix.preset }}/android-build/vcmi-app/build/outputs/bundle/release/*.aab)" >> $GITHUB_ENV
|
||||
env:
|
||||
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
|
@ -403,7 +403,7 @@ void Nullkiller::makeTurn()
|
||||
|
||||
if(selectedTasks.empty())
|
||||
{
|
||||
return;
|
||||
selectedTasks.push_back(taskptr(Goals::Invalid()));
|
||||
}
|
||||
|
||||
bool hasAnySuccess = false;
|
||||
|
@ -26,9 +26,9 @@ bool ExploreNeighbourTile::operator==(const ExploreNeighbourTile & other) const
|
||||
|
||||
void ExploreNeighbourTile::accept(AIGateway * ai)
|
||||
{
|
||||
ExplorationHelper h(hero, ai->nullkiller.get());
|
||||
ExplorationHelper h(hero, ai->nullkiller.get(), true);
|
||||
|
||||
for(int i = 0; i < tilesToExplore && hero->movementPointsRemaining() > 0; i++)
|
||||
for(int i = 0; i < tilesToExplore && ai->myCb->getObj(hero->id, false) && hero->movementPointsRemaining() > 0; i++)
|
||||
{
|
||||
int3 pos = hero->visitablePos();
|
||||
float value = 0;
|
||||
@ -54,7 +54,14 @@ void ExploreNeighbourTile::accept(AIGateway * ai)
|
||||
}
|
||||
});
|
||||
|
||||
if(!target.valid() || !ai->moveHeroToTile(target, hero))
|
||||
if(!target.valid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto danger = ai->nullkiller->pathfinder->getStorage()->evaluateDanger(target, hero, true);
|
||||
|
||||
if(danger > 0 || !ai->moveHeroToTile(target, hero))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -24,8 +24,8 @@ namespace NKAI
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
ExplorationHelper::ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai)
|
||||
:ai(ai), cbp(ai->cb.get()), hero(hero)
|
||||
ExplorationHelper::ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility)
|
||||
:ai(ai), cbp(ai->cb.get()), hero(hero), useCPathfinderAccessibility(useCPathfinderAccessibility)
|
||||
{
|
||||
ts = cbp->getPlayerTeam(ai->playerID);
|
||||
sightRadius = hero->getSightRadius();
|
||||
@ -104,7 +104,7 @@ bool ExplorationHelper::scanMap()
|
||||
|
||||
if(!bestGoal->invalid())
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
allowDeadEndCancellation = false;
|
||||
@ -222,7 +222,9 @@ bool ExplorationHelper::hasReachableNeighbor(const int3 & pos) const
|
||||
int3 tile = pos + dir;
|
||||
if(cbp->isInTheMap(tile))
|
||||
{
|
||||
auto isAccessible = ai->pathfinder->isTileAccessible(hero, tile);
|
||||
auto isAccessible = useCPathfinderAccessibility
|
||||
? ai->cb->getPathsInfo(hero)->getPathInfo(tile)->reachable()
|
||||
: ai->pathfinder->isTileAccessible(hero, tile);
|
||||
|
||||
if(isAccessible)
|
||||
return true;
|
||||
|
@ -34,9 +34,10 @@ private:
|
||||
const TeamState * ts;
|
||||
int3 ourPos;
|
||||
bool allowDeadEndCancellation;
|
||||
bool useCPathfinderAccessibility;
|
||||
|
||||
public:
|
||||
ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai);
|
||||
ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility = false);
|
||||
Goals::TSubgoal makeComposition() const;
|
||||
bool scanSector(int scanRadius);
|
||||
bool scanMap();
|
||||
|
@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install ninja-build
|
||||
echo "ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/25.2.9519653" >> $GITHUB_ENV
|
||||
|
||||
brew install ninja
|
||||
|
||||
mkdir ~/.conan ; cd ~/.conan
|
||||
curl -L "https://github.com/vcmi/vcmi-dependencies/releases/download/android-1.0/$DEPS_FILENAME.txz" \
|
||||
curl -L "https://github.com/vcmi/vcmi-dependencies/releases/download/android-1.1/$DEPS_FILENAME.txz" \
|
||||
| tar -xf - --xz
|
||||
|
4
CI/conan/android-32-ndk
Normal file
4
CI/conan/android-32-ndk
Normal file
@ -0,0 +1,4 @@
|
||||
include(android-32)
|
||||
|
||||
[tool_requires]
|
||||
android-ndk/r25c
|
4
CI/conan/android-64-ndk
Normal file
4
CI/conan/android-64-ndk
Normal file
@ -0,0 +1,4 @@
|
||||
include(android-64)
|
||||
|
||||
[tool_requires]
|
||||
android-ndk/r25c
|
@ -58,13 +58,22 @@ option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" O
|
||||
# Platform-specific options
|
||||
|
||||
if(ANDROID)
|
||||
set(ANDROID_TARGET_SDK_VERSION "33" CACHE STRING "Android target SDK version")
|
||||
set(ANDROIDDEPLOYQT_OPTIONS "" CACHE STRING "Additional androiddeployqt options separated by semi-colon")
|
||||
set(ANDROID_GRADLE_PROPERTIES "" CACHE STRING "Additional Gradle properties separated by semi-colon")
|
||||
|
||||
set(ENABLE_STATIC_LIBS ON)
|
||||
set(ENABLE_LAUNCHER OFF)
|
||||
set(ENABLE_LAUNCHER ON)
|
||||
else()
|
||||
option(ENABLE_STATIC_LIBS "Build library and all components such as AI statically" OFF)
|
||||
option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
|
||||
endif()
|
||||
|
||||
if(APPLE_IOS)
|
||||
set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix")
|
||||
set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen")
|
||||
endif()
|
||||
|
||||
if(APPLE_IOS OR ANDROID)
|
||||
set(ENABLE_MONOLITHIC_INSTALL OFF)
|
||||
set(ENABLE_SINGLE_APP_BUILD ON)
|
||||
@ -100,11 +109,6 @@ if (ENABLE_STATIC_LIBS AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
if(APPLE_IOS)
|
||||
set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix")
|
||||
set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen")
|
||||
endif()
|
||||
|
||||
if(ENABLE_COLORIZED_COMPILER_OUTPUT)
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
add_compile_options(-fcolor-diagnostics)
|
||||
@ -147,10 +151,6 @@ set(CMAKE_MODULE_PATH ${CMAKE_HOME_DIRECTORY}/cmake_modules ${PROJECT_SOURCE_DIR
|
||||
|
||||
include(VCMIUtils)
|
||||
include(VersionDefinition)
|
||||
if(ANDROID)
|
||||
set(VCMI_VERSION "${APP_SHORT_VERSION}")
|
||||
configure_file("android/GeneratedVersion.java.in" "${CMAKE_SOURCE_DIR}/android/vcmi-app/src/main/java/eu/vcmi/vcmi/util/GeneratedVersion.java" @ONLY)
|
||||
endif()
|
||||
|
||||
vcmi_print_important_variables()
|
||||
|
||||
@ -575,8 +575,12 @@ elseif(APPLE)
|
||||
endif()
|
||||
elseif(ANDROID)
|
||||
include(GNUInstallDirs)
|
||||
set(LIB_DIR "jniLibs/${ANDROID_ABI}")
|
||||
set(DATA_DIR "assets")
|
||||
set(LIB_DIR "libs/${ANDROID_ABI}")
|
||||
|
||||
# required by Qt
|
||||
set(androidPackageSourceDir "${CMAKE_SOURCE_DIR}/android")
|
||||
set(androidQtBuildDir "${CMAKE_BINARY_DIR}/android-build")
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${androidQtBuildDir}/${LIB_DIR}")
|
||||
else()
|
||||
# includes lib path which determines where to install shared libraries (either /lib or /lib64)
|
||||
include(GNUInstallDirs)
|
||||
@ -621,6 +625,13 @@ else()
|
||||
set(SCRIPTING_LIB_DIR "${LIB_DIR}/scripting")
|
||||
endif()
|
||||
|
||||
# common Qt paths
|
||||
if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
|
||||
get_target_property(qmakePath Qt${QT_VERSION_MAJOR}::qmake IMPORTED_LOCATION)
|
||||
get_filename_component(qtDir "${qmakePath}/../../" ABSOLUTE)
|
||||
set(qtBinDir "${qtDir}/bin")
|
||||
endif()
|
||||
|
||||
#######################################
|
||||
# Add subdirectories #
|
||||
#######################################
|
||||
@ -682,32 +693,15 @@ endif()
|
||||
#######################################
|
||||
|
||||
if(ANDROID)
|
||||
string(REPLACE ";" "\n" ANDROID_GRADLE_PROPERTIES_MULTILINE "${ANDROID_GRADLE_PROPERTIES}")
|
||||
file(WRITE "${androidPackageSourceDir}/vcmi-app/gradle.properties" "signingRoot=${CMAKE_SOURCE_DIR}/CI/android\n${ANDROID_GRADLE_PROPERTIES_MULTILINE}")
|
||||
|
||||
if(ANDROID_STL MATCHES "_shared$")
|
||||
set(stlLibName "${CMAKE_SHARED_LIBRARY_PREFIX}${ANDROID_STL}${CMAKE_SHARED_LIBRARY_SUFFIX}")
|
||||
install(FILES "${CMAKE_SYSROOT}/usr/lib/${ANDROID_SYSROOT_LIB_SUBDIR}/${stlLibName}"
|
||||
DESTINATION ${LIB_DIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
# zip internal assets - 'config' and 'Mods' dirs, save md5 of the zip
|
||||
install(CODE "
|
||||
cmake_path(ABSOLUTE_PATH CMAKE_INSTALL_PREFIX
|
||||
OUTPUT_VARIABLE absolute_install_prefix
|
||||
)
|
||||
set(absolute_data_dir \"\${absolute_install_prefix}/${DATA_DIR}\")
|
||||
file(MAKE_DIRECTORY \"\${absolute_data_dir}\")
|
||||
|
||||
set(internal_data_zip \"\${absolute_data_dir}/internalData.zip\")
|
||||
execute_process(COMMAND
|
||||
\"${CMAKE_COMMAND}\" -E tar c \"\${internal_data_zip}\" --format=zip -- config Mods
|
||||
WORKING_DIRECTORY \"${CMAKE_SOURCE_DIR}\"
|
||||
)
|
||||
|
||||
file(MD5 \"\${internal_data_zip}\" internal_data_zip_md5)
|
||||
file(WRITE \"\${absolute_data_dir}/internalDataHash.txt\"
|
||||
\${internal_data_zip_md5}
|
||||
)
|
||||
")
|
||||
else()
|
||||
install(DIRECTORY config DESTINATION ${DATA_DIR})
|
||||
if (ENABLE_CLIENT OR ENABLE_SERVER)
|
||||
|
@ -52,7 +52,7 @@
|
||||
"hidden": true,
|
||||
"cacheVariables": {
|
||||
"ENABLE_LOBBY": "ON",
|
||||
"ENABLE_TEST": "ON",
|
||||
"ENABLE_TEST": "ON",
|
||||
"ENABLE_LUA": "ON"
|
||||
}
|
||||
},
|
||||
@ -294,6 +294,15 @@
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-daily-release",
|
||||
"displayName": "Android daily release",
|
||||
"description": "VCMI Android daily build",
|
||||
"inherits": "android-conan-ninja-release",
|
||||
"cacheVariables": {
|
||||
"ANDROID_GRADLE_PROPERTIES": "applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
@ -412,6 +421,11 @@
|
||||
"name": "android-conan-ninja-release",
|
||||
"configurePreset": "android-conan-ninja-release",
|
||||
"inherits": "default-release"
|
||||
},
|
||||
{
|
||||
"name": "android-daily-release",
|
||||
"configurePreset": "android-daily-release",
|
||||
"inherits": "android-conan-ninja-release"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
|
4
android/.gitignore
vendored
4
android/.gitignore
vendored
@ -1,9 +1,11 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
|
||||
# generated by CMake build
|
||||
/vcmi-app/gradle.properties
|
||||
|
98
android/AndroidManifest.xml
Normal file
98
android/AndroidManifest.xml
Normal 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>
|
@ -1,9 +0,0 @@
|
||||
package eu.vcmi.vcmi.util;
|
||||
|
||||
/**
|
||||
* Generated via cmake
|
||||
*/
|
||||
public class GeneratedVersion
|
||||
{
|
||||
public static final String VCMI_VERSION = "@VCMI_VERSION@";
|
||||
}
|
17
android/androiddeployqt.json.in
Normal file
17
android/androiddeployqt.json.in
Normal 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"
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
ext {
|
||||
// these values will be retrieved during gradle build
|
||||
gitInfoVcmi = "none"
|
||||
}
|
||||
|
@ -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
|
||||
|
8
android/vcmi-app/.gitignore
vendored
8
android/vcmi-app/.gitignore
vendored
@ -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
|
@ -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 1600
|
||||
versionName "1.6.0"
|
||||
|
||||
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,7 @@ 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'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
}
|
||||
|
@ -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>
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,307 +1,62 @@
|
||||
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 android.os.Environment;
|
||||
import android.provider.DocumentsContract;
|
||||
|
||||
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 androidx.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import eu.vcmi.vcmi.VcmiSDLActivity;
|
||||
import eu.vcmi.vcmi.util.FileUtil;
|
||||
import eu.vcmi.vcmi.util.Log;
|
||||
import eu.vcmi.vcmi.util.SharedPrefs;
|
||||
|
||||
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 static final int PICK_EXTERNAL_VCMI_DATA_TO_COPY = 1;
|
||||
|
||||
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);
|
||||
justLaunched = savedInstanceState == null;
|
||||
SDL.setContext(this);
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) // only clear the log if this is initial onCreate and not config change
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent resultData)
|
||||
{
|
||||
if (requestCode == PICK_EXTERNAL_VCMI_DATA_TO_COPY && resultCode == Activity.RESULT_OK)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
if (resultData != null && FileUtil.copyData(resultData.getData(), this))
|
||||
NativeMethods.heroesDataUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, resultData);
|
||||
}
|
||||
|
||||
public void requestStoragePermissions()
|
||||
public void copyHeroesData()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
{
|
||||
requestPermissions(
|
||||
new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
PERMISSIONS_REQ_CODE);
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,
|
||||
Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "vcmi-data"))
|
||||
);
|
||||
startActivityForResult(intent, PICK_EXTERNAL_VCMI_DATA_TO_COPY);
|
||||
}
|
||||
|
||||
private void initSettingsGui()
|
||||
public void onLaunchGameBtnPressed()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
@ -17,8 +8,6 @@ import java.io.OutputStreamWriter;
|
||||
public class Const
|
||||
{
|
||||
public static final String JNI_METHOD_SUPPRESS = "unused"; // jni methods are marked as unused, because IDE doesn't understand jni calls
|
||||
// used to disable lint errors about try-with-resources being unsupported on api <19 (it is supported, because retrolambda backports it)
|
||||
public static final int SUPPRESS_TRY_WITH_RESOURCES_WARNING = Build.VERSION_CODES.KITKAT;
|
||||
|
||||
public static final String VCMI_DATA_ROOT_FOLDER_NAME = "vcmi-data";
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -35,7 +26,7 @@ public class NativeMethods
|
||||
}
|
||||
|
||||
public static native void initClassloader();
|
||||
|
||||
public static native void heroesDataUpdate();
|
||||
public static native boolean tryToSaveTheGame();
|
||||
|
||||
public static void setupMsg(final Messenger msg)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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, "");
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 + "%";
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -1,177 +1,101 @@
|
||||
package eu.vcmi.vcmi.util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
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 java.util.List;
|
||||
|
||||
import eu.vcmi.vcmi.Const;
|
||||
import eu.vcmi.vcmi.Storage;
|
||||
|
||||
/**
|
||||
* @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
|
||||
public static boolean copyData(Uri folderToCopy, Activity activity)
|
||||
{
|
||||
try (InputStreamReader reader = new InputStreamReader(stream))
|
||||
{
|
||||
return readInternal(reader);
|
||||
}
|
||||
File targetDir = Storage.getVcmiDataDir(activity);
|
||||
DocumentFile sourceDir = DocumentFile.fromTreeUri(activity, folderToCopy);
|
||||
return copyDirectory(targetDir, sourceDir, List.of("Data", "Maps", "Mp3"), activity);
|
||||
}
|
||||
|
||||
public static String read(final File file) throws IOException
|
||||
private static boolean copyDirectory(File targetDir, DocumentFile sourceDir, @Nullable List<String> allowed, Activity activity)
|
||||
{
|
||||
try (FileReader reader = new FileReader(file))
|
||||
if (!targetDir.exists())
|
||||
{
|
||||
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;
|
||||
targetDir.mkdir();
|
||||
}
|
||||
|
||||
final File dir = file.getParentFile();
|
||||
|
||||
if (dir.exists() || dir.mkdirs())
|
||||
for (DocumentFile child : sourceDir.listFiles())
|
||||
{
|
||||
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))
|
||||
if (allowed != null)
|
||||
{
|
||||
return false;
|
||||
boolean fileAllowed = false;
|
||||
|
||||
for (String str : allowed)
|
||||
{
|
||||
if (str.equalsIgnoreCase(child.getName()))
|
||||
{
|
||||
fileAllowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileAllowed)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!f.delete())
|
||||
File exported = new File(targetDir, child.getName());
|
||||
|
||||
if (child.isFile())
|
||||
{
|
||||
if (!exported.exists())
|
||||
{
|
||||
try
|
||||
{
|
||||
exported.createNewFile();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.e(activity, "createNewFile failed: " + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try (
|
||||
final OutputStream targetStream = new FileOutputStream(exported, false);
|
||||
final InputStream sourceStream = activity.getContentResolver()
|
||||
.openInputStream(child.getUri()))
|
||||
{
|
||||
copyStream(sourceStream, targetStream);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.e(activity, "copyStream failed: " + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (child.isDirectory() && !copyDirectory(exported, child, null, activity))
|
||||
{
|
||||
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
|
||||
private static void copyStream(InputStream source, OutputStream target) throws IOException
|
||||
{
|
||||
final byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int read;
|
||||
@ -180,171 +104,4 @@ public class FileUtil
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
package eu.vcmi.vcmi.util;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface IZipProgressReporter
|
||||
{
|
||||
void onUnpacked(File newFile);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -61,7 +61,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
private static final String TAG = "SDL";
|
||||
private static final int SDL_MAJOR_VERSION = 2;
|
||||
private static final int SDL_MINOR_VERSION = 26;
|
||||
private static final int SDL_MICRO_VERSION = 1;
|
||||
private static final int SDL_MICRO_VERSION = 5;
|
||||
/*
|
||||
// Display InputType.SOURCE/CLASS of events and devices
|
||||
//
|
||||
@ -241,7 +241,14 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
* It can be overridden by derived classes.
|
||||
*/
|
||||
protected String getMainSharedObject() {
|
||||
return null;
|
||||
String library;
|
||||
String[] libraries = SDLActivity.mSingleton.getLibraries();
|
||||
if (libraries.length > 0) {
|
||||
library = "lib" + libraries[libraries.length - 1] + ".so";
|
||||
} else {
|
||||
library = "libmain.so";
|
||||
}
|
||||
return getContext().getApplicationInfo().nativeLibraryDir + "/" + library;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,6 +168,32 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
arg1Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
|
||||
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
|
||||
// This is because the usual pairing are:
|
||||
// - AXIS_X + AXIS_Y (left stick).
|
||||
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
|
||||
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
|
||||
// This sorts the axes in the above order, which tends to be correct
|
||||
// for Xbox-ish game pads that have the right stick on RX/RY and the
|
||||
// triggers on Z/RZ.
|
||||
//
|
||||
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
|
||||
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
|
||||
//
|
||||
// References:
|
||||
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
|
||||
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
|
||||
if (arg0Axis == MotionEvent.AXIS_Z) {
|
||||
arg0Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg0Axis;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_Z) {
|
||||
arg1Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg1Axis;
|
||||
}
|
||||
|
||||
return arg0Axis - arg1Axis;
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 156 B |
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -16,4 +16,4 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user