mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 11:35:05 +08:00
Compare commits
20 Commits
ci-test
...
continuous
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa02f54536 | ||
|
|
ac162b33e9 | ||
|
|
54091b0875 | ||
|
|
0b65f87bb1 | ||
|
|
2553f376ae | ||
|
|
4e6babbd4d | ||
|
|
f7db736340 | ||
|
|
d038162903 | ||
|
|
99f33fb406 | ||
|
|
27c851f071 | ||
|
|
09f3998cda | ||
|
|
4c159015e4 | ||
|
|
35b63ca647 | ||
|
|
4a4086e27c | ||
|
|
b3c2e5568d | ||
|
|
232a3163ff | ||
|
|
aacdde1522 | ||
|
|
54d737fe3e | ||
|
|
b43105f13d | ||
|
|
6661ec3090 |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -222,7 +222,7 @@ publish/
|
|||||||
# Publish Web Output
|
# Publish Web Output
|
||||||
*.[Pp]ublish.xml
|
*.[Pp]ublish.xml
|
||||||
*.azurePubxml
|
*.azurePubxml
|
||||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
*.pubxml
|
*.pubxml
|
||||||
*.publishproj
|
*.publishproj
|
||||||
@@ -345,3 +345,12 @@ enc_temp_folder/
|
|||||||
|
|
||||||
# ccls langauge server
|
# ccls langauge server
|
||||||
.ccls-*
|
.ccls-*
|
||||||
|
|
||||||
|
# JetBrains
|
||||||
|
.idea/
|
||||||
|
cmake-build-*
|
||||||
|
|
||||||
|
# some Android-specific build stuff
|
||||||
|
3rdparty/SDL
|
||||||
|
3rdparty/hlsdk-portable
|
||||||
|
!scripts/build-ninja.py
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -36,6 +36,11 @@ You still needed to copy `valve` directory as all game resources located there.
|
|||||||
|
|
||||||
For additional info, run Xash3D with `-help` command line key.
|
For additional info, run Xash3D with `-help` command line key.
|
||||||
|
|
||||||
|
### Android
|
||||||
|
0) Install the APK file.
|
||||||
|
1) Copy `valve` directory to a folder named `xash` in the Internal storage.
|
||||||
|
2) Run games from within the app.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
* Before sending an issue, check if someone already reported your issue. Make sure you're following "How To Ask Questions The Smart Way" guide by Eric Steven Raymond. Read more: http://www.catb.org/~esr/faqs/smart-questions.html.
|
* Before sending an issue, check if someone already reported your issue. Make sure you're following "How To Ask Questions The Smart Way" guide by Eric Steven Raymond. Read more: http://www.catb.org/~esr/faqs/smart-questions.html.
|
||||||
* Issues are accepted in both English and Russian.
|
* Issues are accepted in both English and Russian.
|
||||||
@@ -84,6 +89,15 @@ This repository contains our fork of HLSDK and restored source code for Half-Lif
|
|||||||
|
|
||||||
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
||||||
|
|
||||||
|
#### Android (Windows/Linux/macOS)
|
||||||
|
* Install [Android Studio](https://developer.android.com/studio) (or the command line tools).
|
||||||
|
* Install [Python](https://python.org) (at least 2.7, latest is better).
|
||||||
|
* Install [Git](https://git-scm.com/download/win).
|
||||||
|
* Install [Ninja](https://ninja-build.org/).
|
||||||
|
* Install [CMake](https://cmake.org/) (for some dependencies).
|
||||||
|
|
||||||
|
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
#### Windows (Visual Studio)
|
#### Windows (Visual Studio)
|
||||||
0) Open command line.
|
0) Open command line.
|
||||||
@@ -100,3 +114,6 @@ If compiling 32-bit on amd64, make sure `PKG_CONFIG_PATH` from the previous step
|
|||||||
1) Configure build: `./waf configure` (you need to pass `-8` to compile 64-bit engine on 64-bit x86 processor).
|
1) Configure build: `./waf configure` (you need to pass `-8` to compile 64-bit engine on 64-bit x86 processor).
|
||||||
2) Compile: `./waf build`.
|
2) Compile: `./waf build`.
|
||||||
3) Install: `./waf install --destdir=/path/to/any/output/directory`.
|
3) Install: `./waf install --destdir=/path/to/any/output/directory`.
|
||||||
|
|
||||||
|
#### Android (Windows/Linux/macOS)
|
||||||
|
You can just open the `android` folder in Android Studio and build from here, or use `gradlew` to build from command line.
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.22)
|
|
||||||
|
|
||||||
# Only used to build Android project
|
|
||||||
|
|
||||||
project(XASH_ANDROID)
|
|
||||||
|
|
||||||
# armeabi-v7a requires cpufeatures library
|
|
||||||
if(ANDROID)
|
|
||||||
include_directories(${ANDROID_NDK}/sources/android/cpufeatures)
|
|
||||||
add_library(cpufeatures ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
|
|
||||||
target_link_libraries(cpufeatures dl)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include(FindPython)
|
|
||||||
|
|
||||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
|
||||||
set(BUILD_TYPE "debug")
|
|
||||||
else()
|
|
||||||
set(BUILD_TYPE "release")
|
|
||||||
list(APPEND WAF_EXTRA_ARGS --enable-poly-opt --enable-lto --enable-limited-debuginfo)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(ANDROID_ABI STREQUAL "x86")
|
|
||||||
# HACKHACK: I don't know why but engine gets built as 64-bit binary here
|
|
||||||
list(APPEND WAF_EXTRA_ARGS -4)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
||||||
|
|
||||||
|
|
||||||
# not cleanest way to get upper directory
|
|
||||||
set(ENGINE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../")
|
|
||||||
|
|
||||||
set(WAF_CC "${CMAKE_C_COMPILER} --target=${CMAKE_C_COMPILER_TARGET}")
|
|
||||||
set(WAF_CXX "${CMAKE_CXX_COMPILER} --target=${CMAKE_CXX_COMPILER_TARGET}")
|
|
||||||
set(WAF ${Python_EXECUTABLE} ${ENGINE_SOURCE_DIR}waf -t ${ENGINE_SOURCE_DIR} -o ${CMAKE_CURRENT_BINARY_DIR}/xash3d-fwgs)
|
|
||||||
|
|
||||||
# try to build minimal SDL. Enable features as we're gonna use them
|
|
||||||
set(SDL_RENDER OFF)
|
|
||||||
set(SDL_POWER OFF)
|
|
||||||
set(SDL_VULKAN OFF)
|
|
||||||
set(SDL_DISKAUDIO OFF)
|
|
||||||
set(SDL_DUMMYAUDIO OFF)
|
|
||||||
set(SDL_DUMMYVIDEO OFF)
|
|
||||||
set(SDL_VULKAN OFF)
|
|
||||||
set(SDL_OFFSCREEN OFF)
|
|
||||||
set(SDL_STATIC OFF)
|
|
||||||
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/SDL" SDL)
|
|
||||||
|
|
||||||
include(ExternalProject)
|
|
||||||
|
|
||||||
# gradle passes backslashes to cmake, how does this even work for everybody else?
|
|
||||||
string(REPLACE "\\" "/" CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
|
|
||||||
|
|
||||||
ExternalProject_Add(
|
|
||||||
Xash3DFWGS
|
|
||||||
SOURCE_DIR ${ENGINE_SOURCE_DIR}
|
|
||||||
INSTALL_DIR ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
|
||||||
BUILD_IN_SOURCE TRUE
|
|
||||||
DEPENDS SDL2
|
|
||||||
BUILD_ALWAYS TRUE
|
|
||||||
LOG_CONFIGURE TRUE
|
|
||||||
LOG_BUILD TRUE
|
|
||||||
LOG_INSTALL TRUE
|
|
||||||
LOG_OUTPUT_ON_FAILURE TRUE
|
|
||||||
LOG_MERGED_STDOUTERR TRUE
|
|
||||||
|
|
||||||
# USES_TERMINAL_CONFIGURE TRUE
|
|
||||||
# USES_TERMINAL_BUILD TRUE
|
|
||||||
# USES_TERMINAL_INSTALL TRUE
|
|
||||||
|
|
||||||
# NOTE: setting up WAFLOCK is important to avoid possible race conditions
|
|
||||||
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env
|
|
||||||
ANDROID_NDK=${ANDROID_NDK}
|
|
||||||
BUILD_CMAKE_LIBRARY_OUTPUT_DIRECTORY=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
|
||||||
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
|
|
||||||
${WAF} configure -T ${BUILD_TYPE} --android=${ANDROID_ABI},,${CMAKE_SYSTEM_VERSION}
|
|
||||||
-s "${ENGINE_SOURCE_DIR}/3rdparty/SDL" --enable-bundled-deps ${WAF_EXTRA_ARGS}
|
|
||||||
|
|
||||||
BUILD_COMMAND ${CMAKE_COMMAND} -E env
|
|
||||||
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
|
|
||||||
${WAF} build -v
|
|
||||||
|
|
||||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E env
|
|
||||||
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
|
|
||||||
${WAF} install --destdir=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
|
||||||
)
|
|
||||||
|
|
||||||
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/hlsdk-portable" hlsdk-portable)
|
|
||||||
|
|
||||||
# a1ba: without this, xash3d target will be ignored as nothing depends on it
|
|
||||||
add_dependencies(client Xash3DFWGS)
|
|
||||||
add_dependencies(server Xash3DFWGS)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,130 +1,137 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.Month
|
import java.time.Month
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
alias(libs.plugins.android.application)
|
||||||
id("org.jetbrains.kotlin.android")
|
alias(libs.plugins.kotlin.android)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "su.xash.engine"
|
namespace = "su.xash.engine"
|
||||||
ndkVersion = "28.0.13004108"
|
ndkVersion = "28.2.13676358"
|
||||||
|
compileSdk = 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "su.xash"
|
applicationId = "su.xash.engine"
|
||||||
applicationIdSuffix = "engine"
|
versionName = "0.21-" + getGitHash()
|
||||||
versionName = "0.21"
|
versionCode = getBuildNum()
|
||||||
versionCode = getBuildNum()
|
minSdk = 21
|
||||||
minSdk = 21
|
targetSdk = 35
|
||||||
targetSdk = 34
|
|
||||||
compileSdk = 34
|
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
val engineRoot = projectDir.parentFile.parent
|
||||||
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
|
||||||
arguments("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
externalNativeBuild {
|
experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a")
|
||||||
cmake {
|
experimentalProperties["ninja.path"] = File(engineRoot, "wscript").path
|
||||||
version = "3.22.1"
|
experimentalProperties["ninja.configure"] = "run-python"
|
||||||
path = file("CMakeLists.txt")
|
experimentalProperties["ninja.arguments"] = setOf(
|
||||||
}
|
File(engineRoot, "scripts/configure-ninja.py").path,
|
||||||
}
|
engineRoot,
|
||||||
|
"--variant=\${ndk.variantName}",
|
||||||
|
"--abi=\${ndk.abi}",
|
||||||
|
"--configuration-dir=\${ndk.buildRoot}",
|
||||||
|
"--ndk-version=\${ndk.moduleNdkVersion}",
|
||||||
|
"--min-sdk-version=\${ndk.minPlatform}",
|
||||||
|
"--ndk-root=${android.ndkDirectory}",
|
||||||
|
// shut up, fake options
|
||||||
|
"-p:Configuration=\${ndk.variantName}",
|
||||||
|
"-p:Platform=\${ndk.abi}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlin {
|
||||||
jvmTarget = "1.8"
|
compilerOptions {
|
||||||
}
|
jvmTarget = JvmTarget.JVM_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildFeatures {
|
||||||
debug {
|
viewBinding = true
|
||||||
isMinifyEnabled = false
|
buildConfig = true
|
||||||
isShrinkResources = false
|
}
|
||||||
isDebuggable = true
|
|
||||||
applicationIdSuffix = ".test"
|
|
||||||
proguardFiles(
|
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
release {
|
lint {
|
||||||
isMinifyEnabled = true
|
abortOnError = false
|
||||||
isShrinkResources = true
|
}
|
||||||
proguardFiles(
|
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
register("asan") {
|
androidResources {
|
||||||
initWith(getByName("debug"))
|
noCompress += ""
|
||||||
}
|
}
|
||||||
|
|
||||||
register("continuous") {
|
packaging {
|
||||||
initWith(getByName("release"))
|
jniLibs {
|
||||||
applicationIdSuffix = ".test"
|
keepDebugSymbols.add("**/*.so")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
getByName("main") {
|
getByName("main") {
|
||||||
assets.srcDirs("../../3rdparty/extras/xash-extras", "../moddb")
|
assets.srcDirs("../../3rdparty/extras/xash-extras")
|
||||||
java.srcDir("../../3rdparty/SDL/android-project/app/src/main/java")
|
java.srcDir("../../3rdparty/SDL/android-project/app/src/main/java")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lint {
|
buildTypes {
|
||||||
abortOnError = false
|
debug {
|
||||||
}
|
isMinifyEnabled = false
|
||||||
|
isShrinkResources = false
|
||||||
|
isDebuggable = true
|
||||||
|
applicationIdSuffix = ".test"
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
buildFeatures {
|
release {
|
||||||
viewBinding = true
|
isMinifyEnabled = true
|
||||||
buildConfig = true
|
isShrinkResources = true
|
||||||
}
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
androidResources {
|
register("asan") {
|
||||||
noCompress += ""
|
initWith(getByName("debug"))
|
||||||
}
|
}
|
||||||
|
|
||||||
packaging {
|
register("continuous") {
|
||||||
jniLibs {
|
initWith(getByName("release"))
|
||||||
useLegacyPackaging = true
|
applicationIdSuffix = ".test"
|
||||||
keepDebugSymbols.add("**/*.so")
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.google.android.material:material:1.11.0")
|
implementation(libs.material)
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
|
||||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
|
|
||||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
|
|
||||||
implementation("androidx.cardview:cardview:1.0.0")
|
|
||||||
implementation("androidx.annotation:annotation:1.7.1")
|
|
||||||
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
|
||||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
|
||||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
|
||||||
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
|
||||||
// implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
|
||||||
|
|
||||||
implementation("com.madgag.spongycastle:prov:1.58.0.0")
|
implementation(libs.appcompat)
|
||||||
implementation("in.dragonbra:javasteam:1.2.0")
|
implementation(libs.navigation.runtime.ktx)
|
||||||
|
implementation(libs.navigation.fragment.ktx)
|
||||||
|
implementation(libs.navigation.ui.ktx)
|
||||||
|
implementation(libs.preference.ktx)
|
||||||
|
implementation(libs.swiperefreshlayout)
|
||||||
|
|
||||||
implementation("ch.acra:acra-http:5.11.2")
|
implementation(libs.acra.http)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBuildNum(): Int {
|
fun getBuildNum(): Int {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0)
|
val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0)
|
||||||
val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS)
|
val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS)
|
||||||
val minuteOfDay = now.hour * 60 + now.minute
|
val minuteOfDay = now.hour * 60 + now.minute
|
||||||
return (qBuildNum * 10000 + minuteOfDay).toInt()
|
return (qBuildNum * 10000 + minuteOfDay).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGitHash(): String {
|
||||||
|
val process = ProcessBuilder("git", "rev-parse", "--short", "HEAD").directory(project.rootDir)
|
||||||
|
.redirectErrorStream(true).start()
|
||||||
|
return process.inputStream.bufferedReader().readText().trim()
|
||||||
}
|
}
|
||||||
|
|||||||
27
android/app/proguard-rules.pro
vendored
27
android/app/proguard-rules.pro
vendored
@@ -1,3 +1,25 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
-keep class su.xash.engine.XashActivity {
|
-keep class su.xash.engine.XashActivity {
|
||||||
java.lang.String loadAndroidID();
|
java.lang.String loadAndroidID();
|
||||||
java.lang.String getAndroidID();
|
java.lang.String getAndroidID();
|
||||||
@@ -87,3 +109,8 @@
|
|||||||
void hapticRun(int, float, int);
|
void hapticRun(int, float, int);
|
||||||
void hapticStop(int);
|
void hapticStop(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Unexpected reference to missing service class: META-INF/services/javax.annotation.processing.Processor.
|
||||||
|
-dontwarn javax.annotation.processing.Processor
|
||||||
|
-dontwarn javax.annotation.processing.AbstractProcessor
|
||||||
|
-dontwarn javax.annotation.processing.SupportedOptions
|
||||||
|
|||||||
3
android/app/run-python
Executable file
3
android/app/run-python
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
exec python $@
|
||||||
1
android/app/run-python.bat
Normal file
1
android/app/run-python.bat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python %*
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
|
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
|
||||||
<string name="authority" translatable="false">su.xash.engine.test.documents</string>
|
</resources>
|
||||||
</resources>
|
|
||||||
|
|||||||
@@ -1,101 +1,83 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
android:allowAudioPlaybackCapture="true"
|
||||||
android:allowAudioPlaybackCapture="true"
|
android:installLocation="preferExternal">
|
||||||
android:installLocation="preferExternal"
|
<!-- OpenGL ES 1.1 -->
|
||||||
tools:targetApi="q">
|
<uses-feature android:glEsVersion="0x00010000" />
|
||||||
<!-- OpenGL ES 1.1 -->
|
<!-- Touchscreen support -->
|
||||||
<uses-feature android:glEsVersion="0x00010000" />
|
<uses-feature
|
||||||
<!-- Touchscreen support -->
|
android:name="android.hardware.touchscreen"
|
||||||
<uses-feature
|
android:required="false" />
|
||||||
android:name="android.hardware.touchscreen"
|
<!-- Game controller support -->
|
||||||
android:required="false" />
|
<uses-feature
|
||||||
<!-- Game controller support -->
|
android:name="android.hardware.bluetooth"
|
||||||
<uses-feature
|
android:required="false" />
|
||||||
android:name="android.hardware.bluetooth"
|
<uses-feature
|
||||||
android:required="false" />
|
android:name="android.hardware.gamepad"
|
||||||
<uses-feature
|
android:required="false" />
|
||||||
android:name="android.hardware.gamepad"
|
<uses-feature
|
||||||
android:required="false" />
|
android:name="android.hardware.usb.host"
|
||||||
<uses-feature
|
android:required="false" />
|
||||||
android:name="android.hardware.usb.host"
|
<!-- External mouse input events -->
|
||||||
android:required="false" />
|
<uses-feature
|
||||||
<!-- External mouse input events -->
|
android:name="android.hardware.type.pc"
|
||||||
<uses-feature
|
android:required="false" />
|
||||||
android:name="android.hardware.type.pc"
|
<!-- Audio recording support -->
|
||||||
android:required="false" />
|
<uses-feature
|
||||||
<!-- Audio recording support -->
|
android:name="android.hardware.microphone"
|
||||||
<uses-feature
|
android:required="false" />
|
||||||
android:name="android.hardware.microphone"
|
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
||||||
android:required="false" />
|
<uses-permission
|
||||||
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
<uses-permission
|
android:maxSdkVersion="22" />
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
<uses-permission
|
||||||
android:maxSdkVersion="22" />
|
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
android:maxSdkVersion="32" />
|
||||||
android:maxSdkVersion="32" />
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
<!-- Allow access to Bluetooth devices -->
|
<!-- Allow access to Bluetooth devices -->
|
||||||
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.BLUETOOTH"
|
android:name="android.permission.BLUETOOTH"
|
||||||
android:maxSdkVersion="30" />
|
android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
<!-- Allow access to the vibrator -->
|
<!-- Allow access to the vibrator -->
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<!-- Allow recording audio -->
|
<!-- Allow recording audio -->
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<!-- Allow internet access -->
|
<!-- Allow internet access -->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<!-- Dedicated server -->
|
<!-- Dedicated server -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MainApplication"
|
android:name=".MainApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:theme="@style/Theme.App"
|
android:theme="@style/Theme.App"
|
||||||
android:usesCleartextTraffic="true">
|
android:usesCleartextTraffic="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".XashActivity"
|
android:name=".XashActivity"
|
||||||
android:alwaysRetainTaskState="true"
|
android:alwaysRetainTaskState="true"
|
||||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation|touchscreen"
|
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation|touchscreen"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:preferMinimalPostProcessing="true"
|
android:preferMinimalPostProcessing="true"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize">
|
||||||
tools:targetApi="r">
|
<intent-filter>
|
||||||
<intent-filter>
|
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
</intent-filter>
|
||||||
</intent-filter>
|
</activity>
|
||||||
</activity>
|
</application>
|
||||||
|
</manifest>
|
||||||
<provider
|
|
||||||
android:name=".XashDocumentsProvider"
|
|
||||||
android:authorities="@string/authority"
|
|
||||||
android:exported="true"
|
|
||||||
android:grantUriPermissions="true"
|
|
||||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
|
||||||
</intent-filter>
|
|
||||||
</provider>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
<queries>
|
|
||||||
<intent>
|
|
||||||
<action android:name="su.xash.engine.MOD" />
|
|
||||||
</intent>
|
|
||||||
</queries>
|
|
||||||
</manifest>
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package su.xash.engine;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
|
|
||||||
public class AndroidBug5497Workaround {
|
|
||||||
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
|
|
||||||
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
|
|
||||||
|
|
||||||
public static void assistActivity(Activity activity) {
|
|
||||||
new AndroidBug5497Workaround(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private View mChildOfContent;
|
|
||||||
private int usableHeightPrevious;
|
|
||||||
private FrameLayout.LayoutParams frameLayoutParams;
|
|
||||||
|
|
||||||
private AndroidBug5497Workaround(Activity activity) {
|
|
||||||
FrameLayout content = activity.findViewById(android.R.id.content);
|
|
||||||
mChildOfContent = content.getChildAt(0);
|
|
||||||
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent);
|
|
||||||
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void possiblyResizeChildOfContent() {
|
|
||||||
int usableHeightNow = computeUsableHeight();
|
|
||||||
if (usableHeightNow != usableHeightPrevious) {
|
|
||||||
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
|
|
||||||
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
|
|
||||||
if (heightDifference > (usableHeightSansKeyboard / 4)) {
|
|
||||||
// keyboard probably just became visible
|
|
||||||
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
|
|
||||||
} else {
|
|
||||||
// keyboard probably just became hidden
|
|
||||||
frameLayoutParams.height = usableHeightSansKeyboard;
|
|
||||||
}
|
|
||||||
mChildOfContent.requestLayout();
|
|
||||||
usableHeightPrevious = usableHeightNow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int computeUsableHeight() {
|
|
||||||
Rect r = new Rect();
|
|
||||||
mChildOfContent.getWindowVisibleDisplayFrame(r);
|
|
||||||
return (r.bottom - r.top);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
package su.xash.engine
|
package su.xash.engine
|
||||||
|
|
||||||
class DedicatedActivity {
|
class DedicatedActivity {}
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import android.app.Service
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
|
||||||
class DedicatedService: Service() {
|
class DedicatedService : Service() {
|
||||||
override fun onBind(intent: Intent?): IBinder? {
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,26 +10,26 @@ import androidx.navigation.ui.setupActionBarWithNavController
|
|||||||
import su.xash.engine.databinding.ActivityMainBinding
|
import su.xash.engine.databinding.ActivityMainBinding
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||||
private lateinit var navController: NavController
|
private lateinit var navController: NavController
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
setSupportActionBar(binding.toolbar)
|
setSupportActionBar(binding.toolbar)
|
||||||
|
|
||||||
val navHostFragment =
|
val navHostFragment =
|
||||||
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
|
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
|
||||||
navController = navHostFragment.navController
|
navController = navHostFragment.navController
|
||||||
appBarConfiguration = AppBarConfiguration(navController.graph)
|
appBarConfiguration = AppBarConfiguration(navController.graph)
|
||||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,25 @@ package su.xash.engine
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
import org.acra.config.httpSender
|
|
||||||
import org.acra.data.StringFormat
|
import org.acra.data.StringFormat
|
||||||
import org.acra.ktx.initAcra
|
import org.acra.ktx.initAcra
|
||||||
|
|
||||||
class MainApplication : Application() {
|
class MainApplication : Application() {
|
||||||
override fun attachBaseContext(base: Context?) {
|
override fun attachBaseContext(base: Context?) {
|
||||||
super.attachBaseContext(base)
|
super.attachBaseContext(base)
|
||||||
|
|
||||||
if (!BuildConfig.DEBUG) {
|
if (!BuildConfig.DEBUG) {
|
||||||
initAcra {
|
initAcra {
|
||||||
buildConfigClass = BuildConfig::class.java
|
buildConfigClass = BuildConfig::class.java
|
||||||
reportFormat = StringFormat.JSON
|
reportFormat = StringFormat.JSON
|
||||||
|
|
||||||
httpSender {
|
// httpSender {
|
||||||
uri = "http://bodis.pp.ua:5000/report"
|
// uri = "http://bodis.pp.ua:5000/report"
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// enable strict mode to detect memory leaks etc.
|
// enable strict mode to detect memory leaks etc.
|
||||||
StrictMode.enableDefaults();
|
StrictMode.enableDefaults();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.content.pm.ActivityInfo;
|
|||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
import android.provider.Settings.Secure;
|
import android.provider.Settings.Secure;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@@ -12,137 +13,142 @@ import android.view.WindowManager;
|
|||||||
|
|
||||||
import org.libsdl.app.SDLActivity;
|
import org.libsdl.app.SDLActivity;
|
||||||
|
|
||||||
|
import su.xash.engine.util.AndroidBug5497Workaround;
|
||||||
|
|
||||||
public class XashActivity extends SDLActivity {
|
public class XashActivity extends SDLActivity {
|
||||||
private boolean mUseVolumeKeys;
|
private boolean mUseVolumeKeys;
|
||||||
private String mPackageName;
|
private String mPackageName;
|
||||||
private static final String TAG = "XashActivity";
|
private static final String TAG = "XashActivity";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
//getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
|
//getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
|
||||||
getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidBug5497Workaround.assistActivity(this);
|
AndroidBug5497Workaround.assistActivity(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy()
|
public void onDestroy() {
|
||||||
{
|
super.onDestroy();
|
||||||
super.onDestroy();
|
|
||||||
|
|
||||||
// Now that we don't exit from native code, we need to exit here, resetting
|
// Now that we don't exit from native code, we need to exit here, resetting
|
||||||
// application state (actually global variables that we don't cleanup on exit)
|
// application state (actually global variables that we don't cleanup on exit)
|
||||||
//
|
//
|
||||||
// When the issue with global variables will be resolved, remove that exit() call
|
// When the issue with global variables will be resolved, remove that exit() call
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String[] getLibraries() {
|
protected String[] getLibraries() {
|
||||||
return new String[]{"SDL2", "xash"};
|
return new String[]{"SDL2", "xash"};
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("HardwareIds")
|
@SuppressLint("HardwareIds")
|
||||||
private String getAndroidID() {
|
private String getAndroidID() {
|
||||||
return Secure.getString(getContentResolver(), Secure.ANDROID_ID);
|
return Secure.getString(getContentResolver(), Secure.ANDROID_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
@SuppressLint("ApplySharedPref")
|
||||||
private void saveAndroidID(String id) {
|
private void saveAndroidID(String id) {
|
||||||
getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit();
|
getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String loadAndroidID() {
|
private String loadAndroidID() {
|
||||||
return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", "");
|
return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCallingPackage() {
|
public String getCallingPackage() {
|
||||||
if (mPackageName != null) {
|
if (mPackageName != null) {
|
||||||
return mPackageName;
|
return mPackageName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.getCallingPackage();
|
return super.getCallingPackage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private AssetManager getAssets(boolean isEngine) {
|
private AssetManager getAssets(boolean isEngine) {
|
||||||
AssetManager am = null;
|
AssetManager am = null;
|
||||||
|
|
||||||
if (isEngine) {
|
if (isEngine) {
|
||||||
am = getAssets();
|
am = getAssets();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets();
|
am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Unable to load mod assets!");
|
Log.e(TAG, "Unable to load mod assets!");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return am;
|
return am;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] getAssetsList(boolean isEngine, String path) {
|
private String[] getAssetsList(boolean isEngine, String path) {
|
||||||
AssetManager am = getAssets(isEngine);
|
AssetManager am = getAssets(isEngine);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return am.list(path);
|
return am.list(path);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new String[]{};
|
return new String[]{};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||||
if (SDLActivity.mBrokenLibraries) {
|
if (SDLActivity.mBrokenLibraries) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int keyCode = event.getKeyCode();
|
int keyCode = event.getKeyCode();
|
||||||
if (!mUseVolumeKeys) {
|
if (!mUseVolumeKeys) {
|
||||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_ZOOM_IN || keyCode == KeyEvent.KEYCODE_ZOOM_OUT) {
|
||||||
keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
|
return false;
|
||||||
keyCode == KeyEvent.KEYCODE_CAMERA ||
|
}
|
||||||
keyCode == KeyEvent.KEYCODE_ZOOM_IN ||
|
}
|
||||||
keyCode == KeyEvent.KEYCODE_ZOOM_OUT) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return getWindow().superDispatchKeyEvent(event);
|
return getWindow().superDispatchKeyEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: REMOVE LATER, temporary launchers support?
|
// TODO: REMOVE LATER, temporary launchers support?
|
||||||
@Override
|
@Override
|
||||||
protected String[] getArguments() {
|
protected String[] getArguments() {
|
||||||
String gamedir = getIntent().getStringExtra("gamedir");
|
String gamedir = getIntent().getStringExtra("gamedir");
|
||||||
if (gamedir == null) gamedir = "valve";
|
if (gamedir == null) gamedir = "valve";
|
||||||
nativeSetenv("XASH3D_GAME", gamedir);
|
nativeSetenv("XASH3D_GAME", gamedir);
|
||||||
|
|
||||||
String gamelibdir = getIntent().getStringExtra("gamelibdir");
|
String gamelibdir = getIntent().getStringExtra("gamelibdir");
|
||||||
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
|
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
|
||||||
|
|
||||||
String pakfile = getIntent().getStringExtra("pakfile");
|
String pakfile = getIntent().getStringExtra("pakfile");
|
||||||
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);
|
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);
|
||||||
|
|
||||||
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
|
String basedir = getIntent().getStringExtra("basedir");
|
||||||
mPackageName = getIntent().getStringExtra("package");
|
if (basedir != null) {
|
||||||
|
nativeSetenv("XASH3D_BASEDIR", basedir);
|
||||||
|
} else {
|
||||||
|
String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/xash";
|
||||||
|
nativeSetenv("XASH3D_BASEDIR", rootPath);
|
||||||
|
}
|
||||||
|
|
||||||
String[] env = getIntent().getStringArrayExtra("env");
|
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
|
||||||
if (env != null) {
|
mPackageName = getIntent().getStringExtra("package");
|
||||||
for (int i = 0; i < env.length; i += 2)
|
|
||||||
nativeSetenv(env[i], env[i + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
String argv = getIntent().getStringExtra("argv");
|
String[] env = getIntent().getStringArrayExtra("env");
|
||||||
if (argv == null) argv = "-console -log";
|
if (env != null) {
|
||||||
return argv.split(" ");
|
for (int i = 0; i < env.length; i += 2)
|
||||||
}
|
nativeSetenv(env[i], env[i + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
String argv = getIntent().getStringExtra("argv");
|
||||||
|
if (argv == null) argv = "-console -log";
|
||||||
|
return argv.split(" ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,253 +0,0 @@
|
|||||||
package su.xash.engine;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.res.AssetFileDescriptor;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.MatrixCursor;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.CancellationSignal;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.provider.DocumentsContract.Document;
|
|
||||||
import android.provider.DocumentsContract.Root;
|
|
||||||
import android.provider.DocumentsProvider;
|
|
||||||
import android.webkit.MimeTypeMap;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
|
||||||
public class XashDocumentsProvider extends DocumentsProvider {
|
|
||||||
private static final String ALL_MIME_TYPES = "*/*";
|
|
||||||
private File mRootDir;
|
|
||||||
|
|
||||||
private static final String TAG = "XashDocumentsProvider";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreate() {
|
|
||||||
mRootDir = getContext().getExternalFilesDir(null);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{Root.COLUMN_ROOT_ID,
|
|
||||||
Root.COLUMN_MIME_TYPES,
|
|
||||||
Root.COLUMN_FLAGS,
|
|
||||||
Root.COLUMN_ICON,
|
|
||||||
Root.COLUMN_TITLE,
|
|
||||||
Root.COLUMN_SUMMARY,
|
|
||||||
Root.COLUMN_DOCUMENT_ID,
|
|
||||||
Root.COLUMN_AVAILABLE_BYTES};
|
|
||||||
|
|
||||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{Document.COLUMN_DOCUMENT_ID,
|
|
||||||
Document.COLUMN_MIME_TYPE,
|
|
||||||
Document.COLUMN_DISPLAY_NAME,
|
|
||||||
Document.COLUMN_LAST_MODIFIED,
|
|
||||||
Document.COLUMN_FLAGS,
|
|
||||||
Document.COLUMN_SIZE};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor queryRoots(String[] projection) {
|
|
||||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
|
||||||
|
|
||||||
final String appName = getContext().getString(R.string.app_name);
|
|
||||||
final String docId = getDocIdForFile(mRootDir);
|
|
||||||
|
|
||||||
int flags;
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH;
|
|
||||||
} else {
|
|
||||||
flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
|
|
||||||
}
|
|
||||||
|
|
||||||
final MatrixCursor.RowBuilder row = result.newRow();
|
|
||||||
row.add(Root.COLUMN_ROOT_ID, docId);
|
|
||||||
row.add(Root.COLUMN_DOCUMENT_ID, docId);
|
|
||||||
row.add(Root.COLUMN_SUMMARY, null);
|
|
||||||
row.add(Root.COLUMN_FLAGS, flags);
|
|
||||||
row.add(Root.COLUMN_TITLE, appName);
|
|
||||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
|
||||||
row.add(Root.COLUMN_AVAILABLE_BYTES, mRootDir.getFreeSpace());
|
|
||||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
|
||||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
|
||||||
|
|
||||||
includeFile(result, documentId, null);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
|
|
||||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
|
||||||
|
|
||||||
final File parent = getFileForDocId(parentDocumentId);
|
|
||||||
final File[] filesList = parent.listFiles();
|
|
||||||
|
|
||||||
if (filesList != null) {
|
|
||||||
for (File file : filesList) {
|
|
||||||
includeFile(result, null, file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ParcelFileDescriptor openDocument(final String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
|
|
||||||
final File file = getFileForDocId(documentId);
|
|
||||||
|
|
||||||
final int accessMode = ParcelFileDescriptor.parseMode(mode);
|
|
||||||
|
|
||||||
return ParcelFileDescriptor.open(file, accessMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
|
||||||
final File file = getFileForDocId(documentId);
|
|
||||||
|
|
||||||
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
||||||
|
|
||||||
return new AssetFileDescriptor(pfd, 0, file.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
|
|
||||||
File newFile = new File(parentDocumentId, displayName);
|
|
||||||
|
|
||||||
int noConflictId = 1;
|
|
||||||
|
|
||||||
while (newFile.exists()) {
|
|
||||||
newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
boolean succeeded;
|
|
||||||
|
|
||||||
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
|
||||||
succeeded = newFile.mkdir();
|
|
||||||
} else {
|
|
||||||
succeeded = newFile.createNewFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!succeeded) {
|
|
||||||
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
return newFile.getPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteDocument(String documentId) throws FileNotFoundException {
|
|
||||||
File file = getFileForDocId(documentId);
|
|
||||||
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
if (!deleteDirectory(file)) {
|
|
||||||
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
|
||||||
}
|
|
||||||
} else if (!file.delete()) {
|
|
||||||
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
|
||||||
File file = getFileForDocId(documentId);
|
|
||||||
|
|
||||||
return getMimeType(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
|
||||||
return documentId.startsWith(parentDocumentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static File getFileForDocId(String docId) throws FileNotFoundException {
|
|
||||||
final File f = new File(docId);
|
|
||||||
|
|
||||||
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
|
|
||||||
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getDocIdForFile(File file) {
|
|
||||||
return file.getAbsolutePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getMimeType(File file) {
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
return Document.MIME_TYPE_DIR;
|
|
||||||
} else {
|
|
||||||
final String name = file.getName();
|
|
||||||
final int lastDot = name.lastIndexOf('.');
|
|
||||||
|
|
||||||
if (lastDot >= 0) {
|
|
||||||
final String extension = name.substring(lastDot + 1).toLowerCase();
|
|
||||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
|
||||||
|
|
||||||
if (mime != null) return mime;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "application/octet-stream";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean deleteDirectory(File dir) {
|
|
||||||
final File[] allContents = dir.listFiles();
|
|
||||||
|
|
||||||
if (allContents != null) {
|
|
||||||
for (File file : allContents) {
|
|
||||||
deleteDirectory(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dir.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void includeFile(MatrixCursor result, String docId, File file) throws FileNotFoundException {
|
|
||||||
if (docId == null) {
|
|
||||||
docId = getDocIdForFile(file);
|
|
||||||
} else {
|
|
||||||
file = getFileForDocId(docId);
|
|
||||||
}
|
|
||||||
|
|
||||||
int flags = 0;
|
|
||||||
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
if (file.canWrite()) {
|
|
||||||
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
|
||||||
}
|
|
||||||
} else if (file.canWrite()) {
|
|
||||||
flags |= Document.FLAG_SUPPORTS_WRITE;
|
|
||||||
}
|
|
||||||
|
|
||||||
File parentFile = file.getParentFile();
|
|
||||||
if (parentFile != null && parentFile.canWrite()) {
|
|
||||||
flags |= Document.FLAG_SUPPORTS_DELETE;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String displayName = file.getName();
|
|
||||||
final String mimeType = getMimeType(file);
|
|
||||||
|
|
||||||
if (mimeType.startsWith("image/")) {
|
|
||||||
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
final MatrixCursor.RowBuilder row = result.newRow();
|
|
||||||
row.add(Document.COLUMN_DOCUMENT_ID, docId);
|
|
||||||
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
|
|
||||||
row.add(Document.COLUMN_SIZE, file.length());
|
|
||||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
|
||||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
|
||||||
row.add(Document.COLUMN_FLAGS, flags);
|
|
||||||
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,64 +14,57 @@ import su.xash.engine.ui.library.LibraryViewModel
|
|||||||
|
|
||||||
|
|
||||||
class GameAdapter(private val libraryViewModel: LibraryViewModel) :
|
class GameAdapter(private val libraryViewModel: LibraryViewModel) :
|
||||||
ListAdapter<Game, GameAdapter.GameViewHolder>(DiffCallback()) {
|
ListAdapter<Game, GameAdapter.GameViewHolder>(DiffCallback()) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder {
|
||||||
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return GameViewHolder(binding)
|
return GameViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) {
|
||||||
return holder.bind(getItem(position))
|
return holder.bind(getItem(position))
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||||
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||||
return oldItem.basedir.name == newItem.basedir.name
|
return oldItem.basedir.name == newItem.basedir.name
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||||
return oldItem.basedir.name == newItem.basedir.name && oldItem.installed == newItem.installed
|
return oldItem.basedir.name == newItem.basedir.name
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class GameViewHolder(val binding: CardGameBinding) :
|
inner class GameViewHolder(val binding: CardGameBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
fun bind(game: Game) {
|
fun bind(game: Game) {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
gameTitle.text = game.title
|
gameTitle.text = game.title
|
||||||
|
|
||||||
if (game.icon != null) {
|
if (game.icon != null) {
|
||||||
gameIcon.setImageBitmap(game.icon)
|
gameIcon.setImageBitmap(game.icon)
|
||||||
} else {
|
} else {
|
||||||
gameIcon.visibility = View.GONE
|
gameIcon.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.cover != null) {
|
if (game.cover != null) {
|
||||||
gameCover.setImageBitmap(game.cover)
|
gameCover.setImageBitmap(game.cover)
|
||||||
} else {
|
} else {
|
||||||
gameCover.visibility = View.GONE
|
gameCover.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!game.installed) {
|
settingsButton.setOnClickListener {
|
||||||
launchButton.visibility = View.GONE
|
libraryViewModel.setSelectedGame(game)
|
||||||
settingsButton.visibility = View.GONE
|
it.findNavController()
|
||||||
progressIndicator.visibility = View.VISIBLE
|
.navigate(R.id.action_libraryFragment_to_gameSettingsFragment)
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
|
||||||
settingsButton.setOnClickListener {
|
root.setOnClickListener { libraryViewModel.startEngine(it.context, game) }
|
||||||
libraryViewModel.setSelectedGame(game)
|
launchButton.setOnClickListener {
|
||||||
it.findNavController()
|
libraryViewModel.startEngine(it.context, game)
|
||||||
.navigate(R.id.action_libraryFragment_to_gameSettingsFragment)
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
root.setOnClickListener { libraryViewModel.startEngine(it.context, game) }
|
}
|
||||||
launchButton.setOnClickListener {
|
}
|
||||||
libraryViewModel.startEngine(it.context, game)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,89 +1,90 @@
|
|||||||
package su.xash.engine.model
|
package su.xash.engine.model
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
import su.xash.engine.util.TGAReader
|
import su.xash.engine.util.TGAReader
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
import java.util.Scanner
|
import java.util.Scanner
|
||||||
|
|
||||||
|
|
||||||
object BackgroundBitmap {
|
object BackgroundBitmap {
|
||||||
private const val BACKGROUND_ROWS = 3
|
private const val BACKGROUND_ROWS = 3
|
||||||
private const val BACKGROUND_COLUMNS = 4
|
private const val BACKGROUND_COLUMNS = 4
|
||||||
private const val BACKGROUND_WIDTH = 800
|
private const val BACKGROUND_WIDTH = 800
|
||||||
private const val BACKGROUND_HEIGHT = 600
|
private const val BACKGROUND_HEIGHT = 600
|
||||||
|
|
||||||
fun createBackground(ctx: Context, file: DocumentFile): Bitmap {
|
fun createBackground(file: File): Bitmap {
|
||||||
var bitmap =
|
var bitmap =
|
||||||
Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)
|
Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)
|
||||||
var canvas = Canvas(bitmap)
|
var canvas = Canvas(bitmap)
|
||||||
var x: Int
|
var x: Int
|
||||||
var y = 0
|
var y = 0
|
||||||
var width: Int
|
var width: Int
|
||||||
var height = 0
|
var height = 0
|
||||||
|
|
||||||
var bgLayout = file.findFile("resource")?.findFile("HD_BackgroundLayout.txt")
|
val resourceFolder = File(file, "resource")
|
||||||
if (bgLayout == null) {
|
var bgLayout = File(resourceFolder, "HD_BackgroundLayout.txt")
|
||||||
bgLayout = file.findFile("resource")?.findFile("BackgroundLayout.txt")
|
if (!bgLayout.exists()) {
|
||||||
}
|
bgLayout = File(resourceFolder, "BackgroundLayout.txt")
|
||||||
|
}
|
||||||
|
|
||||||
if (bgLayout == null) {
|
if (!bgLayout.exists()) {
|
||||||
val dir = file.findFile("resource")?.findFile("background")
|
val dir = File(resourceFolder, "background")
|
||||||
for (i in 0 until BACKGROUND_ROWS) {
|
for (i in 0 until BACKGROUND_ROWS) {
|
||||||
x = 0
|
x = 0
|
||||||
for (j in 0 until BACKGROUND_COLUMNS) {
|
for (j in 0 until BACKGROUND_COLUMNS) {
|
||||||
val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga"
|
val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga"
|
||||||
val bmpFile = dir?.findFile(filename)
|
val bmpFile = File(dir, filename)
|
||||||
val bmpImage = loadTga(ctx, bmpFile!!)
|
val bmpImage = loadTga(bmpFile)
|
||||||
|
|
||||||
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
|
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
|
||||||
x += bmpImage.width
|
x += bmpImage.width
|
||||||
height = bmpImage.height
|
height = bmpImage.height
|
||||||
|
|
||||||
}
|
}
|
||||||
y += height
|
y += height
|
||||||
}
|
}
|
||||||
return bitmap
|
return bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.contentResolver.openInputStream(bgLayout.uri).use { inputStream ->
|
FileInputStream(bgLayout).use { inputStream ->
|
||||||
Scanner(inputStream).use { scanner ->
|
Scanner(inputStream).use { scanner ->
|
||||||
while (scanner.hasNext()) {
|
while (scanner.hasNext()) {
|
||||||
when (val str = scanner.next()) {
|
when (val str = scanner.next()) {
|
||||||
"resolution" -> {
|
"resolution" -> {
|
||||||
width = scanner.nextInt()
|
width = scanner.nextInt()
|
||||||
height = scanner.nextInt()
|
height = scanner.nextInt()
|
||||||
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
canvas = Canvas(bitmap)
|
canvas = Canvas(bitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
var bmpFile = file
|
var bmpFile = file
|
||||||
str.split("/").forEach { bmpFile = bmpFile.findFile(it)!! }
|
str.split("/").forEach { bmpFile = File(bmpFile, it) }
|
||||||
//skip
|
//skip
|
||||||
scanner.next()
|
scanner.next()
|
||||||
x = scanner.nextInt()
|
x = scanner.nextInt()
|
||||||
y = scanner.nextInt()
|
y = scanner.nextInt()
|
||||||
val bmp = loadTga(ctx, bmpFile)
|
val bmp = loadTga(bmpFile)
|
||||||
canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)
|
canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bitmap
|
return bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadTga(ctx: Context, file: DocumentFile): Bitmap {
|
private fun loadTga(file: File): Bitmap {
|
||||||
ctx.contentResolver.openInputStream(file.uri).use {
|
FileInputStream(file).use {
|
||||||
val buffer = it?.readBytes()
|
val buffer = it.readBytes()
|
||||||
val pixels = TGAReader.read(buffer, TGAReader.ARGB)
|
val pixels = TGAReader.read(buffer, TGAReader.ARGB)
|
||||||
|
|
||||||
val width = TGAReader.getWidth(buffer)
|
val width = TGAReader.getWidth(buffer)
|
||||||
val height = TGAReader.getHeight(buffer)
|
val height = TGAReader.getHeight(buffer)
|
||||||
|
|
||||||
return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888)
|
return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,118 +5,123 @@ import android.content.Intent
|
|||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.MediaStore
|
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
import su.xash.engine.XashActivity
|
import su.xash.engine.XashActivity
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
|
||||||
class Game(val ctx: Context, val basedir: DocumentFile, var installed: Boolean = true) {
|
class Game(val ctx: Context, val basedir: File) {
|
||||||
private var iconName = "game.ico"
|
private var iconName = "game.ico"
|
||||||
var title = "Unknown Game"
|
var title = "Unknown Game"
|
||||||
var icon: Bitmap? = null
|
var icon: Bitmap? = null
|
||||||
var cover: Bitmap? = null
|
var cover: Bitmap? = null
|
||||||
|
|
||||||
private val pref = ctx.getSharedPreferences(basedir.name, Context.MODE_PRIVATE)
|
private val pref = ctx.getSharedPreferences(basedir.name, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
basedir.findFile("gameinfo.txt")?.let {
|
val gameInfo = File(basedir, "gameinfo.txt")
|
||||||
parseGameInfo(it)
|
if (gameInfo.exists()) {
|
||||||
} ?: basedir.findFile("liblist.gam")?.let { parseGameInfo(it) }
|
parseGameInfo(gameInfo)
|
||||||
|
} else {
|
||||||
|
val libListGam = File(basedir, "liblist.gam")
|
||||||
|
if (libListGam.exists()) parseGameInfo(libListGam)
|
||||||
|
}
|
||||||
|
|
||||||
basedir.findFile(iconName)
|
val iconFile = File(basedir, iconName)
|
||||||
?.let { icon = MediaStore.Images.Media.getBitmap(ctx.contentResolver, it.uri) }
|
if (iconFile.exists()) {
|
||||||
|
icon = BitmapFactory.decodeFile(iconFile.path)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cover = BackgroundBitmap.createBackground(ctx, basedir)
|
cover = BackgroundBitmap.createBackground(basedir)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startEngine(ctx: Context) {
|
fun startEngine(ctx: Context) {
|
||||||
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
|
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
putExtra("gamedir", basedir.name)
|
putExtra("gamedir", basedir.name)
|
||||||
putExtra("argv", pref.getString("arguments", "-console -log"))
|
putExtra("argv", pref.getString("arguments", "-console -log"))
|
||||||
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
|
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
|
||||||
//.putExtra("gamelibdir", getGameLibDir(context))
|
putExtra("basedir", basedir.parent)
|
||||||
//.putExtra("package", getPackageName()) }
|
//.putExtra("gamelibdir", getGameLibDir(context))
|
||||||
})
|
//.putExtra("package", getPackageName()) }
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseGameInfo(file: DocumentFile) {
|
private fun parseGameInfo(file: File) {
|
||||||
ctx.contentResolver.openInputStream(file.uri).use { inputStream ->
|
FileInputStream(file).use { inputStream ->
|
||||||
inputStream?.bufferedReader().use { reader ->
|
inputStream.bufferedReader().use { reader ->
|
||||||
reader?.forEachLine {
|
reader.forEachLine {
|
||||||
val tokens = it.split("\\s+".toRegex(), limit = 2)
|
val tokens = it.split("\\s+".toRegex(), limit = 2)
|
||||||
if (tokens.size >= 2) {
|
if (tokens.size >= 2) {
|
||||||
val k = tokens[0]
|
val k = tokens[0]
|
||||||
val v = tokens[1].trim('"')
|
val v = tokens[1].trim('"')
|
||||||
|
|
||||||
if (k == "title" || k == "game") title = v
|
if (k == "title" || k == "game") title = v
|
||||||
if (k == "icon") iconName = v
|
if (k == "icon") iconName = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPackageName(): String? {
|
private fun getPackageName(): String? {
|
||||||
// return if (mDbEntry != null) {
|
// return if (mDbEntry != null) {
|
||||||
// mDbEntry.getPackageName()
|
// mDbEntry.getPackageName()
|
||||||
// } else null
|
// } else null
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getGameLibDir(ctx: Context): String? {
|
private fun getGameLibDir(ctx: Context): String? {
|
||||||
val pkgName = getPackageName()
|
val pkgName = getPackageName()
|
||||||
if (pkgName != null) {
|
if (pkgName != null) {
|
||||||
val pkgInfo: PackageInfo = try {
|
val pkgInfo: PackageInfo = try {
|
||||||
ctx.packageManager.getPackageInfo(pkgName, 0)
|
ctx.packageManager.getPackageInfo(pkgName, 0)
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
ctx.startActivity(
|
ctx.startActivity(
|
||||||
Intent(
|
Intent(
|
||||||
Intent.ACTION_VIEW, Uri.parse("market://details?id=$pkgName")
|
Intent.ACTION_VIEW, Uri.parse("market://details?id=$pkgName")
|
||||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return pkgInfo.applicationInfo.nativeLibraryDir
|
return pkgInfo.applicationInfo?.nativeLibraryDir
|
||||||
}
|
}
|
||||||
return ctx.applicationInfo.nativeLibraryDir
|
return ctx.applicationInfo.nativeLibraryDir
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getGames(ctx: Context, file: DocumentFile): List<Game> {
|
fun getGames(ctx: Context, file: File): List<Game> {
|
||||||
val games = mutableListOf<Game>()
|
val games = mutableListOf<Game>()
|
||||||
|
|
||||||
if (checkIfGamedir(file)) {
|
if (checkIfGamedir(file)) {
|
||||||
games.add(Game(ctx, file))
|
games.add(Game(ctx, file))
|
||||||
} else {
|
} else {
|
||||||
file.listFiles().forEach {
|
file.listFiles()?.forEach {
|
||||||
if (it.isDirectory) {
|
if (it.isDirectory) {
|
||||||
if (checkIfGamedir(it)) {
|
if (checkIfGamedir(it)) {
|
||||||
games.add(Game(ctx, it))
|
games.add(Game(ctx, it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return games
|
return games
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkIfGamedir(file: DocumentFile): Boolean {
|
fun checkIfGamedir(file: File): Boolean {
|
||||||
// exclude unfinished downloads
|
if (File(file, "liblist.gam").exists()) return true
|
||||||
if (file.name?.startsWith('.') == true)
|
if (File(file, "gameinfo.txt").exists()) return true
|
||||||
return false
|
|
||||||
|
|
||||||
file.findFile("liblist.gam")?.let { return true }
|
return false
|
||||||
file.findFile("gameinfo.txt")?.let { return true }
|
}
|
||||||
return false
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intent intent = new Intent("su.xash.engine.MOD");
|
// Intent intent = new Intent("su.xash.engine.MOD");
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
package su.xash.engine.model
|
|
||||||
|
|
||||||
import org.json.JSONArray
|
|
||||||
import org.json.JSONObject
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
class ModDatabase(inputStream: InputStream) {
|
|
||||||
val entries = mutableListOf<Entry>()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val VERSION = 1
|
|
||||||
|
|
||||||
fun getFilename(): String {
|
|
||||||
return "v${VERSION}.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
inputStream.bufferedReader().use {
|
|
||||||
val jsonArray = JSONArray(it.readText())
|
|
||||||
|
|
||||||
for (i in 0..<jsonArray.length()) {
|
|
||||||
entries.add(Entry(jsonArray.getJSONObject(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getByGameDir(gamedir: String): Entry? {
|
|
||||||
return entries.filter { it.gamedir.equals(gamedir) }.firstOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
inner class Entry(jsonObject: JSONObject) {
|
|
||||||
var name: String? = null
|
|
||||||
var appid: Int? = null
|
|
||||||
var gamedir: String? = null
|
|
||||||
|
|
||||||
// TODO Depots
|
|
||||||
var pkgname: String? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (jsonObject.has("name")) {
|
|
||||||
name = jsonObject.getString("name");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsonObject.has("app_id")) {
|
|
||||||
appid = jsonObject.getInt("app_id");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsonObject.has("gamedir")) {
|
|
||||||
gamedir = jsonObject.getString("gamedir");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsonObject.has("package_name")) {
|
|
||||||
pkgname = jsonObject.getString("package_name");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +1,110 @@
|
|||||||
package su.xash.engine.ui.library
|
package su.xash.engine.ui.library
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ActivityNotFoundException
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.os.Environment
|
||||||
|
import android.provider.Settings
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.MenuProvider
|
import androidx.core.view.MenuProvider
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import kotlinx.coroutines.launch
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import su.xash.engine.BuildConfig
|
||||||
import su.xash.engine.R
|
import su.xash.engine.R
|
||||||
import su.xash.engine.adapters.GameAdapter
|
import su.xash.engine.adapters.GameAdapter
|
||||||
import su.xash.engine.databinding.FragmentLibraryBinding
|
import su.xash.engine.databinding.FragmentLibraryBinding
|
||||||
|
|
||||||
class LibraryFragment : Fragment(), MenuProvider {
|
class LibraryFragment : Fragment(), MenuProvider {
|
||||||
private var _binding: FragmentLibraryBinding? = null
|
private var _binding: FragmentLibraryBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(
|
private val startActivityForResult =
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
): View? {
|
if (checkStoragePermissions()) {
|
||||||
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
|
libraryViewModel.reloadGames(requireContext())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val adapter = GameAdapter(libraryViewModel)
|
private fun checkStoragePermissions(): Boolean {
|
||||||
binding.gamesList.adapter = adapter
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
|
||||||
|
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||||
|
setTitle(R.string.file_access_required)
|
||||||
|
setMessage(R.string.file_access_message)
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
startActivityForResult.launch(
|
||||||
|
Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).setData(
|
||||||
|
Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
setCancelable(false)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
|
||||||
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return binding.root
|
override fun onCreateView(
|
||||||
}
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
val adapter = GameAdapter(libraryViewModel)
|
||||||
binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }
|
binding.gamesList.adapter = adapter
|
||||||
|
|
||||||
libraryViewModel.isReloading.observe(viewLifecycleOwner) {
|
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
||||||
binding.swipeRefresh.isRefreshing = it
|
|
||||||
}
|
|
||||||
|
|
||||||
libraryViewModel.installedGames.observe(viewLifecycleOwner) {
|
return binding.root
|
||||||
(binding.gamesList.adapter as GameAdapter).submitList(it)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
libraryViewModel.workInfos.observe(viewLifecycleOwner) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
libraryViewModel.refreshDownloads(requireContext())
|
binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }
|
||||||
}
|
|
||||||
|
|
||||||
libraryViewModel.downloads.observe(viewLifecycleOwner) {
|
libraryViewModel.isReloading.observe(viewLifecycleOwner) {
|
||||||
libraryViewModel.reloadGames(requireContext())
|
binding.swipeRefresh.isRefreshing = it
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
libraryViewModel.installedGames.observe(viewLifecycleOwner) {
|
||||||
super.onDestroyView()
|
(binding.gamesList.adapter as GameAdapter).submitList(it)
|
||||||
_binding = null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
if (checkStoragePermissions()) {
|
||||||
menuInflater.inflate(R.menu.menu_library, menu)
|
libraryViewModel.reloadGames(requireContext())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
override fun onDestroyView() {
|
||||||
when (menuItem.itemId) {
|
super.onDestroyView()
|
||||||
R.id.action_browse -> {
|
_binding = null
|
||||||
try {
|
}
|
||||||
startActivity(
|
|
||||||
Intent(Intent.ACTION_VIEW).setDataAndType(
|
|
||||||
null, "vnd.android.document/directory"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
catch(e: ActivityNotFoundException) {
|
|
||||||
Toast.makeText(getActivity(), R.string.library_fragment_no_file_manager, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.action_install -> {
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
findNavController().navigate(R.id.action_libraryFragment_to_setupFragment)
|
menuInflater.inflate(R.menu.menu_library, menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_settings -> {
|
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||||
findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment)
|
when (menuItem.itemId) {
|
||||||
}
|
R.id.action_settings -> {
|
||||||
}
|
findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,125 +3,53 @@ package su.xash.engine.ui.library
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.os.Environment
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import androidx.work.Data
|
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
|
||||||
import androidx.work.OutOfQuotaPolicy
|
|
||||||
import androidx.work.WorkInfo
|
|
||||||
import androidx.work.WorkManager
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import su.xash.engine.model.Game
|
import su.xash.engine.model.Game
|
||||||
import su.xash.engine.model.ModDatabase
|
import java.io.File
|
||||||
import su.xash.engine.workers.FileCopyWorker
|
|
||||||
import su.xash.engine.workers.KEY_FILE_URI
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
const val TAG_INSTALL = "TAG_INSTALL"
|
|
||||||
|
|
||||||
class LibraryViewModel(application: Application) : AndroidViewModel(application) {
|
class LibraryViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
val installedGames: LiveData<List<Game>> get() = _installedGames
|
val installedGames: LiveData<List<Game>> get() = _installedGames
|
||||||
private val _installedGames = MutableLiveData(emptyList<Game>())
|
private val _installedGames = MutableLiveData(emptyList<Game>())
|
||||||
|
|
||||||
val downloads: LiveData<List<Game>> get() = _downloads
|
val isReloading: LiveData<Boolean> get() = _isReloading
|
||||||
private val _downloads = MutableLiveData(emptyList<Game>())
|
private val _isReloading = MutableLiveData(false)
|
||||||
|
|
||||||
val isReloading: LiveData<Boolean> get() = _isReloading
|
val selectedItem: LiveData<Game> get() = _selectedItem
|
||||||
private val _isReloading = MutableLiveData(false)
|
private val _selectedItem = MutableLiveData<Game>()
|
||||||
|
|
||||||
private val workManager = WorkManager.getInstance(application.applicationContext)
|
private val appPreferences: SharedPreferences =
|
||||||
val workInfos: LiveData<List<WorkInfo>> = workManager.getWorkInfosByTagLiveData(TAG_INSTALL)
|
application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
val selectedItem: LiveData<Game> get() = _selectedItem
|
fun reloadGames(ctx: Context) {
|
||||||
private val _selectedItem = MutableLiveData<Game>()
|
if (isReloading.value == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_isReloading.value = true
|
||||||
|
|
||||||
val appPreferences = application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
viewModelScope.launch {
|
||||||
val modDb: ModDatabase
|
withContext(Dispatchers.IO) {
|
||||||
|
val rootPath = appPreferences.getString("game_path", null)
|
||||||
|
?: (Environment.getExternalStorageDirectory().absolutePath + "/xash")
|
||||||
|
val root = File(rootPath)
|
||||||
|
|
||||||
init {
|
_installedGames.postValue(Game.getGames(ctx, root))
|
||||||
modDb = application.assets.open(ModDatabase.getFilename()).use { ModDatabase(it) }
|
_isReloading.postValue(false)
|
||||||
reloadGames(application.applicationContext)
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun reloadGames(ctx: Context) {
|
fun setSelectedGame(game: Game) {
|
||||||
if (isReloading.value == true) {
|
_selectedItem.value = game
|
||||||
return
|
}
|
||||||
}
|
|
||||||
_isReloading.value = true
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
fun startEngine(ctx: Context, game: Game) {
|
||||||
withContext(Dispatchers.IO) {
|
game.startEngine(ctx)
|
||||||
val games = mutableListOf<Game>()
|
}
|
||||||
val root = DocumentFile.fromFile(ctx.getExternalFilesDir(null)!!)
|
}
|
||||||
|
|
||||||
val installedGames = Game.getGames(ctx, root)
|
|
||||||
.filter { p -> _downloads.value?.any { p.basedir.name == it.basedir.name } == false }
|
|
||||||
|
|
||||||
games.addAll(installedGames)
|
|
||||||
downloads.value?.let { games.addAll(it) }
|
|
||||||
|
|
||||||
_installedGames.postValue(games)
|
|
||||||
_isReloading.postValue(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun refreshDownloads(ctx: Context) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val games = mutableListOf<Game>()
|
|
||||||
|
|
||||||
workInfos.value?.filter {
|
|
||||||
it.state == WorkInfo.State.RUNNING && !it.progress.getString(FileCopyWorker.Input)
|
|
||||||
.isNullOrEmpty()
|
|
||||||
}?.forEach {
|
|
||||||
val uri = Uri.parse(it.progress.getString(FileCopyWorker.Input))
|
|
||||||
val file = DocumentFile.fromTreeUri(ctx, uri)
|
|
||||||
games.addAll(Game.getGames(ctx, file!!))
|
|
||||||
games.forEach { g -> g.installed = false }
|
|
||||||
}
|
|
||||||
|
|
||||||
_downloads.postValue(games)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun installGame(uri: Uri) {
|
|
||||||
val data = Data.Builder().putString(KEY_FILE_URI, uri.toString()).build()
|
|
||||||
val request = OneTimeWorkRequestBuilder<FileCopyWorker>().run {
|
|
||||||
setInputData(data)
|
|
||||||
addTag(TAG_INSTALL)
|
|
||||||
build()
|
|
||||||
}
|
|
||||||
workManager.enqueue(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSelectedGame(game: Game) {
|
|
||||||
_selectedItem.value = game
|
|
||||||
}
|
|
||||||
|
|
||||||
fun uninstallGame(game: Game) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
game.installed = false
|
|
||||||
game.basedir.delete()
|
|
||||||
_installedGames.postValue(_installedGames.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startEngine(ctx: Context, game: Game) {
|
|
||||||
game.startEngine(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,24 +8,24 @@ import androidx.fragment.app.Fragment
|
|||||||
import su.xash.engine.databinding.FragmentAppSettingsBinding
|
import su.xash.engine.databinding.FragmentAppSettingsBinding
|
||||||
|
|
||||||
class AppSettingsFragment : Fragment() {
|
class AppSettingsFragment : Fragment() {
|
||||||
private var _binding: FragmentAppSettingsBinding? = null
|
private var _binding: FragmentAppSettingsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)
|
_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
childFragmentManager.beginTransaction()
|
childFragmentManager.beginTransaction()
|
||||||
.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();
|
.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
_binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import androidx.preference.PreferenceFragmentCompat
|
|||||||
import su.xash.engine.R
|
import su.xash.engine.R
|
||||||
|
|
||||||
class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
|
class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
preferenceManager.sharedPreferencesName = "app_preferences";
|
preferenceManager.sharedPreferencesName = "app_preferences";
|
||||||
setPreferencesFromResource(R.xml.app_preferences, rootKey);
|
setPreferencesFromResource(R.xml.app_preferences, rootKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,59 +6,52 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import su.xash.engine.R
|
|
||||||
import su.xash.engine.databinding.FragmentGameSettingsBinding
|
import su.xash.engine.databinding.FragmentGameSettingsBinding
|
||||||
import su.xash.engine.ui.library.LibraryViewModel
|
import su.xash.engine.ui.library.LibraryViewModel
|
||||||
|
|
||||||
class GameSettingsFragment : Fragment() {
|
class GameSettingsFragment : Fragment() {
|
||||||
private var _binding: FragmentGameSettingsBinding? = null
|
private var _binding: FragmentGameSettingsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)
|
_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val game = libraryViewModel.selectedItem.value!!
|
val game = libraryViewModel.selectedItem.value!!
|
||||||
|
|
||||||
binding.gameCard.apply {
|
binding.gameCard.apply {
|
||||||
gameTitle.text = game.title
|
gameTitle.text = game.title
|
||||||
|
|
||||||
if (game.icon != null) {
|
if (game.icon != null) {
|
||||||
gameIcon.setImageBitmap(game.icon)
|
gameIcon.setImageBitmap(game.icon)
|
||||||
} else {
|
} else {
|
||||||
gameIcon.visibility = View.GONE
|
gameIcon.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.cover != null) {
|
if (game.cover != null) {
|
||||||
gameCover.setImageBitmap(game.cover)
|
gameCover.setImageBitmap(game.cover)
|
||||||
} else {
|
} else {
|
||||||
gameCover.visibility = View.GONE
|
gameCover.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonsContainer.visibility = View.GONE
|
buttonsContainer.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
childFragmentManager.beginTransaction()
|
childFragmentManager.beginTransaction()
|
||||||
.add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game))
|
.add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game))
|
||||||
.commit();
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
binding.bottomNavigation.menu.findItem(R.id.action_uninstall).setOnMenuItemClickListener {
|
override fun onDestroyView() {
|
||||||
libraryViewModel.uninstallGame(game)
|
super.onDestroyView()
|
||||||
findNavController().popBackStack()
|
_binding = null
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,33 +8,33 @@ import su.xash.engine.R
|
|||||||
import su.xash.engine.model.Game
|
import su.xash.engine.model.Game
|
||||||
|
|
||||||
class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() {
|
class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
preferenceManager.sharedPreferencesName = game.basedir.name;
|
preferenceManager.sharedPreferencesName = game.basedir.name;
|
||||||
setPreferencesFromResource(R.xml.game_preferences, rootKey);
|
setPreferencesFromResource(R.xml.game_preferences, rootKey);
|
||||||
|
|
||||||
val packageList = findPreference<ListPreference>("package_name")!!
|
val packageList = findPreference<ListPreference>("package_name")!!
|
||||||
packageList.entries = arrayOf(getString(R.string.app_name))
|
packageList.entries = arrayOf(getString(R.string.app_name))
|
||||||
packageList.entryValues = arrayOf(requireContext().packageName)
|
packageList.entryValues = arrayOf(requireContext().packageName)
|
||||||
|
|
||||||
if (packageList.value == null) {
|
if (packageList.value == null) {
|
||||||
packageList.setValueIndex(0);
|
packageList.setValueIndex(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
|
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
|
||||||
val clientPackage = findPreference<ListPreference>("client_package")!!
|
val clientPackage = findPreference<ListPreference>("client_package")!!
|
||||||
val serverPackage = findPreference<ListPreference>("server_package")!!
|
val serverPackage = findPreference<ListPreference>("server_package")!!
|
||||||
separatePackages.setOnPreferenceChangeListener { _, newValue ->
|
separatePackages.setOnPreferenceChangeListener { _, newValue ->
|
||||||
if (newValue == true) {
|
if (newValue == true) {
|
||||||
packageList.isVisible = false
|
packageList.isVisible = false
|
||||||
clientPackage.isVisible = true
|
clientPackage.isVisible = true
|
||||||
serverPackage.isVisible = true
|
serverPackage.isVisible = true
|
||||||
} else {
|
} else {
|
||||||
packageList.isVisible = true
|
packageList.isVisible = true
|
||||||
clientPackage.isVisible = false
|
clientPackage.isVisible = false
|
||||||
serverPackage.isVisible = false
|
serverPackage.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
package su.xash.engine.ui.setup
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
|
||||||
import su.xash.engine.databinding.FragmentSetupBinding
|
|
||||||
import su.xash.engine.ui.setup.pages.LocationPageFragment
|
|
||||||
import su.xash.engine.ui.setup.pages.WelcomePageFragment
|
|
||||||
|
|
||||||
class SetupFragment : Fragment() {
|
|
||||||
private var _binding: FragmentSetupBinding? = null
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
private val setupViewModel: SetupViewModel by activityViewModels()
|
|
||||||
|
|
||||||
private lateinit var setupPageAdapter: SetupPageAdapter
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
_binding = FragmentSetupBinding.inflate(inflater, container, false)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
setupPageAdapter = SetupPageAdapter(this)
|
|
||||||
binding.viewPager.isUserInputEnabled = false
|
|
||||||
binding.viewPager.adapter = setupPageAdapter
|
|
||||||
|
|
||||||
setupViewModel.pageNumber.observe(viewLifecycleOwner) {
|
|
||||||
binding.viewPager.setCurrentItem(it, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SetupPageAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
|
|
||||||
val pages = listOf(WelcomePageFragment(), LocationPageFragment())
|
|
||||||
override fun getItemCount(): Int = 2
|
|
||||||
override fun createFragment(position: Int): Fragment = pages[position]
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package su.xash.engine.ui.setup
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import su.xash.engine.MainApplication
|
|
||||||
import su.xash.engine.model.Game
|
|
||||||
|
|
||||||
class SetupViewModel(application: Application) : AndroidViewModel(application) {
|
|
||||||
val pageNumber: LiveData<Int> get() = _pageNumber
|
|
||||||
private val _pageNumber = MutableLiveData(0)
|
|
||||||
|
|
||||||
fun checkIfGameDir(uri: Uri): Boolean {
|
|
||||||
val ctx = getApplication<MainApplication>().applicationContext
|
|
||||||
val file = DocumentFile.fromTreeUri(ctx, uri)!!
|
|
||||||
return Game.checkIfGamedir(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPageNumber(pos: Int) {
|
|
||||||
_pageNumber.value = pos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package su.xash.engine.ui.setup.pages
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import su.xash.engine.R
|
|
||||||
import su.xash.engine.databinding.PageLocationBinding
|
|
||||||
import su.xash.engine.ui.library.LibraryViewModel
|
|
||||||
import su.xash.engine.ui.setup.SetupViewModel
|
|
||||||
|
|
||||||
class LocationPageFragment : Fragment() {
|
|
||||||
private var _binding: PageLocationBinding? = null
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
private val setupViewModel: SetupViewModel by activityViewModels()
|
|
||||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
_binding = PageLocationBinding.inflate(inflater, container, false)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
binding.pageButton.setOnClickListener {
|
|
||||||
getGamesDirectory.launch(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private val getGamesDirectory =
|
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
|
|
||||||
it?.let {
|
|
||||||
if (!setupViewModel.checkIfGameDir(it)) {
|
|
||||||
MaterialAlertDialogBuilder(requireContext()).apply {
|
|
||||||
setTitle(R.string.error)
|
|
||||||
setMessage(R.string.setup_location_empty)
|
|
||||||
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
requireContext().contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
libraryViewModel.installGame(it)
|
|
||||||
findNavController().navigate(R.id.action_setupFragment_to_libraryFragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package su.xash.engine.ui.setup.pages
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import su.xash.engine.databinding.PageWelcomeBinding
|
|
||||||
import su.xash.engine.ui.setup.SetupViewModel
|
|
||||||
|
|
||||||
class WelcomePageFragment : Fragment() {
|
|
||||||
private var _binding: PageWelcomeBinding? = null
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
private val setupViewModel: SetupViewModel by activityViewModels()
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
_binding = PageWelcomeBinding.inflate(inflater, container, false)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
binding.pageButton.setOnClickListener {
|
|
||||||
setupViewModel.setPageNumber(1)
|
|
||||||
}
|
|
||||||
setupViewModel.setPageNumber(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package su.xash.engine.util;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
public class AndroidBug5497Workaround {
|
||||||
|
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
|
||||||
|
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
|
||||||
|
|
||||||
|
public static void assistActivity(Activity activity) {
|
||||||
|
new AndroidBug5497Workaround(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private View mChildOfContent;
|
||||||
|
private int usableHeightPrevious;
|
||||||
|
private FrameLayout.LayoutParams frameLayoutParams;
|
||||||
|
|
||||||
|
private AndroidBug5497Workaround(Activity activity) {
|
||||||
|
FrameLayout content = activity.findViewById(android.R.id.content);
|
||||||
|
mChildOfContent = content.getChildAt(0);
|
||||||
|
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent);
|
||||||
|
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void possiblyResizeChildOfContent() {
|
||||||
|
int usableHeightNow = computeUsableHeight();
|
||||||
|
if (usableHeightNow != usableHeightPrevious) {
|
||||||
|
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
|
||||||
|
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
|
||||||
|
if (heightDifference > (usableHeightSansKeyboard / 4)) {
|
||||||
|
// keyboard probably just became visible
|
||||||
|
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
|
||||||
|
} else {
|
||||||
|
// keyboard probably just became hidden
|
||||||
|
frameLayoutParams.height = usableHeightSansKeyboard;
|
||||||
|
}
|
||||||
|
mChildOfContent.requestLayout();
|
||||||
|
usableHeightPrevious = usableHeightNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int computeUsableHeight() {
|
||||||
|
Rect r = new Rect();
|
||||||
|
mChildOfContent.getWindowVisibleDisplayFrame(r);
|
||||||
|
return (r.bottom - r.top);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,97 +0,0 @@
|
|||||||
package su.xash.engine.workers
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.WorkerParameters
|
|
||||||
import androidx.work.workDataOf
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
const val KEY_FILE_URI = "KEY_FILE_URI"
|
|
||||||
|
|
||||||
class FileCopyWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
|
|
||||||
companion object {
|
|
||||||
const val Input = "Input"
|
|
||||||
const val Progress = "Progress"
|
|
||||||
}
|
|
||||||
|
|
||||||
private var fileCount = 0
|
|
||||||
private var fileCopied = 0
|
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val fileUri = inputData.getString(KEY_FILE_URI)
|
|
||||||
setProgress(workDataOf(Input to fileUri))
|
|
||||||
|
|
||||||
val uri = Uri.parse(fileUri)
|
|
||||||
val source = DocumentFile.fromTreeUri(applicationContext, uri)
|
|
||||||
|
|
||||||
fileCount = source?.countDirFiles() ?: return@withContext Result.failure()
|
|
||||||
|
|
||||||
setProgress(workDataOf(Progress to 0f))
|
|
||||||
|
|
||||||
val gamedir = source.name!!
|
|
||||||
val externalFilesDir = DocumentFile.fromFile(applicationContext.getExternalFilesDir(null)!!)
|
|
||||||
|
|
||||||
// create a directory to store staged files
|
|
||||||
val target = externalFilesDir.createDirectory(".$gamedir")!!
|
|
||||||
|
|
||||||
source.copyDirTo(applicationContext, this@FileCopyWorker, target)
|
|
||||||
|
|
||||||
target.renameTo(gamedir)
|
|
||||||
|
|
||||||
setProgress(workDataOf(Progress to 1f))
|
|
||||||
}
|
|
||||||
return Result.success()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun fileCopied(count: Int) {
|
|
||||||
if(count == 0)
|
|
||||||
return
|
|
||||||
|
|
||||||
fileCopied += count
|
|
||||||
val percentage: Float = fileCopied.toFloat() / fileCount.toFloat();
|
|
||||||
setProgress(workDataOf(Progress to percentage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DocumentFile.countDirFiles(): Int {
|
|
||||||
var count: Int = 0
|
|
||||||
|
|
||||||
listFiles().forEach {
|
|
||||||
if (it.isDirectory)
|
|
||||||
count += it.countDirFiles()
|
|
||||||
else
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DocumentFile.copyFileTo(ctx: Context, file: DocumentFile) {
|
|
||||||
val outFile = file.createFile("application", name!!)!!
|
|
||||||
|
|
||||||
ctx.contentResolver.openOutputStream(outFile.uri).use { os ->
|
|
||||||
ctx.contentResolver.openInputStream(uri).use {
|
|
||||||
it?.copyTo(os!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun DocumentFile.copyDirTo(ctx: Context, worker: FileCopyWorker, dir: DocumentFile) {
|
|
||||||
var count: Int = 0
|
|
||||||
|
|
||||||
listFiles().forEach {
|
|
||||||
if (it.isDirectory) {
|
|
||||||
val outDir = dir.createDirectory(it.name!!)!!
|
|
||||||
it.copyDirTo(ctx, worker, outDir)
|
|
||||||
} else {
|
|
||||||
it.copyFileTo(ctx, dir)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.fileCopied(count)
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#000000"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M21,10h-8.35C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H13l2,2l2,-2l2,2l4,-4.04L21,10zM7,15c-1.65,0 -3,-1.35 -3,-3c0,-1.65 1.35,-3 3,-3s3,1.35 3,3C10,13.65 8.65,15 7,15z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<vector android:height="24dp" android:tint="#000000"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:state_enabled="true">
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<gradient android:endColor="#2D73FF" android:startColor="#06BFFF" />
|
|
||||||
<corners android:radius="4dp" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
<item android:state_enabled="false">
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<gradient android:endColor="#6D6D6D" android:startColor="#8F8F8F" />
|
|
||||||
<corners android:radius="4dp" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</selector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M12.004,2c-5.25,0 -9.556,4.05 -9.964,9.197l5.36,2.216c0.454,-0.31 1.002,-0.492 1.593,-0.492 0.053,0 0.104,0.003 0.157,0.005l2.384,-3.452v-0.049c0,-2.08 1.69,-3.77 3.77,-3.77 2.079,0 3.77,1.692 3.77,3.772s-1.692,3.771 -3.77,3.771h-0.087l-3.397,2.426c0,0.043 0.003,0.088 0.003,0.133 0,1.562 -1.262,2.83 -2.825,2.83 -1.362,0 -2.513,-0.978 -2.775,-2.273l-3.838,-1.589C3.573,18.922 7.427,22 12.005,22c5.522,0 9.998,-4.477 9.998,-10 0,-5.522 -4.477,-10 -9.999,-10zM7.078,16.667c0.218,0.452 0.595,0.832 1.094,1.041 1.081,0.45 2.328,-0.063 2.777,-1.145 0.22,-0.525 0.22,-1.1 0.004,-1.625 -0.215,-0.525 -0.625,-0.934 -1.147,-1.152 -0.52,-0.217 -1.075,-0.208 -1.565,-0.025l1.269,0.525c0.797,0.333 1.174,1.25 0.84,2.046 -0.33,0.797 -1.247,1.175 -2.044,0.843l-1.228,-0.508zM17.818,9.422c0,-1.385 -1.128,-2.512 -2.513,-2.512 -1.387,0 -2.512,1.127 -2.512,2.512 0,1.388 1.125,2.513 2.512,2.513 1.386,0 2.512,-1.125 2.512,-2.513zM15.31,7.53c1.04,0 1.888,0.845 1.888,1.888s-0.847,1.888 -1.888,1.888c-1.044,0 -1.888,-0.845 -1.888,-1.888s0.845,-1.888 1.888,-1.888z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="295.46dp"
|
|
||||||
android:height="90.47dp"
|
|
||||||
android:viewportWidth="295.46"
|
|
||||||
android:viewportHeight="90.47">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="m45.08,1c-23.24,0 -42.28,17.92 -44.08,40.69l23.71,9.8c2.01,-1.37 4.44,-2.18 7.05,-2.18 0.23,0 0.47,0.01 0.7,0.02l10.54,-15.28c0,-0.07 0,-0.14 0,-0.22 0,-9.2 7.48,-16.68 16.68,-16.68 9.2,0 16.68,7.48 16.68,16.68s-7.48,16.68 -16.68,16.68c-0.13,0 -0.25,0 -0.38,-0.01l-15.04,10.73c0.01,0.19 0.01,0.39 0.01,0.59 0,6.91 -5.62,12.52 -12.52,12.52 -6.06,0 -11.13,-4.33 -12.28,-10.06l-16.96,-7.01c5.25,18.57 22.31,32.18 42.56,32.18 24.43,0 44.24,-19.81 44.24,-44.24 0,-24.43 -19.81,-44.24 -44.24,-44.24"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="m28.72,68.12 l-5.43,-2.24c0.96,2.01 2.63,3.68 4.84,4.61 4.78,1.99 10.3,-0.28 12.29,-5.06 0.96,-2.31 0.97,-4.87 0.01,-7.19 -0.95,-2.32 -2.76,-4.13 -5.07,-5.1 -2.3,-0.96 -4.76,-0.92 -6.93,-0.1l5.61,2.32c3.53,1.47 5.2,5.52 3.72,9.05 -1.47,3.53 -5.52,5.2 -9.05,3.72"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="m70.8,33.83c0,-6.13 -4.99,-11.12 -11.12,-11.12 -6.13,0 -11.12,4.99 -11.12,11.12 0,6.13 4.99,11.11 11.12,11.11 6.13,0 11.12,-4.99 11.12,-11.11m-19.45,-0.02c0,-4.61 3.74,-8.35 8.35,-8.35s8.35,3.74 8.35,8.35 -3.74,8.35 -8.35,8.35 -8.35,-3.74 -8.35,-8.35"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="m136.56,31.27 l-2.96,5.21c-2.28,-1.6 -5.38,-2.56 -8.08,-2.56 -3.09,0 -5,1.28 -5,3.57 0,2.78 3.39,3.43 8.44,5.24 5.42,1.92 8.54,4.17 8.54,9.14 0,6.79 -5.34,10.61 -13.02,10.61 -3.74,0 -8.26,-0.97 -11.73,-3.08l2.16,-5.78c2.82,1.49 6.19,2.37 9.2,2.37 4.05,0 5.98,-1.5 5.98,-3.7 0,-2.53 -2.94,-3.29 -7.68,-4.86 -5.4,-1.8 -9.15,-4.17 -9.15,-9.67 0,-6.2 4.96,-9.76 12.1,-9.76 4.98,0 8.98,1.58 11.2,3.26"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="m152.76,61.9v-27.34h-10.13v-5.99h27.21v5.99h-10.1v27.34z"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="m197.9,42.05v5.99h-13.36v7.82h15.5v6.04h-22.47v-33.33h22.47v5.97h-15.5v7.51z"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="m215.62,55.43 l-2.21,6.47h-7.32l12.49,-33.33h7.03l12.85,33.32h-7.56l-2.25,-6.47h-13.03zM222.07,36.52 L217.51,49.87h9.2z"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="m261.22,60.93 l-8.97,-19.3v20.27h-6.68v-33.33h6.67l11.2,24.06 10.8,-24.06h6.73v33.33h-6.68v-20.44l-9.12,19.47z"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="m294.46,32.78c0,2.86 -2.15,4.65 -4.61,4.65 -2.47,0 -4.62,-1.78 -4.62,-4.65 0,-2.86 2.15,-4.64 4.62,-4.64 2.46,0 4.61,1.77 4.61,4.64m-8.46,0c0,2.4 1.73,3.9 3.85,3.9 2.11,0 3.83,-1.5 3.83,-3.9 0,-2.4 -1.72,-3.88 -3.83,-3.88 -2.12,0 -3.85,1.5 -3.85,3.88m3.91,-2.37c1.2,0 1.6,0.63 1.6,1.32 0,0.63 -0.37,1.05 -0.82,1.26l1.07,2.01h-0.88l-0.9,-1.78h-0.93v1.78h-0.73v-4.58zM289.05,32.54h0.81c0.53,0 0.84,-0.33 0.84,-0.75 0,-0.42 -0.22,-0.69 -0.84,-0.69h-0.81v1.44z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,35 +1,36 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="match_parent"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/appBarLayout"
|
android:id="@+id/appBarLayout"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/fragmentContainerView"
|
android:id="@+id/fragmentContainerView"
|
||||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:defaultNavHost="true"
|
app:defaultNavHost="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
|
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
|
||||||
app:navGraph="@navigation/nav_graph"
|
app:navGraph="@navigation/nav_graph"
|
||||||
tools:layout="@layout/fragment_library"/>
|
tools:layout="@layout/fragment_library" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -1,111 +1,98 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
style="@style/Widget.Material3.CardView.Filled"
|
style="@style/Widget.Material3.CardView.Filled"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp">
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="128dp">
|
android:layout_height="128dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/gameCover"
|
android:id="@+id/gameCover"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
tools:src="@android:mipmap/sym_def_app_icon" />
|
tools:src="@android:mipmap/sym_def_app_icon" />
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
style="@style/Widget.Material3.CardView.Filled"
|
style="@style/Widget.Material3.CardView.Filled"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
app:contentPadding="10dp"
|
app:contentPadding="10dp"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/buttonsContainer"
|
app:layout_constraintEnd_toStartOf="@id/buttonsContainer"
|
||||||
app:layout_constraintHorizontal_bias="0"
|
app:layout_constraintHorizontal_bias="0"
|
||||||
app:layout_constraintStart_toStartOf="parent">
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/gameIcon"
|
android:id="@+id/gameIcon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginRight="8dp"
|
android:layout_marginRight="8dp"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/gameTitle"
|
app:layout_constraintEnd_toStartOf="@id/gameTitle"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:src="@android:mipmap/sym_def_app_icon" />
|
tools:src="@android:mipmap/sym_def_app_icon" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/gameTitle"
|
android:id="@+id/gameTitle"
|
||||||
style="@style/TextAppearance.Material3.TitleMedium"
|
style="@style/TextAppearance.Material3.TitleMedium"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/gameIcon"
|
app:layout_constraintStart_toEndOf="@id/gameIcon"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="Game Title" />
|
tools:text="Game Title" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/buttonsContainer"
|
android:id="@+id/buttonsContainer"
|
||||||
style="@style/Widget.Material3.CardView.Filled"
|
style="@style/Widget.Material3.CardView.Filled"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent">
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/progressIndicator"
|
android:id="@+id/launchButton"
|
||||||
android:layout_width="wrap_content"
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:indeterminate="true"
|
android:layout_height="match_parent"
|
||||||
android:padding="10dp"
|
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||||
android:visibility="gone"
|
app:layout_constraintEnd_toStartOf="@+id/settingsButton" />
|
||||||
app:indicatorSize="20dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/launchButton"
|
android:id="@+id/settingsButton"
|
||||||
style="@style/Widget.Material3.Button.IconButton"
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
app:icon="@drawable/ic_baseline_settings_24"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/settingsButton" />
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
<com.google.android.material.button.MaterialButton
|
</com.google.android.material.card.MaterialCardView>
|
||||||
android:id="@+id/settingsButton"
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
style="@style/Widget.Material3.Button.IconButton"
|
</com.google.android.material.card.MaterialCardView>
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:icon="@drawable/ic_baseline_settings_24"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|||||||
@@ -1,30 +1,45 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_height="wrap_content">
|
style="@style/Widget.Material3.CardView.Filled"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<!-- <com.google.android.material.textfield.TextInputLayout-->
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
<!-- android:layout_width="match_parent"-->
|
android:layout_width="match_parent"
|
||||||
<!-- android:layout_height="wrap_content"-->
|
android:layout_height="wrap_content"
|
||||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
android:padding="10dp">
|
||||||
<!-- app:layout_constraintBottom_toBottomOf="parent">-->
|
|
||||||
|
|
||||||
<!-- <com.google.android.material.textfield.TextInputEditText-->
|
<TextView
|
||||||
<!-- android:layout_width="match_parent"-->
|
android:id="@android:id/title"
|
||||||
<!-- android:layout_height="wrap_content" />-->
|
android:layout_width="match_parent"
|
||||||
<!-- </com.google.android.material.textfield.TextInputLayout>-->
|
android:layout_height="wrap_content"
|
||||||
<TextView
|
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
||||||
android:id="@android:id/title"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:layout_width="match_parent"
|
tools:text="EditText Preference" />
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
<TextView
|
||||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
|
android:id="@android:id/summary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@android:id/title"
|
||||||
|
tools:text="Summary" />
|
||||||
|
|
||||||
|
<!-- <com.google.android.material.button.MaterialButton-->
|
||||||
|
<!-- style="@style/Widget.Material3.Button.IconButton"-->
|
||||||
|
<!-- android:layout_width="wrap_content"-->
|
||||||
|
<!-- android:layout_height="wrap_content"-->
|
||||||
|
<!-- app:icon="@drawable/baseline_edit_24"-->
|
||||||
|
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
||||||
|
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||||
|
<!-- app:layout_constraintTop_toTopOf="parent" />-->
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@android:id/summary"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintTop_toBottomOf="@android:id/title"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"/>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:paddingHorizontal="8dp">
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/settingsFragment"
|
android:id="@+id/settingsFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:layout="@android:layout/list_content" />
|
tools:layout="@android:layout/list_content" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -1,39 +1,32 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/gameCard"
|
android:id="@+id/gameCard"
|
||||||
layout="@layout/card_game"
|
layout="@layout/card_game"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
style="@style/Widget.Material3.CardView.Filled"
|
android:id="@+id/settingsFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
app:layout_constraintTop_toBottomOf="@id/gameCard"
|
||||||
app:contentPadding="16dp"
|
tools:layout="@android:layout/list_content">
|
||||||
app:layout_constraintTop_toBottomOf="@id/gameCard">
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
</androidx.fragment.app.FragmentContainerView>
|
||||||
android:id="@+id/settingsFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
tools:layout="@android:layout/list_content" />
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
<!-- <com.google.android.material.bottomnavigation.BottomNavigationView-->
|
||||||
|
<!-- android:id="@+id/bottomNavigation"-->
|
||||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
<!-- style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"-->
|
||||||
android:id="@+id/bottomNavigation"
|
<!-- android:layout_width="match_parent"-->
|
||||||
style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
|
<!-- android:layout_height="wrap_content"-->
|
||||||
android:layout_width="match_parent"
|
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
||||||
android:layout_height="wrap_content"
|
<!-- app:menu="@menu/menu_game_settings" />-->
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
app:menu="@menu/menu_game_settings" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/swipeRefresh"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:id="@+id/swipeRefresh">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/gamesList"
|
android:id="@+id/gamesList"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layoutManager="LinearLayoutManager"
|
android:clipToPadding="false"
|
||||||
android:clipToPadding="false"
|
android:paddingHorizontal="8dp"
|
||||||
android:paddingHorizontal="8dp"
|
app:layoutManager="LinearLayoutManager"
|
||||||
tools:listitem="@layout/card_game" />
|
tools:listitem="@layout/card_game" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout 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">
|
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
|
||||||
android:id="@+id/viewPager"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="@color/steam_background"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".fragment.SteamLoginFragment">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/imageView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/steam_logo"
|
|
||||||
app:tint="#C5C3C0" />
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="320dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
app:startIconDrawable="@drawable/ic_baseline_person_24">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/steamLogin"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/username"
|
|
||||||
android:inputType="textEmailAddress" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="320dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
|
|
||||||
app:startIconDrawable="@drawable/ic_baseline_key_24">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/steamPassword"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/password"
|
|
||||||
android:inputType="textPassword" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/loginButton"
|
|
||||||
style="@style/App.Theme.SteamButton"
|
|
||||||
android:layout_width="240dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/login" />
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
<TextView
|
|
||||||
android:id="@android:id/title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@android:id/summary"
|
android:id="@android:id/title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toBottomOf="@android:id/title"
|
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"/>
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
<TextView
|
||||||
|
android:id="@android:id/summary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||||
|
app:layout_constraintTop_toBottomOf="@android:id/title" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/pagePic"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_marginTop="64dp"
|
|
||||||
android:layout_marginBottom="32dp"
|
|
||||||
android:src="@drawable/ic_baseline_folder_open_24"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/pageTitle"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/pageTitle"
|
|
||||||
style="@style/TextAppearance.Material3.TitleLarge"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:text="@string/setup_location_title"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/pageMessage"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/pagePic" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/pageMessage"
|
|
||||||
style="@style/TextAppearance.Material3.TitleMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:text="@string/setup_location_message"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/pageButton"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/pageTitle" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/pageButton"
|
|
||||||
android:layout_width="240dp"
|
|
||||||
android:layout_marginBottom="128dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/ok"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/pageMessage" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/pagePic"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_marginTop="64dp"
|
|
||||||
android:layout_marginBottom="32dp"
|
|
||||||
android:src="@mipmap/ic_launcher"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/pageTitle"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/pageTitle"
|
|
||||||
style="@style/TextAppearance.Material3.TitleLarge"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:text="@string/setup_welcome_title"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/pageMessage"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/pagePic" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/pageMessage"
|
|
||||||
style="@style/TextAppearance.Material3.TitleMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:text="@string/setup_welcome_message"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/pageButton"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/pageTitle" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/pageButton"
|
|
||||||
android:layout_width="240dp"
|
|
||||||
android:layout_marginBottom="128dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/next"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/pageMessage" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="32dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/steam_guard_code"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/textInputLayout"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/textView">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/steamCode"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:hint="@string/steam_guard_code" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,23 +1,37 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_height="wrap_content">
|
style="@style/Widget.Material3.CardView.Filled"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<TextView
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@android:id/title"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:padding="10dp">
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
<TextView
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:id="@android:id/title"
|
||||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Switch preference" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/switchWidget"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<com.google.android.material.materialswitch.MaterialSwitch
|
|
||||||
android:id="@+id/switchWidget"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_dedicated"
|
android:id="@+id/action_dedicated"
|
||||||
android:icon="@drawable/ic_baseline_terminal_24"
|
android:icon="@drawable/ic_baseline_terminal_24"
|
||||||
android:title="@string/dedicated_server"
|
android:title="@string/dedicated_server"
|
||||||
app:showAsAction="always|withText"
|
android:visible="false"
|
||||||
android:visible="false"/>
|
app:showAsAction="always|withText" />
|
||||||
<item
|
</menu>
|
||||||
android:id="@+id/action_uninstall"
|
|
||||||
android:icon="@drawable/ic_baseline_delete_24"
|
|
||||||
android:title="@string/uninstall"
|
|
||||||
app:showAsAction="always|withText" />
|
|
||||||
</menu>
|
|
||||||
|
|||||||
@@ -1,19 +1,8 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_browse"
|
android:id="@+id/action_settings"
|
||||||
android:icon="@drawable/ic_baseline_folder_open_24"
|
android:icon="@drawable/ic_baseline_settings_24"
|
||||||
android:title="@string/browse"
|
android:title="@string/app_settings"
|
||||||
app:showAsAction="always|withText" />
|
app:showAsAction="always|withText" />
|
||||||
<item
|
</menu>
|
||||||
android:id="@+id/action_install"
|
|
||||||
android:icon="@drawable/ic_baseline_add_24"
|
|
||||||
android:title="@string/install"
|
|
||||||
app:showAsAction="always|withText" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_settings"
|
|
||||||
android:icon="@drawable/ic_baseline_settings_24"
|
|
||||||
android:title="@string/app_settings"
|
|
||||||
app:showAsAction="always|withText"
|
|
||||||
android:visible="false"/>
|
|
||||||
</menu>
|
|
||||||
|
|||||||
@@ -1,38 +1,26 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
android:id="@+id/nav_graph"
|
||||||
android:id="@+id/nav_graph"
|
android:label="@string/library"
|
||||||
android:label="@string/library"
|
app:startDestination="@id/libraryFragment">
|
||||||
app:startDestination="@id/libraryFragment">
|
<fragment
|
||||||
<fragment
|
android:id="@+id/libraryFragment"
|
||||||
android:id="@+id/libraryFragment"
|
android:name="su.xash.engine.ui.library.LibraryFragment"
|
||||||
android:name="su.xash.engine.ui.library.LibraryFragment"
|
android:label="@string/library">
|
||||||
android:label="@string/library">
|
<action
|
||||||
<action
|
android:id="@+id/action_libraryFragment_to_gameSettingsFragment"
|
||||||
android:id="@+id/action_libraryFragment_to_setupFragment"
|
app:destination="@id/gameSettingsFragment" />
|
||||||
app:destination="@id/setupFragment" />
|
<action
|
||||||
<action
|
android:id="@+id/action_libraryFragment_to_appSettingsFragment"
|
||||||
android:id="@+id/action_libraryFragment_to_gameSettingsFragment"
|
app:destination="@id/appSettingsFragment" />
|
||||||
app:destination="@id/gameSettingsFragment" />
|
</fragment>
|
||||||
<action
|
<fragment
|
||||||
android:id="@+id/action_libraryFragment_to_appSettingsFragment"
|
android:id="@+id/gameSettingsFragment"
|
||||||
app:destination="@id/appSettingsFragment" />
|
android:name="su.xash.engine.ui.settings.GameSettingsFragment"
|
||||||
</fragment>
|
android:label="@string/game_settings" />
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/setupFragment"
|
android:id="@+id/appSettingsFragment"
|
||||||
android:name="su.xash.engine.ui.setup.SetupFragment"
|
android:name="su.xash.engine.ui.settings.AppSettingsFragment"
|
||||||
android:label="@string/setup" >
|
android:label="@string/app_settings" />
|
||||||
<action
|
</navigation>
|
||||||
android:id="@+id/action_setupFragment_to_libraryFragment"
|
|
||||||
app:destination="@id/libraryFragment" />
|
|
||||||
</fragment>
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/gameSettingsFragment"
|
|
||||||
android:name="su.xash.engine.ui.settings.GameSettingsFragment"
|
|
||||||
android:label="@string/game_settings" />
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/appSettingsFragment"
|
|
||||||
android:name="su.xash.engine.ui.settings.AppSettingsFragment"
|
|
||||||
android:label="@string/app_settings" />
|
|
||||||
</navigation>
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="ok">Ok</string>
|
|
||||||
<string name="cancel">Cancelar</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="cancel">Annulla</string>
|
|
||||||
<string name="ok">Ok</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,33 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="ok">Ok</string>
|
<string name="library">Biblioteca</string>
|
||||||
<string name="cancel">Cancelar</string>
|
<string name="dedicated_server">Dedicado</string>
|
||||||
<string name="next">Próximo</string>
|
<string name="app_settings">Configurações</string>
|
||||||
<string name="browse">Navegar</string>
|
<string name="game_settings">Configurações do jogo</string>
|
||||||
<string name="install">Instalar</string>
|
<string name="game_settings_command_line">Argumentos de linha de comando</string>
|
||||||
<string name="library">Biblioteca</string>
|
<string name="game_settings_volume_buttons">Usar botões de volume no jogo</string>
|
||||||
<string name="steam_guard_code">Código do Steam Guard</string>
|
<string name="preferences_package_name">Bibliotecas de pacotes</string>
|
||||||
<string name="steam_login">Logar com Steam</string>
|
<string name="preferences_separate_libraries">Bibliotecas de pacotes separados</string>
|
||||||
<string name="login">Logar</string>
|
<string name="preferences_client_package">Pacotes de Cliente</string>
|
||||||
<string name="username">Usuário</string>
|
<string name="preferences_server_package">Pacotes de Servidor</string>
|
||||||
<string name="password">Senha</string>
|
<string name="preferences_use_icons">Usar ícones ao inves de imagens de fundo</string>
|
||||||
<string name="uninstall">Desinstalar</string>
|
|
||||||
<string name="dedicated_server">Dedicado</string>
|
|
||||||
<string name="error">Erro!</string>
|
|
||||||
<string name="setup">Configurar</string>
|
|
||||||
<string name="app_settings">Configurações</string>
|
|
||||||
<string name="game_settings">Configurações do jogo</string>
|
|
||||||
<string name="game_settings_command_line">Argumentos de linha de comando</string>
|
|
||||||
<string name="game_settings_volume_buttons">Usar botões de volume no jogo</string>
|
|
||||||
<string name="setup_welcome_title">Bem vindo ao Xash3D FWGS!</string>
|
|
||||||
<string name="setup_welcome_message">Agora, nós vamos guiá-lo durante o processo de instalação.</string>
|
|
||||||
<string name="setup_location_title">Jogos</string>
|
|
||||||
<string name="setup_location_message">Selecione a pasta onde seus jogos estão localizados.</string>
|
|
||||||
<string name="setup_location_empty">A pasta selecionada não contem nenhum jogo.</string>
|
|
||||||
<string name="setup_location_invalid">A pasta selecionada é invalida.</string>
|
|
||||||
<string name="preferences_package_name">Bibliotecas de pacotes</string>
|
|
||||||
<string name="preferences_separate_libraries">Bibliotecas de pacotes separados</string>
|
|
||||||
<string name="preferences_client_package">Pacotes de Cliente</string>
|
|
||||||
<string name="preferences_server_package">Pacotes de Servidor</string>
|
|
||||||
<string name="preferences_use_icons">Usar ícones ao inves de imagens de fundo</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,33 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="cancel">Отмена</string>
|
<string name="library">Библиотека</string>
|
||||||
<string name="ok">ОК</string>
|
<string name="dedicated_server">Выделенный</string>
|
||||||
<string name="next">Далее</string>
|
<string name="app_settings">Настройки</string>
|
||||||
<string name="browse">Обзор</string>
|
<string name="game_settings">Настройки игры</string>
|
||||||
<string name="install">Установить</string>
|
<string name="game_settings_command_line">Аргументы командной строки</string>
|
||||||
<string name="library">Библиотека</string>
|
<string name="game_settings_volume_buttons">Использовать кнопки громкости</string>
|
||||||
<string name="steam_guard_code">Код Steam Guard</string>
|
<string name="preferences_package_name">Пакет библиотек</string>
|
||||||
<string name="steam_login">Логин Steam</string>
|
<string name="preferences_separate_libraries">Библиотеки из отдельных пакетов</string>
|
||||||
<string name="login">Логин</string>
|
<string name="preferences_client_package">Пакет клиента</string>
|
||||||
<string name="username">Имя пользователя</string>
|
<string name="preferences_server_package">Пакет сервера</string>
|
||||||
<string name="password">Пароль</string>
|
<string name="preferences_use_icons">Использовать иконки вместо фона</string>
|
||||||
<string name="uninstall">Удалить</string>
|
</resources>
|
||||||
<string name="dedicated_server">Выделенный</string>
|
|
||||||
<string name="error">Ошибка!</string>
|
|
||||||
<string name="setup">Установка</string>
|
|
||||||
<string name="app_settings">Настройки</string>
|
|
||||||
<string name="game_settings">Настройки игры</string>
|
|
||||||
<string name="game_settings_command_line">Аргументы командной строки</string>
|
|
||||||
<string name="game_settings_volume_buttons">Использовать кнопки громкости</string>
|
|
||||||
<string name="setup_welcome_title">Добро пожаловать в Xash3D FWGS!</string>
|
|
||||||
<string name="setup_welcome_message">Теперь мы ознакомим вас с процессом установки.</string>
|
|
||||||
<string name="setup_location_title">Игры</string>
|
|
||||||
<string name="setup_location_message">Выберите папку, в которой находятся ваши игры.</string>
|
|
||||||
<string name="setup_location_empty">Выбранный каталог не содержит ни одной игры.</string>
|
|
||||||
<string name="setup_location_invalid">Выбранный каталог недопустим.</string>
|
|
||||||
<string name="preferences_package_name">Пакет библиотек</string>
|
|
||||||
<string name="preferences_separate_libraries">Библиотеки из отдельных пакетов</string>
|
|
||||||
<string name="preferences_client_package">Пакет клиента</string>
|
|
||||||
<string name="preferences_server_package">Пакет сервера</string>
|
|
||||||
<string name="preferences_use_icons">Использовать иконки вместо фона</string>
|
|
||||||
</resources>
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="ok">Tamam</string>
|
|
||||||
<string name="cancel">İptal</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="ok">Ок</string>
|
|
||||||
<string name="cancel">Скасувати</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="valve_red">#F74843</color>
|
<color name="valve_red">#F74843</color>
|
||||||
<color name="steam_grey">#C5C3C0</color>
|
<color name="hl_orange">#FB7E14</color>
|
||||||
<color name="steam_background">#181A21</color>
|
<color name="black">#000000</color>
|
||||||
<color name="steam_dark_grey">#32353C</color>
|
<color name="hl_dark_grey">#151515</color>
|
||||||
<color name="hl_orange">#FB7E14</color>
|
<color name="hl_grey">#292929</color>
|
||||||
<color name="black">#000000</color>
|
|
||||||
<color name="hl_dark_grey">#151515</color>
|
|
||||||
<color name="hl_grey">#292929</color>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#F74843</color>
|
<color name="ic_launcher_background">#F74843</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,37 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">Xash3D FWGS</string>
|
<string name="app_name" translatable="false">Xash3D FWGS</string>
|
||||||
<string name="authority" translatable="false">su.xash.engine.documents</string>
|
<string name="library">Library</string>
|
||||||
<string name="cancel">Cancel</string>
|
<string name="dedicated_server">Dedicated</string>
|
||||||
<string name="ok">OK</string>
|
<string name="app_settings">Settings</string>
|
||||||
<string name="next">Next</string>
|
<string name="game_settings">Game Settings</string>
|
||||||
<string name="browse">Browse</string>
|
<string name="game_settings_command_line">Command-line arguments</string>
|
||||||
<string name="install">Install</string>
|
<string name="game_settings_volume_buttons">Use volume buttons in-game</string>
|
||||||
<string name="library">Library</string>
|
<string name="preferences_package_name">Libraries package</string>
|
||||||
<string name="steam_guard_code">Steam Guard Code</string>
|
<string name="preferences_separate_libraries">Libraries from separate packages</string>
|
||||||
<string name="steam_login">Steam Login</string>
|
<string name="preferences_client_package">Client package</string>
|
||||||
<string name="login">Login</string>
|
<string name="preferences_server_package">Server package</string>
|
||||||
<string name="username">Username</string>
|
<string name="preferences_use_icons">Use icons instead of backgrounds</string>
|
||||||
<string name="password">Password</string>
|
<string name="game_data_location">Game data location</string>
|
||||||
<string name="steam" translatable="false">Steam</string>
|
<string name="file_access_required">All-files access required</string>
|
||||||
<string name="uninstall">Uninstall</string>
|
<string name="file_access_message">All-files access is required for the app to function properly.</string>
|
||||||
<string name="dedicated_server">Dedicated</string>
|
<string name="select_current_directory">Select current directory</string>
|
||||||
<string name="error">Error!</string>
|
|
||||||
<string name="setup">Setup</string>
|
|
||||||
<string name="app_settings">Settings</string>
|
|
||||||
<string name="game_settings">Game Settings</string>
|
|
||||||
<string name="game_settings_command_line">Command-line arguments</string>
|
|
||||||
<string name="game_settings_volume_buttons">Use volume buttons in-game</string>
|
|
||||||
<string name="setup_welcome_title">Welcome to Xash3D FWGS!</string>
|
|
||||||
<string name="setup_welcome_message">Now, we will guide you through the installation process.</string>
|
|
||||||
<string name="setup_location_title">Games</string>
|
|
||||||
<string name="setup_location_message">Select the folder, where your games are located.</string>
|
|
||||||
<string name="setup_location_empty">Selected directory doesn\'t contain any games.</string>
|
|
||||||
<string name="setup_location_invalid">Selected directory is invalid.</string>
|
|
||||||
<string name="preferences_package_name">Libraries package</string>
|
|
||||||
<string name="preferences_separate_libraries">Libraries from separate packages</string>
|
|
||||||
<string name="preferences_client_package">Client package</string>
|
|
||||||
<string name="preferences_server_package">Server package</string>
|
|
||||||
<string name="preferences_use_icons">Use icons instead of backgrounds</string>
|
|
||||||
<string name="library_fragment_no_file_manager">File manager is missing</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="App.Theme.SteamButton" parent="@style/Widget.Material3.Button">
|
<style name="ShapeAppearance.App.MediumComponent" parent="ShapeAppearance.Material3.MediumComponent">
|
||||||
<item name="android:background">@drawable/steam_button_gradient</item>
|
<item name="cornerSize">4dp</item>
|
||||||
<item name="backgroundTint">@null</item>
|
</style>
|
||||||
<item name="android:textColor">#FFFFFF</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="ShapeAppearance.App.MediumComponent" parent="ShapeAppearance.Material3.MediumComponent">
|
|
||||||
<item name="cornerSize">4dp</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<style name="Theme.App" parent="Theme.Material3.Dark.NoActionBar">
|
|
||||||
<item name="colorPrimary">@color/hl_orange</item>
|
|
||||||
<item name="colorPrimaryDark">@color/hl_dark_grey</item>
|
|
||||||
<!-- <item name="colorPrimaryContainer">@color/valve_red</item>-->
|
|
||||||
<item name="colorSurface">@color/hl_grey</item>
|
|
||||||
<item name="colorSurfaceVariant">@color/hl_grey</item>
|
|
||||||
<item name="android:colorBackground">@color/hl_dark_grey</item>
|
|
||||||
<item name="colorControlNormal">@color/hl_orange</item>
|
|
||||||
<item name="colorOnPrimary">@color/hl_dark_grey</item>
|
|
||||||
<!-- <item name="colorOnPrimaryContainer">@android:color/white</item>-->
|
|
||||||
<!-- <item name="colorOnSurface">@android:color/white</item>-->
|
|
||||||
<!-- <item name="colorOnSurfaceVariant">@android:color/white</item>-->
|
|
||||||
|
|
||||||
<!-- <item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.App.MediumComponent</item>-->
|
<style name="Theme.App" parent="Theme.Material3.Dark.NoActionBar">
|
||||||
</style>
|
<item name="colorPrimary">@color/hl_orange</item>
|
||||||
</resources>
|
<item name="colorPrimaryDark">@color/hl_dark_grey</item>
|
||||||
|
<!-- <item name="colorPrimaryContainer">@color/valve_red</item>-->
|
||||||
|
<item name="colorSurface">@color/hl_grey</item>
|
||||||
|
<item name="colorSurfaceVariant">@color/hl_grey</item>
|
||||||
|
<item name="android:colorBackground">@color/hl_dark_grey</item>
|
||||||
|
<item name="colorControlNormal">@color/hl_orange</item>
|
||||||
|
<item name="colorOnPrimary">@color/hl_dark_grey</item>
|
||||||
|
<!-- <item name="colorOnPrimaryContainer">@android:color/white</item>-->
|
||||||
|
<!-- <item name="colorOnSurface">@android:color/white</item>-->
|
||||||
|
<!-- <item name="colorOnSurfaceVariant">@android:color/white</item>-->
|
||||||
|
|
||||||
|
<!-- <item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.App.MediumComponent</item>-->
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<SwitchPreferenceCompat
|
<Preference
|
||||||
app:key="use_icons"
|
app:key="game_path"
|
||||||
app:layout="@layout/switch_preference"
|
app:layout="@layout/edit_text_preference"
|
||||||
app:title="@string/preferences_use_icons" />
|
app:title="@string/game_data_location"
|
||||||
</PreferenceScreen>
|
android:summary="/storage/emulated/0/xash" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="use_icons"
|
||||||
|
app:layout="@layout/switch_preference"
|
||||||
|
app:title="@string/preferences_use_icons" />
|
||||||
|
</PreferenceScreen>
|
||||||
|
|||||||
@@ -1,42 +1,41 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
<EditTextPreference
|
||||||
<EditTextPreference
|
app:defaultValue="-console -log"
|
||||||
app:defaultValue="-console -log"
|
app:key="arguments"
|
||||||
app:key="arguments"
|
app:layout="@layout/edit_text_preference"
|
||||||
app:layout="@layout/edit_text_preference"
|
app:title="@string/game_settings_command_line"
|
||||||
app:title="@string/game_settings_command_line"
|
app:useSimpleSummaryProvider="true" />
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
app:key="use_volume_buttons"
|
app:key="use_volume_buttons"
|
||||||
app:layout="@layout/switch_preference"
|
app:layout="@layout/switch_preference"
|
||||||
app:title="@string/game_settings_volume_buttons" />
|
app:title="@string/game_settings_volume_buttons" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
app:key="separate_libraries"
|
app:key="separate_libraries"
|
||||||
app:layout="@layout/switch_preference"
|
app:layout="@layout/switch_preference"
|
||||||
app:title="@string/preferences_separate_libraries"
|
app:title="@string/preferences_separate_libraries"
|
||||||
app:isPreferenceVisible="false"/>
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
app:key="package_name"
|
app:key="package_name"
|
||||||
app:layout="@layout/list_preference"
|
app:layout="@layout/list_preference"
|
||||||
app:title="@string/preferences_package_name"
|
app:title="@string/preferences_package_name"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:isPreferenceVisible="false"/>
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
app:key="client_package"
|
app:key="client_package"
|
||||||
app:layout="@layout/list_preference"
|
app:layout="@layout/list_preference"
|
||||||
app:title="@string/preferences_client_package"
|
app:title="@string/preferences_client_package"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:isPreferenceVisible="false"/>
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
app:key="server_package"
|
app:key="server_package"
|
||||||
app:layout="@layout/list_preference"
|
app:layout="@layout/list_preference"
|
||||||
app:title="@string/preferences_server_package"
|
app:title="@string/preferences_server_package"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:isPreferenceVisible="false"/>
|
app:isPreferenceVisible="false" />
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") version "8.5.0" apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. For more details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
# 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
|
# Android operating system, and which are packaged with your app's APK
|
||||||
@@ -22,5 +22,6 @@ kotlin.code.style=official
|
|||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
|
|
||||||
|
|
||||||
# Enable verbose output for CMake
|
# Enable verbose output for CMake
|
||||||
android.native.buildOutput=verbose
|
android.native.buildOutput=verbose
|
||||||
|
|||||||
23
android/gradle/libs.versions.toml
Normal file
23
android/gradle/libs.versions.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[versions]
|
||||||
|
acraHttp = "5.12.0"
|
||||||
|
agp = "8.11.1"
|
||||||
|
appcompat = "1.7.1"
|
||||||
|
kotlin = "2.2.0"
|
||||||
|
material = "1.12.0"
|
||||||
|
navigationRuntimeKtx = "2.9.1"
|
||||||
|
preferenceKtx = "1.2.1"
|
||||||
|
swiperefreshlayout = "1.1.0"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
acra-http = { module = "ch.acra:acra-http", version.ref = "acraHttp" }
|
||||||
|
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
||||||
|
material = { module = "com.google.android.material:material", version.ref = "material" }
|
||||||
|
navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigationRuntimeKtx" }
|
||||||
|
navigation-runtime-ktx = { module = "androidx.navigation:navigation-runtime-ktx", version.ref = "navigationRuntimeKtx" }
|
||||||
|
navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigationRuntimeKtx" }
|
||||||
|
preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" }
|
||||||
|
swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#Fri Sep 22 13:23:17 EEST 2023
|
#Tue Jul 01 19:51:06 EEST 2025
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,282 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"name": "Counter-Strike",
|
|
||||||
"app_id": 10,
|
|
||||||
"gamedir": "cstrike",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Base Goldsrc Shared Content",
|
|
||||||
"depot_id": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Counter-Strike Base Content",
|
|
||||||
"depot_id": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Condition Zero Models",
|
|
||||||
"depot_id": 95
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"package_name": "in.celest.xash3d.cs16client"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life",
|
|
||||||
"app_id": 70,
|
|
||||||
"gamedir": "valve",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Base Goldsrc Shared Content",
|
|
||||||
"depot_id": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life Base Content",
|
|
||||||
"depot_id": 71
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life High Definition",
|
|
||||||
"depot_id": 96
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"package_name": "su.xash.engine"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life: Opposing Force",
|
|
||||||
"app_id": 50,
|
|
||||||
"gamedir": "gearbox",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Base Goldsrc Shared Content",
|
|
||||||
"depot_id": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Opposing Force Base Content",
|
|
||||||
"depot_id": 51
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life High Definition",
|
|
||||||
"depot_id": 96
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Counter-Strike: Condition Zero",
|
|
||||||
"app_id": 80,
|
|
||||||
"gamedir": "czero",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Base Goldsrc Shared Content",
|
|
||||||
"depot_id": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Counter-Strike Base Content",
|
|
||||||
"depot_id": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Condition Zero Base Contentt",
|
|
||||||
"depot_id": 81
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life: Blue Shift",
|
|
||||||
"app_id": 130,
|
|
||||||
"gamedir": "bshift",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Base Goldsrc Shared Content",
|
|
||||||
"depot_id": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life: Blue Shift",
|
|
||||||
"depot_id": 130
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life High Definition",
|
|
||||||
"depot_id": 96
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life: C.A.G.E.D.",
|
|
||||||
"app_id": 679990,
|
|
||||||
"gamedir": "caged_fgs",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Half-Life: Caged Content",
|
|
||||||
"depot_id": 679991
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life: C.A.G.E.D. Pathnodes",
|
|
||||||
"depot_id": 679992
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life: C.A.G.E.D. HOLIDAY NONE",
|
|
||||||
"depot_id": 679996
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Day of Defeat",
|
|
||||||
"app_id": 30,
|
|
||||||
"gamedir": "dod",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Base Goldsrc Shared Content",
|
|
||||||
"depot_id": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Day of Defeat Base Content",
|
|
||||||
"depot_id": 31
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Cry of Fear",
|
|
||||||
"app_id": 223710,
|
|
||||||
"gamedir": "cryoffear",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Cry of Fear Content",
|
|
||||||
"depot_id": 223711
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Headcrab Frenzy!",
|
|
||||||
"app_id": 354900,
|
|
||||||
"gamedir": "hcfrenzy",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Headcrab Frenzy Common Content",
|
|
||||||
"depot_id": 354901
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Team Fortress Classic",
|
|
||||||
"app_id": 20,
|
|
||||||
"gamedir": "tfc",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Base Goldsrc Shared Content",
|
|
||||||
"depot_id": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Team Fortress Classic Base Content",
|
|
||||||
"depot_id": 21
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life High Definition",
|
|
||||||
"depot_id": 96
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Base Defense",
|
|
||||||
"app_id": 632730,
|
|
||||||
"gamedir": "bdef",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Base Defense Content",
|
|
||||||
"depot_id": 632731
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Ricochet",
|
|
||||||
"app_id": 60,
|
|
||||||
"gamedir": "ricochet",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Base Goldsrc Shared Content",
|
|
||||||
"depot_id": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Ricochet Base Content",
|
|
||||||
"depot_id": 61
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Deathmatch Classic",
|
|
||||||
"app_id": 40,
|
|
||||||
"gamedir": "dmc",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Base Goldsrc Shared Content",
|
|
||||||
"depot_id": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Deathmatch Classic Base Content",
|
|
||||||
"depot_id": 41
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Halfquake Trilogy",
|
|
||||||
"app_id": 644320,
|
|
||||||
"gamedir": "hqtrilogy",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Halfquake Trilogy Content",
|
|
||||||
"depot_id": 644321
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Rats: Parasomnia",
|
|
||||||
"app_id": 638360,
|
|
||||||
"gamedir": "hrp",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Parasomnia - Content",
|
|
||||||
"depot_id": 638361
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Gold Source (Parasomnia) - Content",
|
|
||||||
"depot_id": 638364
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Counter-Strike: Condition Zero Deleted Scenes",
|
|
||||||
"app_id": 100,
|
|
||||||
"gamedir": "czeror",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Base Goldsrc Shared Content",
|
|
||||||
"depot_id": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Counter-Strike Base Content",
|
|
||||||
"depot_id": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Condition Zero Deleted Scenes Base Content",
|
|
||||||
"depot_id": 101
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hard-Life",
|
|
||||||
"app_id": 850870,
|
|
||||||
"gamedir": "hardlife",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Hard-Life Content",
|
|
||||||
"depot_id": 850871
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Half-Life Decay: Solo Mission DEMO",
|
|
||||||
"app_id": 1889470,
|
|
||||||
"gamedir": "decaysolodemo",
|
|
||||||
"depots": [
|
|
||||||
{
|
|
||||||
"name": "Half-Life Decay: Solo Mission Demo Content",
|
|
||||||
"depot_id": 1889471
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,16 +1,22 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google {
|
||||||
mavenCentral()
|
content {
|
||||||
gradlePluginPortal()
|
includeGroupByRegex("com\\.android.*")
|
||||||
}
|
includeGroupByRegex("com\\.google.*")
|
||||||
|
includeGroupByRegex("androidx.*")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "Xash3D FWGS"
|
rootProject.name = "Xash3D FWGS"
|
||||||
|
|||||||
@@ -168,11 +168,11 @@ Default build-depended cvar and constant values
|
|||||||
#define DEFAULT_M_IGNORE "1"
|
#define DEFAULT_M_IGNORE "1"
|
||||||
#endif // !XASH_MOBILE_PLATFORM && !XASH_NSWITCH
|
#endif // !XASH_MOBILE_PLATFORM && !XASH_NSWITCH
|
||||||
|
|
||||||
#if (XASH_ANDROID && !XASH_TERMUX) || XASH_IOS
|
#if XASH_IOS
|
||||||
// this means that libraries are provided with engine, but not in game data
|
// this means that libraries are provided with engine, but not in game data
|
||||||
// You need add library loading code to library.c when adding new platform
|
// You need add library loading code to library.c when adding new platform
|
||||||
#define XASH_INTERNAL_GAMELIBS
|
#define XASH_INTERNAL_GAMELIBS
|
||||||
#endif // XASH_ANDROID || XASH_IOS || XASH_EMSCRIPTEN
|
#endif // XASH_IOS
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
#ifndef DEFAULT_TOUCH_ENABLE
|
#ifndef DEFAULT_TOUCH_ENABLE
|
||||||
|
|||||||
@@ -226,32 +226,29 @@ void COM_GetCommonLibraryPath( ECommonLibraryType eLibType, char *out, size_t si
|
|||||||
case LIBRARY_GAMEUI:
|
case LIBRARY_GAMEUI:
|
||||||
if( COM_CheckStringEmpty( host.menulib ))
|
if( COM_CheckStringEmpty( host.menulib ))
|
||||||
{
|
{
|
||||||
Q_strncpy( out, host.menulib, size );
|
if( host.menulib[0] == '@' )
|
||||||
}
|
COM_GenerateClientLibraryPath( host.menulib + 1, out, size );
|
||||||
else
|
else Q_strncpy( out, host.menulib, size );
|
||||||
{
|
|
||||||
COM_GenerateClientLibraryPath( "menu", out, size );
|
|
||||||
}
|
}
|
||||||
|
else COM_GenerateClientLibraryPath( "menu", out, size );
|
||||||
break;
|
break;
|
||||||
case LIBRARY_CLIENT:
|
case LIBRARY_CLIENT:
|
||||||
if( COM_CheckStringEmpty( host.clientlib ))
|
if( COM_CheckStringEmpty( host.clientlib ))
|
||||||
{
|
{
|
||||||
Q_strncpy( out, host.clientlib, size );
|
if( host.clientlib[0] == '@' )
|
||||||
}
|
COM_GenerateClientLibraryPath( host.clientlib + 1, out, size );
|
||||||
else
|
else Q_strncpy( out, host.clientlib, size );
|
||||||
{
|
|
||||||
COM_GenerateClientLibraryPath( "client", out, size );
|
|
||||||
}
|
}
|
||||||
|
else COM_GenerateClientLibraryPath( "client", out, size );
|
||||||
break;
|
break;
|
||||||
case LIBRARY_SERVER:
|
case LIBRARY_SERVER:
|
||||||
if( COM_CheckStringEmpty( host.gamedll ))
|
if( COM_CheckStringEmpty( host.gamedll ))
|
||||||
{
|
{
|
||||||
Q_strncpy( out, host.gamedll, size );
|
if( host.gamedll[0] == '@' )
|
||||||
}
|
COM_GenerateClientLibraryPath( host.gamedll + 1, out, size );
|
||||||
else
|
else Q_strncpy( out, host.gamedll, size );
|
||||||
{
|
|
||||||
COM_GenerateServerLibraryPath( out, size );
|
|
||||||
}
|
}
|
||||||
|
else COM_GenerateServerLibraryPath( out, size );
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ASSERT( 0 );
|
ASSERT( 0 );
|
||||||
|
|||||||
@@ -19,44 +19,37 @@ GNU General Public License for more details.
|
|||||||
#include "platform/android/lib_android.h"
|
#include "platform/android/lib_android.h"
|
||||||
#include "platform/android/dlsym-weak.h" // Android < 5.0
|
#include "platform/android/dlsym-weak.h" // Android < 5.0
|
||||||
|
|
||||||
void *ANDROID_LoadLibrary( const char *dllname )
|
void *ANDROID_LoadLibrary( const char *path )
|
||||||
{
|
{
|
||||||
char path[MAX_SYSPATH];
|
const char *libdir[2], *name = COM_FileWithoutPath( path );
|
||||||
const char *libdir[2];
|
char fullpath[MAX_SYSPATH];
|
||||||
int i;
|
void *handle;
|
||||||
void *pHandle = NULL;
|
|
||||||
|
|
||||||
libdir[0] = getenv("XASH3D_GAMELIBDIR");
|
libdir[0] = getenv( "XASH3D_GAMELIBDIR" ); // TODO: remove this once distributing games from APKs will be deprecated
|
||||||
libdir[1] = getenv("XASH3D_ENGLIBDIR");
|
libdir[1] = NULL; // TODO: put here data directory where libraries will be downloaded to
|
||||||
|
|
||||||
for( i = 0; i < 2; i++ )
|
for( int i = 0; i < ARRAYSIZE( libdir ); i++ )
|
||||||
{
|
{
|
||||||
|
// this is an APK directory, get base path
|
||||||
|
const char *p = i == 0 ? name : path;
|
||||||
|
|
||||||
if( !libdir[i] )
|
if( !libdir[i] )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Q_snprintf( path, MAX_SYSPATH, "%s/lib%s."OS_LIB_EXT, libdir[i], dllname );
|
Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", libdir[i], p );
|
||||||
pHandle = dlopen( path, RTLD_NOW );
|
|
||||||
if( pHandle )
|
handle = dlopen( fullpath, RTLD_NOW );
|
||||||
return pHandle;
|
|
||||||
|
if( handle )
|
||||||
|
return handle;
|
||||||
|
|
||||||
COM_PushLibraryError( dlerror() );
|
COM_PushLibraryError( dlerror() );
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACKHACK: keep old behaviour for compatibility
|
// find in system search path, that includes our APK
|
||||||
if( Q_strstr( dllname, "." OS_LIB_EXT ) || Q_strstr( dllname, "/" ))
|
handle = dlopen( name, RTLD_NOW );
|
||||||
{
|
if( handle )
|
||||||
pHandle = dlopen( dllname, RTLD_NOW );
|
return handle;
|
||||||
if( pHandle )
|
|
||||||
return pHandle;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Q_snprintf( path, MAX_SYSPATH, "lib%s."OS_LIB_EXT, dllname );
|
|
||||||
pHandle = dlopen( path, RTLD_NOW );
|
|
||||||
if( pHandle )
|
|
||||||
return pHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
COM_PushLibraryError( dlerror() );
|
COM_PushLibraryError( dlerror() );
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ def configure(conf):
|
|||||||
if conf.env.DEST_OS == 'android':
|
if conf.env.DEST_OS == 'android':
|
||||||
conf.check_cc(lib='android')
|
conf.check_cc(lib='android')
|
||||||
|
|
||||||
# remove lib prefix for other systems than Android
|
# remove lib prefix
|
||||||
if conf.env.DEST_OS != 'android' or conf.env.TERMUX:
|
if conf.env.cxxshlib_PATTERN.startswith('lib'):
|
||||||
if conf.env.cxxshlib_PATTERN.startswith('lib'):
|
conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:]
|
||||||
conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:]
|
|
||||||
|
|
||||||
if conf.check_cc(fragment=MEMFD_CREATE_TEST, msg='Checking for memfd_create', mandatory=False):
|
if conf.check_cc(fragment=MEMFD_CREATE_TEST, msg='Checking for memfd_create', mandatory=False):
|
||||||
conf.define('HAVE_MEMFD_CREATE', 1)
|
conf.define('HAVE_MEMFD_CREATE', 1)
|
||||||
|
|||||||
81
scripts/build-ninja.py
Normal file
81
scripts/build-ninja.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# encoding: utf-8
|
||||||
|
# Copyright (C) 2025 Velaron
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmake(bin_path, libs, inst_path):
|
||||||
|
cmake_exec = ["cmake", "--build", bin_path]
|
||||||
|
cmake_process = subprocess.Popen(cmake_exec)
|
||||||
|
cmake_process.communicate()
|
||||||
|
|
||||||
|
if libs:
|
||||||
|
for lib in libs:
|
||||||
|
src = os.path.join(bin_path, *lib.split("/"))
|
||||||
|
dest = os.path.join(inst_path, lib.split("/")[-1])
|
||||||
|
|
||||||
|
dest_dir = os.path.dirname(dest)
|
||||||
|
|
||||||
|
if not os.path.exists(dest_dir):
|
||||||
|
os.makedirs(dest_dir)
|
||||||
|
|
||||||
|
shutil.copyfile(src, dest)
|
||||||
|
else:
|
||||||
|
cmake_exec = ["cmake", "--install", bin_path, "--prefix", inst_path]
|
||||||
|
cmake_process = subprocess.Popen(cmake_exec)
|
||||||
|
cmake_process.communicate()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("cmd")
|
||||||
|
parser.add_argument("top_dir")
|
||||||
|
parser.add_argument("out_dir")
|
||||||
|
parser.add_argument("waflock")
|
||||||
|
parser.add_argument("--targets", type=str, default="")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
waf_path = os.path.join(args.top_dir, "waf")
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["WAFLOCK"] = args.waflock
|
||||||
|
|
||||||
|
waf_exec = [sys.executable, waf_path, args.cmd, "-t", args.top_dir]
|
||||||
|
|
||||||
|
if args.targets:
|
||||||
|
waf_exec += ["--targets={}".format(args.targets)]
|
||||||
|
else:
|
||||||
|
# build SDL2 and hlsdk-portable with cmake
|
||||||
|
sdl_bin_path = os.path.join(args.out_dir, "SDL")
|
||||||
|
hlsdk_bin_path = os.path.join(args.out_dir, "hlsdk-portable")
|
||||||
|
|
||||||
|
abi = args.waflock.replace(".lock-waf_android_", "").replace("_build", "")
|
||||||
|
inst_path = os.path.join(args.top_dir, "android", "app", "src", "main", "jniLibs", abi)
|
||||||
|
|
||||||
|
if not os.path.exists(inst_path):
|
||||||
|
os.makedirs(inst_path)
|
||||||
|
|
||||||
|
run_cmake(sdl_bin_path, ["libSDL2.so"], inst_path)
|
||||||
|
run_cmake(hlsdk_bin_path, None, inst_path)
|
||||||
|
|
||||||
|
process = subprocess.Popen(waf_exec, env=env)
|
||||||
|
process.communicate()
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
108
scripts/configure-ninja.py
Executable file
108
scripts/configure-ninja.py
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# encoding: utf-8
|
||||||
|
# Copyright (C) 2025 Velaron
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import io
|
||||||
|
|
||||||
|
|
||||||
|
def check_repo(name, branch, url, path):
|
||||||
|
if not os.path.exists(path):
|
||||||
|
print("{} not found. Cloning...".format(name))
|
||||||
|
git_exec = ["git", "clone", "--branch", branch, url, path]
|
||||||
|
git_process = subprocess.Popen(git_exec)
|
||||||
|
git_process.communicate()
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmake(root, out, toolchain, abi, build_type, ndk_root, min_sdk, *args):
|
||||||
|
cmake_exec = ["cmake", "-H{}".format(root), "-DCMAKE_BUILD_TYPE={}".format(build_type),
|
||||||
|
"-DCMAKE_TOOLCHAIN_FILE={}".format(toolchain), "-DANDROID_ABI={}".format(abi),
|
||||||
|
"-DANDROID_NDK={}".format(ndk_root),
|
||||||
|
"-DANDROID_PLATFORM=android-{}".format(min_sdk),
|
||||||
|
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
|
||||||
|
"-DCMAKE_SYSTEM_NAME=Android", "-DCMAKE_SYSTEM_VERSION={}".format(min_sdk),
|
||||||
|
"-B{}".format(out), "-GNinja"]
|
||||||
|
|
||||||
|
cmake_exec.extend(args)
|
||||||
|
cmake_process = subprocess.Popen(cmake_exec)
|
||||||
|
cmake_process.communicate()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("wscript_path")
|
||||||
|
parser.add_argument("--variant")
|
||||||
|
parser.add_argument("--abi")
|
||||||
|
parser.add_argument("--configuration-dir")
|
||||||
|
parser.add_argument("--ndk-version")
|
||||||
|
parser.add_argument("--min-sdk-version")
|
||||||
|
parser.add_argument("--ndk-root")
|
||||||
|
|
||||||
|
args, unknown = parser.parse_known_args()
|
||||||
|
|
||||||
|
abi = args.abi
|
||||||
|
|
||||||
|
cmake_build_type = "Debug" if args.variant in ["debug", "asan"] else "Release"
|
||||||
|
cmake_toolchain_path = os.path.join(args.ndk_root, "build", "cmake", "android.toolchain.cmake")
|
||||||
|
|
||||||
|
# configure SDL2
|
||||||
|
sdl_path = os.path.join(args.wscript_path, "3rdparty", "SDL")
|
||||||
|
check_repo("SDL", "release-2.32.8", "https://github.com/libsdl-org/SDL", sdl_path)
|
||||||
|
|
||||||
|
sdl_out_path = os.path.join(args.configuration_dir, "SDL")
|
||||||
|
|
||||||
|
run_cmake(sdl_path, sdl_out_path, cmake_toolchain_path, abi, cmake_build_type, args.ndk_root, args.min_sdk_version,
|
||||||
|
"-DSDL_RENDER=OFF", "-DSDL_POWER=OFF", "-DSDL_VULKAN=OFF", "-DSDL_DISKAUDIO=OFF",
|
||||||
|
"-DSDL_DUMMYAUDIO=OFF", "-DSDL_DUMMYVIDEO=OFF",
|
||||||
|
"-DSDL_VULKAN=OFF", "-DSDL_OFFSCREEN=OFF", "-DSDL_STATIC=OFF")
|
||||||
|
|
||||||
|
# configure hlsdk-portable
|
||||||
|
hlsdk_path = os.path.join(args.wscript_path, "3rdparty", "hlsdk-portable")
|
||||||
|
check_repo("hlsdk-portable", "mobile_hacks", "https://github.com/FWGS/hlsdk-portable", hlsdk_path)
|
||||||
|
|
||||||
|
hlsdk_out_path = os.path.join(args.configuration_dir, "hlsdk-portable")
|
||||||
|
|
||||||
|
run_cmake(hlsdk_path, hlsdk_out_path, cmake_toolchain_path, abi, cmake_build_type, args.ndk_root,
|
||||||
|
args.min_sdk_version, "-DANDROID_APK=ON")
|
||||||
|
|
||||||
|
# waf configure
|
||||||
|
waf_path = os.path.join(args.wscript_path, "waf")
|
||||||
|
out_path = os.path.join(args.configuration_dir, "xash3d-fwgs")
|
||||||
|
|
||||||
|
waf_build_type = "debug" if args.variant in ["debug", "asan"] else "release"
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["WAFLOCK"] = ".lock-waf_android_{}_build".format(abi)
|
||||||
|
env["ANDROID_NDK"] = args.ndk_root
|
||||||
|
env["BUILD_CMAKE_LIBRARY_OUTPUT_DIRECTORY"] = sdl_out_path
|
||||||
|
|
||||||
|
waf_exec = [sys.executable, waf_path, "configure", "-t", args.wscript_path, "-o", out_path,
|
||||||
|
"-T", waf_build_type, "--android={},,{}".format(abi, args.min_sdk_version), "-s",
|
||||||
|
sdl_path, "--skip-sdl2-sanity-check", "--enable-bundled-deps", "ninja"]
|
||||||
|
|
||||||
|
process = subprocess.Popen(waf_exec, env=env)
|
||||||
|
process.communicate()
|
||||||
|
|
||||||
|
with io.open(os.path.join(args.configuration_dir, "build.ninja.txt"), "w", encoding="utf-8") as f:
|
||||||
|
f.write(os.path.join(out_path, "build.ninja"))
|
||||||
|
|
||||||
|
# required for Android Studio
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
unset ANDROID_SDK_ROOT
|
unset ANDROID_SDK_ROOT
|
||||||
export JAVA_HOME=$GITHUB_WORKSPACE/jdk-17.0.7+7
|
export JAVA_HOME=$GITHUB_WORKSPACE/jdk-17.0.15+6
|
||||||
export ANDROID_HOME=$GITHUB_WORKSPACE/sdk
|
export ANDROID_HOME=$GITHUB_WORKSPACE/sdk
|
||||||
export PATH=$PATH:$JAVA_HOME/bin:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/tools/bin
|
export PATH=$PATH:$JAVA_HOME/bin:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/tools/bin
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ pushd android
|
|||||||
|
|
||||||
pushd app/build/outputs/apk/continuous
|
pushd app/build/outputs/apk/continuous
|
||||||
|
|
||||||
$ANDROID_HOME/build-tools/34.0.0/apksigner sign \
|
$ANDROID_HOME/build-tools/36.0.0/apksigner sign \
|
||||||
--ks $GITHUB_WORKSPACE/android/debug.keystore \
|
--ks $GITHUB_WORKSPACE/android/debug.keystore \
|
||||||
--ks-key-alias androiddebugkey \
|
--ks-key-alias androiddebugkey \
|
||||||
--ks-pass pass:android \
|
--ks-pass pass:android \
|
||||||
|
|||||||
@@ -2,17 +2,18 @@
|
|||||||
|
|
||||||
cd $GITHUB_WORKSPACE
|
cd $GITHUB_WORKSPACE
|
||||||
|
|
||||||
ANDROID_COMMANDLINE_TOOLS_VER="11076708"
|
ANDROID_COMMANDLINE_TOOLS_VER="13114758"
|
||||||
ANDROID_BUILD_TOOLS_VER="34.0.0"
|
ANDROID_BUILD_TOOLS_VER="36.0.0"
|
||||||
ANDROID_PLATFORM_VER="android-34"
|
ANDROID_PLATFORM_VER="android-35"
|
||||||
|
ANDROID_NDK_VERSION="28.2.13676358"
|
||||||
|
|
||||||
echo "Download JDK 17"
|
echo "Download JDK 17"
|
||||||
wget https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.7%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.7_7.tar.gz -qO- | tar -xzf - || exit 1
|
wget https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_linux_hotspot_17.0.15_6.tar.gz -qO- | tar -xzf - || exit 1
|
||||||
export JAVA_HOME=$GITHUB_WORKSPACE/jdk-17.0.7+7
|
export JAVA_HOME=$GITHUB_WORKSPACE/jdk-17.0.15+6
|
||||||
export PATH=$PATH:$JAVA_HOME/bin
|
export PATH=$PATH:$JAVA_HOME/bin
|
||||||
|
|
||||||
echo "Download hlsdk-portable"
|
echo "Download hlsdk-portable"
|
||||||
git clone --depth 1 --recursive https://github.com/FWGS/hlsdk-portable -b mobile_hacks 3rdparty/hlsdk-portable || exit 1
|
git clone --depth 1 --recursive https://github.com/FWGS/hlsdk-portable -b android_library_naming 3rdparty/hlsdk-portable || exit 1
|
||||||
|
|
||||||
echo "Download SDL"
|
echo "Download SDL"
|
||||||
pushd 3rdparty
|
pushd 3rdparty
|
||||||
@@ -35,4 +36,4 @@ popd
|
|||||||
|
|
||||||
echo "Download all needed tools and Android NDK"
|
echo "Download all needed tools and Android NDK"
|
||||||
yes | sdkmanager --licenses > /dev/null 2>/dev/null # who even reads licenses? :)
|
yes | sdkmanager --licenses > /dev/null 2>/dev/null # who even reads licenses? :)
|
||||||
sdkmanager --install build-tools\;${ANDROID_BUILD_TOOLS_VER} platform-tools platforms\;${ANDROID_PLATFORM_VER}
|
sdkmanager --install build-tools\;${ANDROID_BUILD_TOOLS_VER} platform-tools platforms\;${ANDROID_PLATFORM_VER} ndk\;${ANDROID_NDK_VERSION}
|
||||||
|
|||||||
340
scripts/waifulib/ninja.py
Normal file
340
scripts/waifulib/ninja.py
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# encoding: utf-8
|
||||||
|
# Copyright (C) 2025 Velaron
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import abc
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import List, Type, Union
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
from waflib import Logs, Task, Build, Options, Node
|
||||||
|
from ninja_syntax import Writer, escape_path
|
||||||
|
|
||||||
|
try:
|
||||||
|
ABC = abc.ABC
|
||||||
|
except AttributeError:
|
||||||
|
class ABC(object):
|
||||||
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
|
Task.Task.keep_last_cmd = True
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_path(node): # type: (Union[Node, str]) -> str
|
||||||
|
if isinstance(node, str):
|
||||||
|
return escape_path(node)
|
||||||
|
else:
|
||||||
|
return escape_path(node.abspath())
|
||||||
|
|
||||||
|
|
||||||
|
class TaskAdapter(ABC):
|
||||||
|
task_type = None # type: str
|
||||||
|
|
||||||
|
def __init__(self, task): # type: (Task) -> None
|
||||||
|
if not task.inputs:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
if not task.outputs:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
self.task = task # type: Task
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def write_rule(cls, writer): # type: (Writer) -> None
|
||||||
|
writer.rule(
|
||||||
|
name=cls.task_type,
|
||||||
|
command="$cmd",
|
||||||
|
description="Building {} object $out".format(cls.task_type)
|
||||||
|
)
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def write_build(self, writer): # type: (Writer) -> None
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write_target(self, writer): # type: (Writer) -> None
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_inputs(self): # type: () -> List[str]
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_outputs(self): # type: () -> List[str]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class CAdapter(TaskAdapter):
|
||||||
|
task_type = "c" # type: str
|
||||||
|
|
||||||
|
def write_build(self, writer): # type: (Writer) -> None
|
||||||
|
output_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode)
|
||||||
|
input_files = []
|
||||||
|
|
||||||
|
cmd = " ".join(self.task.last_cmd)
|
||||||
|
|
||||||
|
for node in self.task.inputs:
|
||||||
|
cmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath())
|
||||||
|
input_files.append(node.abspath())
|
||||||
|
|
||||||
|
cmd = cmd.replace(self.task.outputs[0].abspath(), output_file)
|
||||||
|
|
||||||
|
for inc in self.task.env.INCPATHS:
|
||||||
|
cwd = self.task.get_cwd().abspath()
|
||||||
|
path = os.path.normpath(os.path.join(cwd, inc))
|
||||||
|
cmd = cmd.replace(self.task.env.CPPPATH_ST % inc,
|
||||||
|
self.task.env.CPPPATH_ST % path)
|
||||||
|
|
||||||
|
writer.build(
|
||||||
|
rule=self.task_type,
|
||||||
|
outputs=output_file,
|
||||||
|
inputs=input_files,
|
||||||
|
variables={
|
||||||
|
"cmd": cmd,
|
||||||
|
})
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
|
||||||
|
class CxxAdapter(CAdapter):
|
||||||
|
task_type = "cxx" # type: str
|
||||||
|
|
||||||
|
|
||||||
|
class CStLibAdapter(TaskAdapter):
|
||||||
|
task_type = "cstlib" # type: str
|
||||||
|
|
||||||
|
def write_build(self, writer): # type: (Writer) -> None
|
||||||
|
output_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode)
|
||||||
|
input_files = []
|
||||||
|
|
||||||
|
cmd = " ".join(self.task.last_cmd)
|
||||||
|
|
||||||
|
for node in self.task.inputs:
|
||||||
|
cmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath())
|
||||||
|
input_files.append(node.abspath())
|
||||||
|
|
||||||
|
cmd = cmd.replace(self.task.outputs[0].abspath(), output_file)
|
||||||
|
|
||||||
|
writer.build(
|
||||||
|
rule=self.task_type,
|
||||||
|
outputs=output_file,
|
||||||
|
inputs=input_files,
|
||||||
|
variables={
|
||||||
|
"cmd": cmd,
|
||||||
|
})
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
def write_target(self, writer): # type: (Writer) -> None
|
||||||
|
writer.build(
|
||||||
|
rule="waf_build",
|
||||||
|
outputs="{}.passthrough".format(self.get_outputs()[0]),
|
||||||
|
inputs=self.get_inputs(),
|
||||||
|
variables={
|
||||||
|
"tgt": self.task.generator.name
|
||||||
|
})
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
def get_inputs(self): # type: () -> List[str]
|
||||||
|
return [node.abspath() for node in self.task.generator.source]
|
||||||
|
|
||||||
|
def get_outputs(self): # type: () -> List[str]
|
||||||
|
return [self.task.outputs[0].path_from(self.task.generator.bld.bldnode)]
|
||||||
|
|
||||||
|
|
||||||
|
class CShLibAdapter(CStLibAdapter):
|
||||||
|
task_type = "cshlib" # type: str
|
||||||
|
|
||||||
|
def write_build(self, writer): # type: (Writer) -> None
|
||||||
|
output_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode)
|
||||||
|
input_files = []
|
||||||
|
|
||||||
|
cmd = " ".join(self.task.last_cmd)
|
||||||
|
|
||||||
|
for node in self.task.inputs:
|
||||||
|
cmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath())
|
||||||
|
input_files.append(node.abspath())
|
||||||
|
|
||||||
|
cmd = cmd.replace(self.task.outputs[0].abspath(), output_file)
|
||||||
|
|
||||||
|
for lib in self.task.env.STLIBPATH:
|
||||||
|
cwd = self.task.get_cwd().abspath()
|
||||||
|
path = os.path.normpath(os.path.join(cwd, lib))
|
||||||
|
cmd = cmd.replace(self.task.env.STLIBPATH_ST % lib,
|
||||||
|
self.task.env.STLIBPATH_ST % path)
|
||||||
|
|
||||||
|
for lib in self.task.env.LIBPATH:
|
||||||
|
cwd = self.task.get_cwd().abspath()
|
||||||
|
path = os.path.normpath(os.path.join(cwd, lib))
|
||||||
|
cmd = cmd.replace(self.task.env.LIBPATH_ST % lib,
|
||||||
|
self.task.env.LIBPATH_ST % path)
|
||||||
|
|
||||||
|
writer.build(
|
||||||
|
rule=self.task_type,
|
||||||
|
outputs=output_file,
|
||||||
|
inputs=input_files,
|
||||||
|
variables={
|
||||||
|
"cmd": cmd,
|
||||||
|
})
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
|
||||||
|
class CxxStLibAdapter(CStLibAdapter):
|
||||||
|
task_type = "cxxstlib" # type: str
|
||||||
|
|
||||||
|
|
||||||
|
class CxxShLibAdapter(CShLibAdapter):
|
||||||
|
task_type = "cxxshlib" # type: str
|
||||||
|
|
||||||
|
|
||||||
|
def get_subclasses(cls):
|
||||||
|
subs = set()
|
||||||
|
for sub in cls.__subclasses__():
|
||||||
|
subs.add(sub)
|
||||||
|
subs.update(get_subclasses(sub))
|
||||||
|
return list(subs)
|
||||||
|
|
||||||
|
|
||||||
|
Adapters = get_subclasses(TaskAdapter) # type: List[Type[TaskAdapter]]
|
||||||
|
|
||||||
|
|
||||||
|
class NinjaContext(Build.BuildContext):
|
||||||
|
cmd = "ninja"
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
self.restore()
|
||||||
|
|
||||||
|
tasks = [] # type: List[TaskAdapter]
|
||||||
|
|
||||||
|
if not self.all_envs:
|
||||||
|
self.load_envs()
|
||||||
|
|
||||||
|
self.recurse([self.run_dir])
|
||||||
|
self.pre_build()
|
||||||
|
|
||||||
|
def exec_command(self, *k, **kw):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
for group in self.groups:
|
||||||
|
for task_gen in group:
|
||||||
|
try:
|
||||||
|
if hasattr(task_gen, "post"):
|
||||||
|
task_gen.post()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if isinstance(task_gen, Task.Task):
|
||||||
|
current_tasks = [task_gen]
|
||||||
|
else:
|
||||||
|
current_tasks = task_gen.tasks
|
||||||
|
|
||||||
|
for task in current_tasks:
|
||||||
|
try:
|
||||||
|
adapter = next(a for a in Adapters if a.task_type == task.__class__.__name__)
|
||||||
|
except StopIteration:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if adapter:
|
||||||
|
try:
|
||||||
|
tasks.append(adapter(task))
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
task.nocache = True
|
||||||
|
|
||||||
|
old_exec = task.exec_command
|
||||||
|
task.exec_command = exec_command
|
||||||
|
try:
|
||||||
|
task.run()
|
||||||
|
except Exception as e:
|
||||||
|
Logs.error("Error running task {}: {}".format(task, e))
|
||||||
|
finally:
|
||||||
|
task.exec_command = old_exec
|
||||||
|
|
||||||
|
ninja_file_node = self.bldnode.make_node("build.ninja")
|
||||||
|
|
||||||
|
Logs.info("Ninja build commands will be stored in %s", ninja_file_node.abspath())
|
||||||
|
|
||||||
|
string_buffer = StringIO()
|
||||||
|
writer = Writer(string_buffer)
|
||||||
|
|
||||||
|
writer.variable(key="ninja_required_version", value="1.5")
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
for a in Adapters:
|
||||||
|
a.write_rule(writer)
|
||||||
|
|
||||||
|
writer.rule(
|
||||||
|
"waf_build",
|
||||||
|
command="python {} build {} {} {} --targets=$tgt".format(
|
||||||
|
os.path.join(self.top_dir, "scripts", "build-ninja.py"),
|
||||||
|
self.top_dir, os.path.dirname(self.out_dir), Options.lockfile)
|
||||||
|
)
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
writer.rule(
|
||||||
|
"waf_build_all",
|
||||||
|
command="python {} build {} {} {}".format(os.path.join(self.top_dir, "scripts", "build-ninja.py"),
|
||||||
|
self.top_dir, os.path.dirname(self.out_dir), Options.lockfile)
|
||||||
|
)
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
writer.rule(
|
||||||
|
"waf_clean",
|
||||||
|
command="python {} clean {} {} {}".format(os.path.join(self.top_dir, "scripts", "build-ninja.py"),
|
||||||
|
self.top_dir, os.path.dirname(self.out_dir), Options.lockfile)
|
||||||
|
)
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
for task in tasks:
|
||||||
|
task.write_build(writer)
|
||||||
|
|
||||||
|
for task in tasks:
|
||||||
|
task.write_target(writer)
|
||||||
|
|
||||||
|
outputs = [] # type: List[str]
|
||||||
|
for task in tasks:
|
||||||
|
outputs += task.get_outputs()
|
||||||
|
|
||||||
|
writer.build(
|
||||||
|
outputs="all",
|
||||||
|
rule="phony",
|
||||||
|
inputs=outputs
|
||||||
|
)
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
inputs = [] # type: List[str]
|
||||||
|
for task in tasks:
|
||||||
|
inputs += task.get_inputs()
|
||||||
|
|
||||||
|
writer.build(
|
||||||
|
outputs="all.passthrough",
|
||||||
|
rule="waf_build_all",
|
||||||
|
inputs=inputs
|
||||||
|
)
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
writer.build(outputs="clean", rule="waf_clean")
|
||||||
|
writer.newline()
|
||||||
|
|
||||||
|
writer.default("all")
|
||||||
|
|
||||||
|
file_content = string_buffer.getvalue()
|
||||||
|
|
||||||
|
with io.open(ninja_file_node.abspath(), "w", encoding="utf-8") as f:
|
||||||
|
f.write(file_content)
|
||||||
235
scripts/waifulib/ninja_syntax.py
Normal file
235
scripts/waifulib/ninja_syntax.py
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
# Copyright 2011 Google Inc. All Rights Reserved.
|
||||||
|
# Copyright 2025 Velaron (edited for Python 2.7 compatibility)
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Python module for generating .ninja files.
|
||||||
|
|
||||||
|
Note that this is emphatically not a required piece of Ninja; it's
|
||||||
|
just a helpful utility for build-file-generation systems that already
|
||||||
|
use Python.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import textwrap
|
||||||
|
from io import TextIOWrapper
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import Dict, List, Optional, Tuple, Union
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def escape_path(word): # type: (str) -> str
|
||||||
|
return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
|
||||||
|
|
||||||
|
|
||||||
|
class Writer(object):
|
||||||
|
def __init__(self, output, width=78): # type: (TextIOWrapper, int) -> None
|
||||||
|
self.output = output
|
||||||
|
self.width = width
|
||||||
|
|
||||||
|
def newline(self):
|
||||||
|
self.output.write('\n')
|
||||||
|
|
||||||
|
def comment(self, text): # type: (str) -> None
|
||||||
|
for line in textwrap.wrap(text, self.width - 2, break_long_words=False,
|
||||||
|
break_on_hyphens=False):
|
||||||
|
self.output.write('# ' + line + '\n')
|
||||||
|
|
||||||
|
def variable(
|
||||||
|
self,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
indent=0,
|
||||||
|
): # type: (str, Optional[Union[bool, int, float, str, List[str]]], int) -> None
|
||||||
|
if value is None:
|
||||||
|
return
|
||||||
|
if isinstance(value, list):
|
||||||
|
value = ' '.join(filter(None, value)) # Filter out empty strings.
|
||||||
|
self._line('%s = %s' % (key, value), indent)
|
||||||
|
|
||||||
|
def pool(self, name, depth): # type: (str, int) -> None
|
||||||
|
self._line('pool %s' % name)
|
||||||
|
self.variable('depth', depth, indent=1)
|
||||||
|
|
||||||
|
def rule(
|
||||||
|
self,
|
||||||
|
name,
|
||||||
|
command,
|
||||||
|
description=None,
|
||||||
|
depfile=None,
|
||||||
|
generator=False,
|
||||||
|
pool=None,
|
||||||
|
restat=False,
|
||||||
|
rspfile=None,
|
||||||
|
rspfile_content=None,
|
||||||
|
deps=None,
|
||||||
|
): # type: (str, str, Optional[str], Optional[str], bool, Optional[str], bool, Optional[str], Optional[str], Optional[Union[str, List[str]]]) -> None
|
||||||
|
self._line('rule %s' % name)
|
||||||
|
self.variable('command', command, indent=1)
|
||||||
|
if description:
|
||||||
|
self.variable('description', description, indent=1)
|
||||||
|
if depfile:
|
||||||
|
self.variable('depfile', depfile, indent=1)
|
||||||
|
if generator:
|
||||||
|
self.variable('generator', '1', indent=1)
|
||||||
|
if pool:
|
||||||
|
self.variable('pool', pool, indent=1)
|
||||||
|
if restat:
|
||||||
|
self.variable('restat', '1', indent=1)
|
||||||
|
if rspfile:
|
||||||
|
self.variable('rspfile', rspfile, indent=1)
|
||||||
|
if rspfile_content:
|
||||||
|
self.variable('rspfile_content', rspfile_content, indent=1)
|
||||||
|
if deps:
|
||||||
|
self.variable('deps', deps, indent=1)
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
outputs,
|
||||||
|
rule,
|
||||||
|
inputs=None,
|
||||||
|
implicit=None,
|
||||||
|
order_only=None,
|
||||||
|
variables=None,
|
||||||
|
implicit_outputs=None,
|
||||||
|
pool=None,
|
||||||
|
dyndep=None,
|
||||||
|
): # type: (Union[str, List[str]], str, Optional[Union[str, List[str]]], Optional[Union[str, List[str]]], Optional[Union[str, List[str]]], Optional[Union[List[Tuple[str, Optional[Union[str, List[str]]]]],Dict[str, Optional[Union[str, List[str]]]],]], Optional[Union[str, List[str]]], Optional[str], Optional[str]) -> List[str]
|
||||||
|
outputs = as_list(outputs)
|
||||||
|
out_outputs = [escape_path(x) for x in outputs]
|
||||||
|
all_inputs = [escape_path(x) for x in as_list(inputs)]
|
||||||
|
|
||||||
|
if implicit:
|
||||||
|
implicit = [escape_path(x) for x in as_list(implicit)]
|
||||||
|
all_inputs.append('|')
|
||||||
|
all_inputs.extend(implicit)
|
||||||
|
if order_only:
|
||||||
|
order_only = [escape_path(x) for x in as_list(order_only)]
|
||||||
|
all_inputs.append('||')
|
||||||
|
all_inputs.extend(order_only)
|
||||||
|
if implicit_outputs:
|
||||||
|
implicit_outputs = [escape_path(x)
|
||||||
|
for x in as_list(implicit_outputs)]
|
||||||
|
out_outputs.append('|')
|
||||||
|
out_outputs.extend(implicit_outputs)
|
||||||
|
|
||||||
|
self._line('build %s: %s' % (' '.join(out_outputs),
|
||||||
|
' '.join([rule] + all_inputs)))
|
||||||
|
if pool is not None:
|
||||||
|
self._line(' pool = %s' % pool)
|
||||||
|
if dyndep is not None:
|
||||||
|
self._line(' dyndep = %s' % dyndep)
|
||||||
|
|
||||||
|
if variables:
|
||||||
|
if isinstance(variables, dict):
|
||||||
|
iterator = iter(variables.items())
|
||||||
|
else:
|
||||||
|
iterator = iter(variables)
|
||||||
|
|
||||||
|
for key, val in iterator:
|
||||||
|
self.variable(key, val, indent=1)
|
||||||
|
|
||||||
|
return outputs
|
||||||
|
|
||||||
|
def include(self, path): # type: (str) -> None
|
||||||
|
self._line('include %s' % path)
|
||||||
|
|
||||||
|
def subninja(self, path): # type: (str) -> None
|
||||||
|
self._line('subninja %s' % path)
|
||||||
|
|
||||||
|
def default(self, paths): # type: (Union[str, List[str]]) -> None
|
||||||
|
self._line('default %s' % ' '.join(as_list(paths)))
|
||||||
|
|
||||||
|
def _count_dollars_before_index(self, s, i): # type: (str, int) -> int
|
||||||
|
"""Returns the number of '$' characters right in front of s[i]."""
|
||||||
|
dollar_count = 0
|
||||||
|
dollar_index = i - 1
|
||||||
|
while dollar_index > 0 and s[dollar_index] == '$':
|
||||||
|
dollar_count += 1
|
||||||
|
dollar_index -= 1
|
||||||
|
return dollar_count
|
||||||
|
|
||||||
|
def _line(self, text, indent=0): # type: (str, int) -> None
|
||||||
|
"""Write 'text' word-wrapped at self.width characters."""
|
||||||
|
leading_space = ' ' * indent
|
||||||
|
while len(leading_space) + len(text) > self.width:
|
||||||
|
# The text is too wide; wrap if possible.
|
||||||
|
|
||||||
|
# Find the rightmost space that would obey our width constraint and
|
||||||
|
# that's not an escaped space.
|
||||||
|
available_space = self.width - len(leading_space) - len(' $')
|
||||||
|
space = available_space
|
||||||
|
while True:
|
||||||
|
space = text.rfind(' ', 0, space)
|
||||||
|
if (space < 0 or
|
||||||
|
self._count_dollars_before_index(text, space) % 2 == 0):
|
||||||
|
break
|
||||||
|
|
||||||
|
if space < 0:
|
||||||
|
# No such space; just use the first unescaped space we can find.
|
||||||
|
space = available_space - 1
|
||||||
|
while True:
|
||||||
|
space = text.find(' ', space + 1)
|
||||||
|
if (space < 0 or
|
||||||
|
self._count_dollars_before_index(text, space) % 2 == 0):
|
||||||
|
break
|
||||||
|
if space < 0:
|
||||||
|
# Give up on breaking.
|
||||||
|
break
|
||||||
|
|
||||||
|
self.output.write(leading_space + text[0:space] + ' $\n')
|
||||||
|
text = text[space + 1:]
|
||||||
|
|
||||||
|
# Subsequent lines are continuations, so indent them.
|
||||||
|
leading_space = ' ' * (indent + 2)
|
||||||
|
|
||||||
|
self.output.write(leading_space + text + '\n')
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.output.close()
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(input): # type: (Optional[Union[str, List[str]]]) -> List[str]
|
||||||
|
if input is None:
|
||||||
|
return []
|
||||||
|
if isinstance(input, list):
|
||||||
|
return input
|
||||||
|
return [input]
|
||||||
|
|
||||||
|
|
||||||
|
def escape(string): # type: (str) -> str
|
||||||
|
"""Escape a string such that it can be embedded into a Ninja file without
|
||||||
|
further interpretation."""
|
||||||
|
assert '\n' not in string, 'Ninja syntax does not allow newlines'
|
||||||
|
# We only have one special metacharacter: '$'.
|
||||||
|
return string.replace('$', '$$')
|
||||||
|
|
||||||
|
|
||||||
|
def expand(string, vars, local_vars={}): # type: (str, Dict[str, str], Dict[str, str]) -> str
|
||||||
|
"""Expand a string containing $vars as Ninja would.
|
||||||
|
|
||||||
|
Note: doesn't handle the full Ninja variable syntax, but it's enough
|
||||||
|
to make configure.py's use of it work.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def exp(m): # type (Match[str]) -> str:
|
||||||
|
var = m.group(1)
|
||||||
|
if var == '$':
|
||||||
|
return '$'
|
||||||
|
return local_vars.get(var, vars.get(var, ''))
|
||||||
|
|
||||||
|
return re.sub(r'\$(\$|\w*)', exp, string)
|
||||||
@@ -25,6 +25,7 @@ ANDROID_NDK_HARDFP_MAX = 11 # latest version that supports hardfp
|
|||||||
ANDROID_NDK_GCC_MAX = 17 # latest NDK that ships with GCC
|
ANDROID_NDK_GCC_MAX = 17 # latest NDK that ships with GCC
|
||||||
ANDROID_NDK_UNIFIED_SYSROOT_MIN = 15
|
ANDROID_NDK_UNIFIED_SYSROOT_MIN = 15
|
||||||
ANDROID_NDK_SYSROOT_FLAG_MAX = 19 # latest NDK that need --sysroot flag
|
ANDROID_NDK_SYSROOT_FLAG_MAX = 19 # latest NDK that need --sysroot flag
|
||||||
|
ANDROID_NDK_BUGGED_LINKER_MAX = 22
|
||||||
ANDROID_NDK_API_MIN = {
|
ANDROID_NDK_API_MIN = {
|
||||||
10: 3,
|
10: 3,
|
||||||
19: 16,
|
19: 16,
|
||||||
@@ -354,6 +355,12 @@ class Android:
|
|||||||
else: linkflags += ['-no-canonical-prefixes']
|
else: linkflags += ['-no-canonical-prefixes']
|
||||||
|
|
||||||
linkflags += ['-Wl,--hash-style=sysv', '-Wl,--no-undefined']
|
linkflags += ['-Wl,--hash-style=sysv', '-Wl,--no-undefined']
|
||||||
|
|
||||||
|
linkflags += ["-Wl,-z,max-page-size=16384"]
|
||||||
|
|
||||||
|
if self.ndk_rev <= ANDROID_NDK_BUGGED_LINKER_MAX:
|
||||||
|
linkflags += ["-Wl,-z,common-page-size=16384"]
|
||||||
|
|
||||||
return linkflags
|
return linkflags
|
||||||
|
|
||||||
def ldflags(self):
|
def ldflags(self):
|
||||||
|
|||||||
4
wscript
4
wscript
@@ -128,7 +128,7 @@ REFDLLS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
def options(opt):
|
def options(opt):
|
||||||
opt.load('reconfigure compiler_optimizations xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msvs subproject')
|
opt.load('reconfigure compiler_optimizations xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msvs subproject ninja')
|
||||||
|
|
||||||
grp = opt.add_option_group('Common options')
|
grp = opt.add_option_group('Common options')
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@ def configure(conf):
|
|||||||
if conf.env.COMPILER_CC == 'msvc':
|
if conf.env.COMPILER_CC == 'msvc':
|
||||||
conf.load('msvc_pdb')
|
conf.load('msvc_pdb')
|
||||||
|
|
||||||
conf.load('msvs subproject clang_compilation_database strip_on_install waf_unit_test enforce_pic force_32bit')
|
conf.load('msvs subproject clang_compilation_database strip_on_install waf_unit_test enforce_pic force_32bit ninja')
|
||||||
|
|
||||||
conf.env.MSVC_SUBSYSTEM = 'WINDOWS'
|
conf.env.MSVC_SUBSYSTEM = 'WINDOWS'
|
||||||
conf.env.CONSOLE_SUBSYSTEM = 'CONSOLE'
|
conf.env.CONSOLE_SUBSYSTEM = 'CONSOLE'
|
||||||
|
|||||||
Reference in New Issue
Block a user