mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 03:24:56 +08:00
Compare commits
20 Commits
goldsrc-a2
...
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
|
||||
*.[Pp]ublish.xml
|
||||
*.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
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
@@ -345,3 +345,12 @@ enc_temp_folder/
|
||||
|
||||
# ccls langauge server
|
||||
.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.
|
||||
|
||||
### 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
|
||||
* 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.
|
||||
@@ -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`.
|
||||
|
||||
#### 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
|
||||
#### Windows (Visual Studio)
|
||||
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).
|
||||
2) Compile: `./waf build`.
|
||||
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.Month
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "su.xash.engine"
|
||||
ndkVersion = "28.0.13004108"
|
||||
namespace = "su.xash.engine"
|
||||
ndkVersion = "28.2.13676358"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "su.xash"
|
||||
applicationIdSuffix = "engine"
|
||||
versionName = "0.21"
|
||||
versionCode = getBuildNum()
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
compileSdk = 34
|
||||
defaultConfig {
|
||||
applicationId = "su.xash.engine"
|
||||
versionName = "0.21-" + getGitHash()
|
||||
versionCode = getBuildNum()
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
arguments("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF")
|
||||
}
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
val engineRoot = projectDir.parentFile.parent
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.22.1"
|
||||
path = file("CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a")
|
||||
experimentalProperties["ninja.path"] = File(engineRoot, "wscript").path
|
||||
experimentalProperties["ninja.configure"] = "run-python"
|
||||
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 {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_11
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
isDebuggable = true
|
||||
applicationIdSuffix = ".test"
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
lint {
|
||||
abortOnError = false
|
||||
}
|
||||
|
||||
register("asan") {
|
||||
initWith(getByName("debug"))
|
||||
}
|
||||
androidResources {
|
||||
noCompress += ""
|
||||
}
|
||||
|
||||
register("continuous") {
|
||||
initWith(getByName("release"))
|
||||
applicationIdSuffix = ".test"
|
||||
}
|
||||
}
|
||||
packaging {
|
||||
jniLibs {
|
||||
keepDebugSymbols.add("**/*.so")
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("main") {
|
||||
assets.srcDirs("../../3rdparty/extras/xash-extras", "../moddb")
|
||||
java.srcDir("../../3rdparty/SDL/android-project/app/src/main/java")
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
getByName("main") {
|
||||
assets.srcDirs("../../3rdparty/extras/xash-extras")
|
||||
java.srcDir("../../3rdparty/SDL/android-project/app/src/main/java")
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = false
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
isDebuggable = true
|
||||
applicationIdSuffix = ".test"
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
|
||||
androidResources {
|
||||
noCompress += ""
|
||||
}
|
||||
register("asan") {
|
||||
initWith(getByName("debug"))
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
keepDebugSymbols.add("**/*.so")
|
||||
}
|
||||
}
|
||||
register("continuous") {
|
||||
initWith(getByName("release"))
|
||||
applicationIdSuffix = ".test"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.google.android.material:material:1.11.0")
|
||||
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(libs.material)
|
||||
|
||||
implementation("com.madgag.spongycastle:prov:1.58.0.0")
|
||||
implementation("in.dragonbra:javasteam:1.2.0")
|
||||
implementation(libs.appcompat)
|
||||
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 {
|
||||
val now = LocalDateTime.now()
|
||||
val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0)
|
||||
val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS)
|
||||
val minuteOfDay = now.hour * 60 + now.minute
|
||||
return (qBuildNum * 10000 + minuteOfDay).toInt()
|
||||
val now = LocalDateTime.now()
|
||||
val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0)
|
||||
val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS)
|
||||
val minuteOfDay = now.hour * 60 + now.minute
|
||||
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 {
|
||||
java.lang.String loadAndroidID();
|
||||
java.lang.String getAndroidID();
|
||||
@@ -87,3 +109,8 @@
|
||||
void hapticRun(int, float, 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"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
|
||||
<string name="authority" translatable="false">su.xash.engine.test.documents</string>
|
||||
</resources>
|
||||
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,101 +1,83 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:allowAudioPlaybackCapture="true"
|
||||
android:installLocation="preferExternal"
|
||||
tools:targetApi="q">
|
||||
<!-- OpenGL ES 1.1 -->
|
||||
<uses-feature android:glEsVersion="0x00010000" />
|
||||
<!-- Touchscreen support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<!-- Game controller support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.gamepad"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="false" />
|
||||
<!-- External mouse input events -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.type.pc"
|
||||
android:required="false" />
|
||||
<!-- Audio recording support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" />
|
||||
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="22" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<!-- Allow access to Bluetooth devices -->
|
||||
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<!-- Allow access to the vibrator -->
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<!-- Allow recording audio -->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<!-- Allow internet access -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- Dedicated server -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
android:allowAudioPlaybackCapture="true"
|
||||
android:installLocation="preferExternal">
|
||||
<!-- OpenGL ES 1.1 -->
|
||||
<uses-feature android:glEsVersion="0x00010000" />
|
||||
<!-- Touchscreen support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<!-- Game controller support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.gamepad"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="false" />
|
||||
<!-- External mouse input events -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.type.pc"
|
||||
android:required="false" />
|
||||
<!-- Audio recording support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" />
|
||||
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="22" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
<!-- Allow access to Bluetooth devices -->
|
||||
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<!-- Allow access to the vibrator -->
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<!-- Allow recording audio -->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<!-- Allow internet access -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- Dedicated server -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:theme="@style/Theme.App"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:theme="@style/Theme.App"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".XashActivity"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation|touchscreen"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:preferMinimalPostProcessing="true"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
tools:targetApi="r">
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<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>
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".XashActivity"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation|touchscreen"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:preferMinimalPostProcessing="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</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
|
||||
|
||||
class DedicatedActivity {
|
||||
}
|
||||
class DedicatedActivity {}
|
||||
|
||||
@@ -4,8 +4,8 @@ import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
|
||||
class DedicatedService: Service() {
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
class DedicatedService : Service() {
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,26 +10,26 @@ import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import su.xash.engine.databinding.ActivityMainBinding
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
private lateinit var navController: NavController
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
private lateinit var navController: NavController
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
|
||||
navController = navHostFragment.navController
|
||||
appBarConfiguration = AppBarConfiguration(navController.graph)
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
}
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
|
||||
navController = navHostFragment.navController
|
||||
appBarConfiguration = AppBarConfiguration(navController.graph)
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
||||
}
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,26 +3,25 @@ package su.xash.engine
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.StrictMode
|
||||
import org.acra.config.httpSender
|
||||
import org.acra.data.StringFormat
|
||||
import org.acra.ktx.initAcra
|
||||
|
||||
class MainApplication : Application() {
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
|
||||
if (!BuildConfig.DEBUG) {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
initAcra {
|
||||
buildConfigClass = BuildConfig::class.java
|
||||
reportFormat = StringFormat.JSON
|
||||
|
||||
httpSender {
|
||||
uri = "http://bodis.pp.ua:5000/report"
|
||||
}
|
||||
// httpSender {
|
||||
// uri = "http://bodis.pp.ua:5000/report"
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
// enable strict mode to detect memory leaks etc.
|
||||
StrictMode.enableDefaults();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// enable strict mode to detect memory leaks etc.
|
||||
StrictMode.enableDefaults();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.pm.ActivityInfo;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.provider.Settings.Secure;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
@@ -12,137 +13,142 @@ import android.view.WindowManager;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
import su.xash.engine.util.AndroidBug5497Workaround;
|
||||
|
||||
public class XashActivity extends SDLActivity {
|
||||
private boolean mUseVolumeKeys;
|
||||
private String mPackageName;
|
||||
private static final String TAG = "XashActivity";
|
||||
private boolean mUseVolumeKeys;
|
||||
private String mPackageName;
|
||||
private static final String TAG = "XashActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
//getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
|
||||
getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
}
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
//getWindow().addFlags(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
|
||||
public void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
// 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)
|
||||
//
|
||||
// When the issue with global variables will be resolved, remove that exit() call
|
||||
System.exit(0);
|
||||
}
|
||||
// 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)
|
||||
//
|
||||
// When the issue with global variables will be resolved, remove that exit() call
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[]{"SDL2", "xash"};
|
||||
}
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[]{"SDL2", "xash"};
|
||||
}
|
||||
|
||||
@SuppressLint("HardwareIds")
|
||||
private String getAndroidID() {
|
||||
return Secure.getString(getContentResolver(), Secure.ANDROID_ID);
|
||||
}
|
||||
@SuppressLint("HardwareIds")
|
||||
private String getAndroidID() {
|
||||
return Secure.getString(getContentResolver(), Secure.ANDROID_ID);
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private void saveAndroidID(String id) {
|
||||
getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit();
|
||||
}
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private void saveAndroidID(String id) {
|
||||
getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit();
|
||||
}
|
||||
|
||||
private String loadAndroidID() {
|
||||
return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", "");
|
||||
}
|
||||
private String loadAndroidID() {
|
||||
return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCallingPackage() {
|
||||
if (mPackageName != null) {
|
||||
return mPackageName;
|
||||
}
|
||||
@Override
|
||||
public String getCallingPackage() {
|
||||
if (mPackageName != null) {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
return super.getCallingPackage();
|
||||
}
|
||||
return super.getCallingPackage();
|
||||
}
|
||||
|
||||
private AssetManager getAssets(boolean isEngine) {
|
||||
AssetManager am = null;
|
||||
private AssetManager getAssets(boolean isEngine) {
|
||||
AssetManager am = null;
|
||||
|
||||
if (isEngine) {
|
||||
am = getAssets();
|
||||
} else {
|
||||
try {
|
||||
am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to load mod assets!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (isEngine) {
|
||||
am = getAssets();
|
||||
} else {
|
||||
try {
|
||||
am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to load mod assets!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return am;
|
||||
}
|
||||
return am;
|
||||
}
|
||||
|
||||
private String[] getAssetsList(boolean isEngine, String path) {
|
||||
AssetManager am = getAssets(isEngine);
|
||||
private String[] getAssetsList(boolean isEngine, String path) {
|
||||
AssetManager am = getAssets(isEngine);
|
||||
|
||||
try {
|
||||
return am.list(path);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
return am.list(path);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return new String[]{};
|
||||
}
|
||||
return new String[]{};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (SDLActivity.mBrokenLibraries) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (SDLActivity.mBrokenLibraries) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int keyCode = event.getKeyCode();
|
||||
if (!mUseVolumeKeys) {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
int keyCode = event.getKeyCode();
|
||||
if (!mUseVolumeKeys) {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return getWindow().superDispatchKeyEvent(event);
|
||||
}
|
||||
return getWindow().superDispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
// TODO: REMOVE LATER, temporary launchers support?
|
||||
@Override
|
||||
protected String[] getArguments() {
|
||||
String gamedir = getIntent().getStringExtra("gamedir");
|
||||
if (gamedir == null) gamedir = "valve";
|
||||
nativeSetenv("XASH3D_GAME", gamedir);
|
||||
// TODO: REMOVE LATER, temporary launchers support?
|
||||
@Override
|
||||
protected String[] getArguments() {
|
||||
String gamedir = getIntent().getStringExtra("gamedir");
|
||||
if (gamedir == null) gamedir = "valve";
|
||||
nativeSetenv("XASH3D_GAME", gamedir);
|
||||
|
||||
String gamelibdir = getIntent().getStringExtra("gamelibdir");
|
||||
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
|
||||
String gamelibdir = getIntent().getStringExtra("gamelibdir");
|
||||
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
|
||||
|
||||
String pakfile = getIntent().getStringExtra("pakfile");
|
||||
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);
|
||||
String pakfile = getIntent().getStringExtra("pakfile");
|
||||
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);
|
||||
|
||||
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
|
||||
mPackageName = getIntent().getStringExtra("package");
|
||||
String basedir = getIntent().getStringExtra("basedir");
|
||||
if (basedir != null) {
|
||||
nativeSetenv("XASH3D_BASEDIR", basedir);
|
||||
} else {
|
||||
String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/xash";
|
||||
nativeSetenv("XASH3D_BASEDIR", rootPath);
|
||||
}
|
||||
|
||||
String[] env = getIntent().getStringArrayExtra("env");
|
||||
if (env != null) {
|
||||
for (int i = 0; i < env.length; i += 2)
|
||||
nativeSetenv(env[i], env[i + 1]);
|
||||
}
|
||||
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
|
||||
mPackageName = getIntent().getStringExtra("package");
|
||||
|
||||
String argv = getIntent().getStringExtra("argv");
|
||||
if (argv == null) argv = "-console -log";
|
||||
return argv.split(" ");
|
||||
}
|
||||
String[] env = getIntent().getStringArrayExtra("env");
|
||||
if (env != null) {
|
||||
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) :
|
||||
ListAdapter<Game, GameAdapter.GameViewHolder>(DiffCallback()) {
|
||||
ListAdapter<Game, GameAdapter.GameViewHolder>(DiffCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder {
|
||||
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return GameViewHolder(binding)
|
||||
}
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder {
|
||||
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return GameViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) {
|
||||
return holder.bind(getItem(position))
|
||||
}
|
||||
override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) {
|
||||
return holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem.basedir.name == newItem.basedir.name
|
||||
}
|
||||
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem.basedir.name == newItem.basedir.name
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem.basedir.name == newItem.basedir.name && oldItem.installed == newItem.installed
|
||||
}
|
||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem.basedir.name == newItem.basedir.name
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
inner class GameViewHolder(val binding: CardGameBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(game: Game) {
|
||||
binding.apply {
|
||||
gameTitle.text = game.title
|
||||
inner class GameViewHolder(val binding: CardGameBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(game: Game) {
|
||||
binding.apply {
|
||||
gameTitle.text = game.title
|
||||
|
||||
if (game.icon != null) {
|
||||
gameIcon.setImageBitmap(game.icon)
|
||||
} else {
|
||||
gameIcon.visibility = View.GONE
|
||||
}
|
||||
if (game.icon != null) {
|
||||
gameIcon.setImageBitmap(game.icon)
|
||||
} else {
|
||||
gameIcon.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (game.cover != null) {
|
||||
gameCover.setImageBitmap(game.cover)
|
||||
} else {
|
||||
gameCover.visibility = View.GONE
|
||||
}
|
||||
if (game.cover != null) {
|
||||
gameCover.setImageBitmap(game.cover)
|
||||
} else {
|
||||
gameCover.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (!game.installed) {
|
||||
launchButton.visibility = View.GONE
|
||||
settingsButton.visibility = View.GONE
|
||||
progressIndicator.visibility = View.VISIBLE
|
||||
return
|
||||
}
|
||||
settingsButton.setOnClickListener {
|
||||
libraryViewModel.setSelectedGame(game)
|
||||
it.findNavController()
|
||||
.navigate(R.id.action_libraryFragment_to_gameSettingsFragment)
|
||||
}
|
||||
|
||||
settingsButton.setOnClickListener {
|
||||
libraryViewModel.setSelectedGame(game)
|
||||
it.findNavController()
|
||||
.navigate(R.id.action_libraryFragment_to_gameSettingsFragment)
|
||||
}
|
||||
|
||||
root.setOnClickListener { libraryViewModel.startEngine(it.context, game) }
|
||||
launchButton.setOnClickListener {
|
||||
libraryViewModel.startEngine(it.context, game)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
root.setOnClickListener { libraryViewModel.startEngine(it.context, game) }
|
||||
launchButton.setOnClickListener {
|
||||
libraryViewModel.startEngine(it.context, game)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +1,90 @@
|
||||
package su.xash.engine.model
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import su.xash.engine.util.TGAReader
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.util.Scanner
|
||||
|
||||
|
||||
object BackgroundBitmap {
|
||||
private const val BACKGROUND_ROWS = 3
|
||||
private const val BACKGROUND_COLUMNS = 4
|
||||
private const val BACKGROUND_WIDTH = 800
|
||||
private const val BACKGROUND_HEIGHT = 600
|
||||
private const val BACKGROUND_ROWS = 3
|
||||
private const val BACKGROUND_COLUMNS = 4
|
||||
private const val BACKGROUND_WIDTH = 800
|
||||
private const val BACKGROUND_HEIGHT = 600
|
||||
|
||||
fun createBackground(ctx: Context, file: DocumentFile): Bitmap {
|
||||
var bitmap =
|
||||
Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)
|
||||
var canvas = Canvas(bitmap)
|
||||
var x: Int
|
||||
var y = 0
|
||||
var width: Int
|
||||
var height = 0
|
||||
fun createBackground(file: File): Bitmap {
|
||||
var bitmap =
|
||||
Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)
|
||||
var canvas = Canvas(bitmap)
|
||||
var x: Int
|
||||
var y = 0
|
||||
var width: Int
|
||||
var height = 0
|
||||
|
||||
var bgLayout = file.findFile("resource")?.findFile("HD_BackgroundLayout.txt")
|
||||
if (bgLayout == null) {
|
||||
bgLayout = file.findFile("resource")?.findFile("BackgroundLayout.txt")
|
||||
}
|
||||
val resourceFolder = File(file, "resource")
|
||||
var bgLayout = File(resourceFolder, "HD_BackgroundLayout.txt")
|
||||
if (!bgLayout.exists()) {
|
||||
bgLayout = File(resourceFolder, "BackgroundLayout.txt")
|
||||
}
|
||||
|
||||
if (bgLayout == null) {
|
||||
val dir = file.findFile("resource")?.findFile("background")
|
||||
for (i in 0 until BACKGROUND_ROWS) {
|
||||
x = 0
|
||||
for (j in 0 until BACKGROUND_COLUMNS) {
|
||||
val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga"
|
||||
val bmpFile = dir?.findFile(filename)
|
||||
val bmpImage = loadTga(ctx, bmpFile!!)
|
||||
if (!bgLayout.exists()) {
|
||||
val dir = File(resourceFolder, "background")
|
||||
for (i in 0 until BACKGROUND_ROWS) {
|
||||
x = 0
|
||||
for (j in 0 until BACKGROUND_COLUMNS) {
|
||||
val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga"
|
||||
val bmpFile = File(dir, filename)
|
||||
val bmpImage = loadTga(bmpFile)
|
||||
|
||||
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
|
||||
x += bmpImage.width
|
||||
height = bmpImage.height
|
||||
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
|
||||
x += bmpImage.width
|
||||
height = bmpImage.height
|
||||
|
||||
}
|
||||
y += height
|
||||
}
|
||||
return bitmap
|
||||
}
|
||||
}
|
||||
y += height
|
||||
}
|
||||
return bitmap
|
||||
}
|
||||
|
||||
ctx.contentResolver.openInputStream(bgLayout.uri).use { inputStream ->
|
||||
Scanner(inputStream).use { scanner ->
|
||||
while (scanner.hasNext()) {
|
||||
when (val str = scanner.next()) {
|
||||
"resolution" -> {
|
||||
width = scanner.nextInt()
|
||||
height = scanner.nextInt()
|
||||
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
canvas = Canvas(bitmap)
|
||||
}
|
||||
FileInputStream(bgLayout).use { inputStream ->
|
||||
Scanner(inputStream).use { scanner ->
|
||||
while (scanner.hasNext()) {
|
||||
when (val str = scanner.next()) {
|
||||
"resolution" -> {
|
||||
width = scanner.nextInt()
|
||||
height = scanner.nextInt()
|
||||
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
canvas = Canvas(bitmap)
|
||||
}
|
||||
|
||||
else -> {
|
||||
var bmpFile = file
|
||||
str.split("/").forEach { bmpFile = bmpFile.findFile(it)!! }
|
||||
//skip
|
||||
scanner.next()
|
||||
x = scanner.nextInt()
|
||||
y = scanner.nextInt()
|
||||
val bmp = loadTga(ctx, bmpFile)
|
||||
canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bitmap
|
||||
}
|
||||
else -> {
|
||||
var bmpFile = file
|
||||
str.split("/").forEach { bmpFile = File(bmpFile, it) }
|
||||
//skip
|
||||
scanner.next()
|
||||
x = scanner.nextInt()
|
||||
y = scanner.nextInt()
|
||||
val bmp = loadTga(bmpFile)
|
||||
canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bitmap
|
||||
}
|
||||
|
||||
private fun loadTga(ctx: Context, file: DocumentFile): Bitmap {
|
||||
ctx.contentResolver.openInputStream(file.uri).use {
|
||||
val buffer = it?.readBytes()
|
||||
val pixels = TGAReader.read(buffer, TGAReader.ARGB)
|
||||
private fun loadTga(file: File): Bitmap {
|
||||
FileInputStream(file).use {
|
||||
val buffer = it.readBytes()
|
||||
val pixels = TGAReader.read(buffer, TGAReader.ARGB)
|
||||
|
||||
val width = TGAReader.getWidth(buffer)
|
||||
val height = TGAReader.getHeight(buffer)
|
||||
val width = TGAReader.getWidth(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.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
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) {
|
||||
private var iconName = "game.ico"
|
||||
var title = "Unknown Game"
|
||||
var icon: Bitmap? = null
|
||||
var cover: Bitmap? = null
|
||||
class Game(val ctx: Context, val basedir: File) {
|
||||
private var iconName = "game.ico"
|
||||
var title = "Unknown Game"
|
||||
var icon: 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 {
|
||||
basedir.findFile("gameinfo.txt")?.let {
|
||||
parseGameInfo(it)
|
||||
} ?: basedir.findFile("liblist.gam")?.let { parseGameInfo(it) }
|
||||
init {
|
||||
val gameInfo = File(basedir, "gameinfo.txt")
|
||||
if (gameInfo.exists()) {
|
||||
parseGameInfo(gameInfo)
|
||||
} else {
|
||||
val libListGam = File(basedir, "liblist.gam")
|
||||
if (libListGam.exists()) parseGameInfo(libListGam)
|
||||
}
|
||||
|
||||
basedir.findFile(iconName)
|
||||
?.let { icon = MediaStore.Images.Media.getBitmap(ctx.contentResolver, it.uri) }
|
||||
val iconFile = File(basedir, iconName)
|
||||
if (iconFile.exists()) {
|
||||
icon = BitmapFactory.decodeFile(iconFile.path)
|
||||
}
|
||||
|
||||
try {
|
||||
cover = BackgroundBitmap.createBackground(ctx, basedir)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
try {
|
||||
cover = BackgroundBitmap.createBackground(basedir)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun startEngine(ctx: Context) {
|
||||
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra("gamedir", basedir.name)
|
||||
putExtra("argv", pref.getString("arguments", "-console -log"))
|
||||
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
|
||||
//.putExtra("gamelibdir", getGameLibDir(context))
|
||||
//.putExtra("package", getPackageName()) }
|
||||
})
|
||||
}
|
||||
fun startEngine(ctx: Context) {
|
||||
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra("gamedir", basedir.name)
|
||||
putExtra("argv", pref.getString("arguments", "-console -log"))
|
||||
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
|
||||
putExtra("basedir", basedir.parent)
|
||||
//.putExtra("gamelibdir", getGameLibDir(context))
|
||||
//.putExtra("package", getPackageName()) }
|
||||
})
|
||||
}
|
||||
|
||||
private fun parseGameInfo(file: DocumentFile) {
|
||||
ctx.contentResolver.openInputStream(file.uri).use { inputStream ->
|
||||
inputStream?.bufferedReader().use { reader ->
|
||||
reader?.forEachLine {
|
||||
val tokens = it.split("\\s+".toRegex(), limit = 2)
|
||||
if (tokens.size >= 2) {
|
||||
val k = tokens[0]
|
||||
val v = tokens[1].trim('"')
|
||||
private fun parseGameInfo(file: File) {
|
||||
FileInputStream(file).use { inputStream ->
|
||||
inputStream.bufferedReader().use { reader ->
|
||||
reader.forEachLine {
|
||||
val tokens = it.split("\\s+".toRegex(), limit = 2)
|
||||
if (tokens.size >= 2) {
|
||||
val k = tokens[0]
|
||||
val v = tokens[1].trim('"')
|
||||
|
||||
if (k == "title" || k == "game") title = v
|
||||
if (k == "icon") iconName = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (k == "title" || k == "game") title = v
|
||||
if (k == "icon") iconName = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPackageName(): String? {
|
||||
private fun getPackageName(): String? {
|
||||
// return if (mDbEntry != null) {
|
||||
// mDbEntry.getPackageName()
|
||||
// } else null
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getGameLibDir(ctx: Context): String? {
|
||||
val pkgName = getPackageName()
|
||||
if (pkgName != null) {
|
||||
val pkgInfo: PackageInfo = try {
|
||||
ctx.packageManager.getPackageInfo(pkgName, 0)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
ctx.startActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW, Uri.parse("market://details?id=$pkgName")
|
||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
)
|
||||
return null
|
||||
}
|
||||
return pkgInfo.applicationInfo.nativeLibraryDir
|
||||
}
|
||||
return ctx.applicationInfo.nativeLibraryDir
|
||||
}
|
||||
private fun getGameLibDir(ctx: Context): String? {
|
||||
val pkgName = getPackageName()
|
||||
if (pkgName != null) {
|
||||
val pkgInfo: PackageInfo = try {
|
||||
ctx.packageManager.getPackageInfo(pkgName, 0)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
ctx.startActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW, Uri.parse("market://details?id=$pkgName")
|
||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
)
|
||||
return null
|
||||
}
|
||||
return pkgInfo.applicationInfo?.nativeLibraryDir
|
||||
}
|
||||
return ctx.applicationInfo.nativeLibraryDir
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getGames(ctx: Context, file: DocumentFile): List<Game> {
|
||||
val games = mutableListOf<Game>()
|
||||
companion object {
|
||||
fun getGames(ctx: Context, file: File): List<Game> {
|
||||
val games = mutableListOf<Game>()
|
||||
|
||||
if (checkIfGamedir(file)) {
|
||||
games.add(Game(ctx, file))
|
||||
} else {
|
||||
file.listFiles().forEach {
|
||||
if (it.isDirectory) {
|
||||
if (checkIfGamedir(it)) {
|
||||
games.add(Game(ctx, it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checkIfGamedir(file)) {
|
||||
games.add(Game(ctx, file))
|
||||
} else {
|
||||
file.listFiles()?.forEach {
|
||||
if (it.isDirectory) {
|
||||
if (checkIfGamedir(it)) {
|
||||
games.add(Game(ctx, it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return games
|
||||
}
|
||||
return games
|
||||
}
|
||||
|
||||
fun checkIfGamedir(file: DocumentFile): Boolean {
|
||||
// exclude unfinished downloads
|
||||
if (file.name?.startsWith('.') == true)
|
||||
return false
|
||||
fun checkIfGamedir(file: File): Boolean {
|
||||
if (File(file, "liblist.gam").exists()) return true
|
||||
if (File(file, "gameinfo.txt").exists()) return true
|
||||
|
||||
file.findFile("liblist.gam")?.let { return true }
|
||||
file.findFile("gameinfo.txt")?.let { return true }
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
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.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
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.adapters.GameAdapter
|
||||
import su.xash.engine.databinding.FragmentLibraryBinding
|
||||
|
||||
class LibraryFragment : Fragment(), MenuProvider {
|
||||
private var _binding: FragmentLibraryBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private var _binding: FragmentLibraryBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
|
||||
private val startActivityForResult =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (checkStoragePermissions()) {
|
||||
libraryViewModel.reloadGames(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
val adapter = GameAdapter(libraryViewModel)
|
||||
binding.gamesList.adapter = adapter
|
||||
private fun checkStoragePermissions(): Boolean {
|
||||
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?) {
|
||||
binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }
|
||||
val adapter = GameAdapter(libraryViewModel)
|
||||
binding.gamesList.adapter = adapter
|
||||
|
||||
libraryViewModel.isReloading.observe(viewLifecycleOwner) {
|
||||
binding.swipeRefresh.isRefreshing = it
|
||||
}
|
||||
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
||||
|
||||
libraryViewModel.installedGames.observe(viewLifecycleOwner) {
|
||||
(binding.gamesList.adapter as GameAdapter).submitList(it)
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
libraryViewModel.workInfos.observe(viewLifecycleOwner) {
|
||||
libraryViewModel.refreshDownloads(requireContext())
|
||||
}
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }
|
||||
|
||||
libraryViewModel.downloads.observe(viewLifecycleOwner) {
|
||||
libraryViewModel.reloadGames(requireContext())
|
||||
}
|
||||
}
|
||||
libraryViewModel.isReloading.observe(viewLifecycleOwner) {
|
||||
binding.swipeRefresh.isRefreshing = it
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
libraryViewModel.installedGames.observe(viewLifecycleOwner) {
|
||||
(binding.gamesList.adapter as GameAdapter).submitList(it)
|
||||
}
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_library, menu)
|
||||
}
|
||||
if (checkStoragePermissions()) {
|
||||
libraryViewModel.reloadGames(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_browse -> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
R.id.action_install -> {
|
||||
findNavController().navigate(R.id.action_libraryFragment_to_setupFragment)
|
||||
}
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_library, menu)
|
||||
}
|
||||
|
||||
R.id.action_settings -> {
|
||||
findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment)
|
||||
}
|
||||
}
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
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.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import android.os.Environment
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import su.xash.engine.model.Game
|
||||
import su.xash.engine.model.ModDatabase
|
||||
import su.xash.engine.workers.FileCopyWorker
|
||||
import su.xash.engine.workers.KEY_FILE_URI
|
||||
import java.util.Locale
|
||||
|
||||
const val TAG_INSTALL = "TAG_INSTALL"
|
||||
import java.io.File
|
||||
|
||||
class LibraryViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val installedGames: LiveData<List<Game>> get() = _installedGames
|
||||
private val _installedGames = MutableLiveData(emptyList<Game>())
|
||||
val installedGames: LiveData<List<Game>> get() = _installedGames
|
||||
private val _installedGames = MutableLiveData(emptyList<Game>())
|
||||
|
||||
val downloads: LiveData<List<Game>> get() = _downloads
|
||||
private val _downloads = MutableLiveData(emptyList<Game>())
|
||||
val isReloading: LiveData<Boolean> get() = _isReloading
|
||||
private val _isReloading = MutableLiveData(false)
|
||||
|
||||
val isReloading: LiveData<Boolean> get() = _isReloading
|
||||
private val _isReloading = MutableLiveData(false)
|
||||
val selectedItem: LiveData<Game> get() = _selectedItem
|
||||
private val _selectedItem = MutableLiveData<Game>()
|
||||
|
||||
private val workManager = WorkManager.getInstance(application.applicationContext)
|
||||
val workInfos: LiveData<List<WorkInfo>> = workManager.getWorkInfosByTagLiveData(TAG_INSTALL)
|
||||
private val appPreferences: SharedPreferences =
|
||||
application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
||||
|
||||
val selectedItem: LiveData<Game> get() = _selectedItem
|
||||
private val _selectedItem = MutableLiveData<Game>()
|
||||
fun reloadGames(ctx: Context) {
|
||||
if (isReloading.value == true) {
|
||||
return
|
||||
}
|
||||
_isReloading.value = true
|
||||
|
||||
val appPreferences = application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
||||
val modDb: ModDatabase
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val rootPath = appPreferences.getString("game_path", null)
|
||||
?: (Environment.getExternalStorageDirectory().absolutePath + "/xash")
|
||||
val root = File(rootPath)
|
||||
|
||||
init {
|
||||
modDb = application.assets.open(ModDatabase.getFilename()).use { ModDatabase(it) }
|
||||
reloadGames(application.applicationContext)
|
||||
}
|
||||
_installedGames.postValue(Game.getGames(ctx, root))
|
||||
_isReloading.postValue(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun reloadGames(ctx: Context) {
|
||||
if (isReloading.value == true) {
|
||||
return
|
||||
}
|
||||
_isReloading.value = true
|
||||
fun setSelectedGame(game: Game) {
|
||||
_selectedItem.value = game
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
fun startEngine(ctx: Context, game: Game) {
|
||||
game.startEngine(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,24 @@ import androidx.fragment.app.Fragment
|
||||
import su.xash.engine.databinding.FragmentAppSettingsBinding
|
||||
|
||||
class AppSettingsFragment : Fragment() {
|
||||
private var _binding: FragmentAppSettingsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private var _binding: FragmentAppSettingsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
childFragmentManager.beginTransaction()
|
||||
.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();
|
||||
}
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
childFragmentManager.beginTransaction()
|
||||
.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import androidx.preference.PreferenceFragmentCompat
|
||||
import su.xash.engine.R
|
||||
|
||||
class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.sharedPreferencesName = "app_preferences";
|
||||
setPreferencesFromResource(R.xml.app_preferences, rootKey);
|
||||
}
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.sharedPreferencesName = "app_preferences";
|
||||
setPreferencesFromResource(R.xml.app_preferences, rootKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,59 +6,52 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
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.ui.library.LibraryViewModel
|
||||
|
||||
class GameSettingsFragment : Fragment() {
|
||||
private var _binding: FragmentGameSettingsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private var _binding: FragmentGameSettingsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val game = libraryViewModel.selectedItem.value!!
|
||||
val game = libraryViewModel.selectedItem.value!!
|
||||
|
||||
binding.gameCard.apply {
|
||||
gameTitle.text = game.title
|
||||
binding.gameCard.apply {
|
||||
gameTitle.text = game.title
|
||||
|
||||
if (game.icon != null) {
|
||||
gameIcon.setImageBitmap(game.icon)
|
||||
} else {
|
||||
gameIcon.visibility = View.GONE
|
||||
}
|
||||
if (game.icon != null) {
|
||||
gameIcon.setImageBitmap(game.icon)
|
||||
} else {
|
||||
gameIcon.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (game.cover != null) {
|
||||
gameCover.setImageBitmap(game.cover)
|
||||
} else {
|
||||
gameCover.visibility = View.GONE
|
||||
}
|
||||
if (game.cover != null) {
|
||||
gameCover.setImageBitmap(game.cover)
|
||||
} else {
|
||||
gameCover.visibility = View.GONE
|
||||
}
|
||||
|
||||
buttonsContainer.visibility = View.GONE
|
||||
}
|
||||
buttonsContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
childFragmentManager.beginTransaction()
|
||||
.add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game))
|
||||
.commit();
|
||||
childFragmentManager.beginTransaction()
|
||||
.add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game))
|
||||
.commit();
|
||||
}
|
||||
|
||||
binding.bottomNavigation.menu.findItem(R.id.action_uninstall).setOnMenuItemClickListener {
|
||||
libraryViewModel.uninstallGame(game)
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,33 +8,33 @@ import su.xash.engine.R
|
||||
import su.xash.engine.model.Game
|
||||
|
||||
class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.sharedPreferencesName = game.basedir.name;
|
||||
setPreferencesFromResource(R.xml.game_preferences, rootKey);
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.sharedPreferencesName = game.basedir.name;
|
||||
setPreferencesFromResource(R.xml.game_preferences, rootKey);
|
||||
|
||||
val packageList = findPreference<ListPreference>("package_name")!!
|
||||
packageList.entries = arrayOf(getString(R.string.app_name))
|
||||
packageList.entryValues = arrayOf(requireContext().packageName)
|
||||
val packageList = findPreference<ListPreference>("package_name")!!
|
||||
packageList.entries = arrayOf(getString(R.string.app_name))
|
||||
packageList.entryValues = arrayOf(requireContext().packageName)
|
||||
|
||||
if (packageList.value == null) {
|
||||
packageList.setValueIndex(0);
|
||||
}
|
||||
if (packageList.value == null) {
|
||||
packageList.setValueIndex(0);
|
||||
}
|
||||
|
||||
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
|
||||
val clientPackage = findPreference<ListPreference>("client_package")!!
|
||||
val serverPackage = findPreference<ListPreference>("server_package")!!
|
||||
separatePackages.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue == true) {
|
||||
packageList.isVisible = false
|
||||
clientPackage.isVisible = true
|
||||
serverPackage.isVisible = true
|
||||
} else {
|
||||
packageList.isVisible = true
|
||||
clientPackage.isVisible = false
|
||||
serverPackage.isVisible = false
|
||||
}
|
||||
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
|
||||
val clientPackage = findPreference<ListPreference>("client_package")!!
|
||||
val serverPackage = findPreference<ListPreference>("server_package")!!
|
||||
separatePackages.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue == true) {
|
||||
packageList.isVisible = false
|
||||
clientPackage.isVisible = true
|
||||
serverPackage.isVisible = true
|
||||
} else {
|
||||
packageList.isVisible = true
|
||||
clientPackage.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"?>
|
||||
<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">
|
||||
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:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragmentContainerView"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
|
||||
app:navGraph="@navigation/nav_graph"
|
||||
tools:layout="@layout/fragment_library"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragmentContainerView"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
|
||||
app:navGraph="@navigation/nav_graph"
|
||||
tools:layout="@layout/fragment_library" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -1,111 +1,98 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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:tools="http://schemas.android.com/tools"
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gameCover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="@android:mipmap/sym_def_app_icon" />
|
||||
<ImageView
|
||||
android:id="@+id/gameCover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="@android:mipmap/sym_def_app_icon" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_margin="8dp"
|
||||
app:contentPadding="10dp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/buttonsContainer"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_margin="8dp"
|
||||
app:contentPadding="10dp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/buttonsContainer"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gameIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginRight="8dp"
|
||||
android:adjustViewBounds="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/gameTitle"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@android:mipmap/sym_def_app_icon" />
|
||||
<ImageView
|
||||
android:id="@+id/gameIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginRight="8dp"
|
||||
android:adjustViewBounds="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/gameTitle"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@android:mipmap/sym_def_app_icon" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/gameTitle"
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:singleLine="true"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/gameIcon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Game Title" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/gameTitle"
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:singleLine="true"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/gameIcon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Game Title" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/buttonsContainer"
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/buttonsContainer"
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/progressIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:padding="10dp"
|
||||
android:visibility="gone"
|
||||
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
|
||||
android:id="@+id/launchButton"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
app:layout_constraintEnd_toStartOf="@+id/settingsButton" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/launchButton"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
app:layout_constraintEnd_toStartOf="@+id/settingsButton" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/settingsButton"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
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>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/settingsButton"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
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"?>
|
||||
<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="wrap_content">
|
||||
<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:tools="http://schemas.android.com/tools"
|
||||
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-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="parent">-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp">
|
||||
|
||||
<!-- <com.google.android.material.textfield.TextInputEditText-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content" />-->
|
||||
<!-- </com.google.android.material.textfield.TextInputLayout>-->
|
||||
<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
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="EditText Preference" />
|
||||
|
||||
<TextView
|
||||
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"?>
|
||||
<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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="8dp">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/settingsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/settingsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -1,39 +1,32 @@
|
||||
<?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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
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">
|
||||
|
||||
<include
|
||||
android:id="@+id/gameCard"
|
||||
layout="@layout/card_game"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<include
|
||||
android:id="@+id/gameCard"
|
||||
layout="@layout/card_game"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:contentPadding="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/gameCard">
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/settingsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/gameCard"
|
||||
tools:layout="@android:layout/list_content">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/settingsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
</androidx.fragment.app.FragmentContainerView>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
android:id="@+id/bottomNavigation"
|
||||
style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:menu="@menu/menu_game_settings" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<!-- <com.google.android.material.bottomnavigation.BottomNavigationView-->
|
||||
<!-- android:id="@+id/bottomNavigation"-->
|
||||
<!-- style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
||||
<!-- app:menu="@menu/menu_game_settings" />-->
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout 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:id="@+id/swipeRefresh">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/swipeRefresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gamesList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="8dp"
|
||||
tools:listitem="@layout/card_game" />
|
||||
</RelativeLayout>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gamesList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="8dp"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:listitem="@layout/card_game" />
|
||||
</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"?>
|
||||
<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="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"/>
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<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>
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<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"?>
|
||||
<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="wrap_content">
|
||||
<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:tools="http://schemas.android.com/tools"
|
||||
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
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
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"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_dedicated"
|
||||
android:icon="@drawable/ic_baseline_terminal_24"
|
||||
android:title="@string/dedicated_server"
|
||||
app:showAsAction="always|withText"
|
||||
android:visible="false"/>
|
||||
<item
|
||||
android:id="@+id/action_uninstall"
|
||||
android:icon="@drawable/ic_baseline_delete_24"
|
||||
android:title="@string/uninstall"
|
||||
app:showAsAction="always|withText" />
|
||||
</menu>
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_dedicated"
|
||||
android:icon="@drawable/ic_baseline_terminal_24"
|
||||
android:title="@string/dedicated_server"
|
||||
android:visible="false"
|
||||
app:showAsAction="always|withText" />
|
||||
</menu>
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_browse"
|
||||
android:icon="@drawable/ic_baseline_folder_open_24"
|
||||
android:title="@string/browse"
|
||||
app:showAsAction="always|withText" />
|
||||
<item
|
||||
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>
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:icon="@drawable/ic_baseline_settings_24"
|
||||
android:title="@string/app_settings"
|
||||
app:showAsAction="always|withText" />
|
||||
</menu>
|
||||
|
||||
@@ -1,38 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation 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:id="@+id/nav_graph"
|
||||
android:label="@string/library"
|
||||
app:startDestination="@id/libraryFragment">
|
||||
<fragment
|
||||
android:id="@+id/libraryFragment"
|
||||
android:name="su.xash.engine.ui.library.LibraryFragment"
|
||||
android:label="@string/library">
|
||||
<action
|
||||
android:id="@+id/action_libraryFragment_to_setupFragment"
|
||||
app:destination="@id/setupFragment" />
|
||||
<action
|
||||
android:id="@+id/action_libraryFragment_to_gameSettingsFragment"
|
||||
app:destination="@id/gameSettingsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_libraryFragment_to_appSettingsFragment"
|
||||
app:destination="@id/appSettingsFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/setupFragment"
|
||||
android:name="su.xash.engine.ui.setup.SetupFragment"
|
||||
android:label="@string/setup" >
|
||||
<action
|
||||
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>
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/nav_graph"
|
||||
android:label="@string/library"
|
||||
app:startDestination="@id/libraryFragment">
|
||||
<fragment
|
||||
android:id="@+id/libraryFragment"
|
||||
android:name="su.xash.engine.ui.library.LibraryFragment"
|
||||
android:label="@string/library">
|
||||
<action
|
||||
android:id="@+id/action_libraryFragment_to_gameSettingsFragment"
|
||||
app:destination="@id/gameSettingsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_libraryFragment_to_appSettingsFragment"
|
||||
app:destination="@id/appSettingsFragment" />
|
||||
</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"?>
|
||||
<resources>
|
||||
<string name="ok">Ok</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="next">Próximo</string>
|
||||
<string name="browse">Navegar</string>
|
||||
<string name="install">Instalar</string>
|
||||
<string name="library">Biblioteca</string>
|
||||
<string name="steam_guard_code">Código do Steam Guard</string>
|
||||
<string name="steam_login">Logar com Steam</string>
|
||||
<string name="login">Logar</string>
|
||||
<string name="username">Usuário</string>
|
||||
<string name="password">Senha</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>
|
||||
<string name="library">Biblioteca</string>
|
||||
<string name="dedicated_server">Dedicado</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="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>
|
||||
|
||||
@@ -1,33 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="cancel">Отмена</string>
|
||||
<string name="ok">ОК</string>
|
||||
<string name="next">Далее</string>
|
||||
<string name="browse">Обзор</string>
|
||||
<string name="install">Установить</string>
|
||||
<string name="library">Библиотека</string>
|
||||
<string name="steam_guard_code">Код Steam Guard</string>
|
||||
<string name="steam_login">Логин Steam</string>
|
||||
<string name="login">Логин</string>
|
||||
<string name="username">Имя пользователя</string>
|
||||
<string name="password">Пароль</string>
|
||||
<string name="uninstall">Удалить</string>
|
||||
<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>
|
||||
<string name="library">Библиотека</string>
|
||||
<string name="dedicated_server">Выделенный</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="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"?>
|
||||
<resources>
|
||||
<color name="valve_red">#F74843</color>
|
||||
<color name="steam_grey">#C5C3C0</color>
|
||||
<color name="steam_background">#181A21</color>
|
||||
<color name="steam_dark_grey">#32353C</color>
|
||||
<color name="hl_orange">#FB7E14</color>
|
||||
<color name="black">#000000</color>
|
||||
<color name="hl_dark_grey">#151515</color>
|
||||
<color name="hl_grey">#292929</color>
|
||||
<color name="valve_red">#F74843</color>
|
||||
<color name="hl_orange">#FB7E14</color>
|
||||
<color name="black">#000000</color>
|
||||
<color name="hl_dark_grey">#151515</color>
|
||||
<color name="hl_grey">#292929</color>
|
||||
</resources>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#F74843</color>
|
||||
</resources>
|
||||
<color name="ic_launcher_background">#F74843</color>
|
||||
</resources>
|
||||
|
||||
@@ -1,37 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Xash3D FWGS</string>
|
||||
<string name="authority" translatable="false">su.xash.engine.documents</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="browse">Browse</string>
|
||||
<string name="install">Install</string>
|
||||
<string name="library">Library</string>
|
||||
<string name="steam_guard_code">Steam Guard Code</string>
|
||||
<string name="steam_login">Steam Login</string>
|
||||
<string name="login">Login</string>
|
||||
<string name="username">Username</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="steam" translatable="false">Steam</string>
|
||||
<string name="uninstall">Uninstall</string>
|
||||
<string name="dedicated_server">Dedicated</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>
|
||||
<string name="app_name" translatable="false">Xash3D FWGS</string>
|
||||
<string name="library">Library</string>
|
||||
<string name="dedicated_server">Dedicated</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="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="game_data_location">Game data location</string>
|
||||
<string name="file_access_required">All-files access required</string>
|
||||
<string name="file_access_message">All-files access is required for the app to function properly.</string>
|
||||
<string name="select_current_directory">Select current directory</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="App.Theme.SteamButton" parent="@style/Widget.Material3.Button">
|
||||
<item name="android:background">@drawable/steam_button_gradient</item>
|
||||
<item name="backgroundTint">@null</item>
|
||||
<item name="android:textColor">#FFFFFF</item>
|
||||
</style>
|
||||
|
||||
<style name="ShapeAppearance.App.MediumComponent" parent="ShapeAppearance.Material3.MediumComponent">
|
||||
<item name="cornerSize">4dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ShapeAppearance.App.MediumComponent" parent="ShapeAppearance.Material3.MediumComponent">
|
||||
<item name="cornerSize">4dp</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<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>
|
||||
</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>
|
||||
</resources>
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<SwitchPreferenceCompat
|
||||
app:key="use_icons"
|
||||
app:layout="@layout/switch_preference"
|
||||
app:title="@string/preferences_use_icons" />
|
||||
</PreferenceScreen>
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<Preference
|
||||
app:key="game_path"
|
||||
app:layout="@layout/edit_text_preference"
|
||||
app:title="@string/game_data_location"
|
||||
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"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<EditTextPreference
|
||||
app:defaultValue="-console -log"
|
||||
app:key="arguments"
|
||||
app:layout="@layout/edit_text_preference"
|
||||
app:title="@string/game_settings_command_line"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<EditTextPreference
|
||||
app:defaultValue="-console -log"
|
||||
app:key="arguments"
|
||||
app:layout="@layout/edit_text_preference"
|
||||
app:title="@string/game_settings_command_line"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:key="use_volume_buttons"
|
||||
app:layout="@layout/switch_preference"
|
||||
app:title="@string/game_settings_volume_buttons" />
|
||||
<SwitchPreferenceCompat
|
||||
app:key="use_volume_buttons"
|
||||
app:layout="@layout/switch_preference"
|
||||
app:title="@string/game_settings_volume_buttons" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:key="separate_libraries"
|
||||
app:layout="@layout/switch_preference"
|
||||
app:title="@string/preferences_separate_libraries"
|
||||
app:isPreferenceVisible="false"/>
|
||||
<SwitchPreferenceCompat
|
||||
app:key="separate_libraries"
|
||||
app:layout="@layout/switch_preference"
|
||||
app:title="@string/preferences_separate_libraries"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
<ListPreference
|
||||
app:key="package_name"
|
||||
app:layout="@layout/list_preference"
|
||||
app:title="@string/preferences_package_name"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:isPreferenceVisible="false"/>
|
||||
<ListPreference
|
||||
app:key="package_name"
|
||||
app:layout="@layout/list_preference"
|
||||
app:title="@string/preferences_package_name"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
<ListPreference
|
||||
app:key="client_package"
|
||||
app:layout="@layout/list_preference"
|
||||
app:title="@string/preferences_client_package"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:isPreferenceVisible="false"/>
|
||||
<ListPreference
|
||||
app:key="client_package"
|
||||
app:layout="@layout/list_preference"
|
||||
app:title="@string/preferences_client_package"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
<ListPreference
|
||||
app:key="server_package"
|
||||
app:layout="@layout/list_preference"
|
||||
app:title="@string/preferences_server_package"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:isPreferenceVisible="false"/>
|
||||
<ListPreference
|
||||
app:key="server_package"
|
||||
app:layout="@layout/list_preference"
|
||||
app:title="@string/preferences_server_package"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:isPreferenceVisible="false" />
|
||||
</PreferenceScreen>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.5.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
|
||||
}
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# This option should only be used with decoupled projects. For more details, visit
|
||||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
@@ -22,5 +22,6 @@ kotlin.code.style=official
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
|
||||
# Enable verbose output for CMake
|
||||
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
|
||||
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
|
||||
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 {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
repositories {
|
||||
google {
|
||||
content {
|
||||
includeGroupByRegex("com\\.android.*")
|
||||
includeGroupByRegex("com\\.google.*")
|
||||
includeGroupByRegex("androidx.*")
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "Xash3D FWGS"
|
||||
|
||||
@@ -168,11 +168,11 @@ Default build-depended cvar and constant values
|
||||
#define DEFAULT_M_IGNORE "1"
|
||||
#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
|
||||
// You need add library loading code to library.c when adding new platform
|
||||
#define XASH_INTERNAL_GAMELIBS
|
||||
#endif // XASH_ANDROID || XASH_IOS || XASH_EMSCRIPTEN
|
||||
#endif // XASH_IOS
|
||||
|
||||
// Defaults
|
||||
#ifndef DEFAULT_TOUCH_ENABLE
|
||||
|
||||
@@ -226,32 +226,29 @@ void COM_GetCommonLibraryPath( ECommonLibraryType eLibType, char *out, size_t si
|
||||
case LIBRARY_GAMEUI:
|
||||
if( COM_CheckStringEmpty( host.menulib ))
|
||||
{
|
||||
Q_strncpy( out, host.menulib, size );
|
||||
}
|
||||
else
|
||||
{
|
||||
COM_GenerateClientLibraryPath( "menu", out, size );
|
||||
if( host.menulib[0] == '@' )
|
||||
COM_GenerateClientLibraryPath( host.menulib + 1, out, size );
|
||||
else Q_strncpy( out, host.menulib, size );
|
||||
}
|
||||
else COM_GenerateClientLibraryPath( "menu", out, size );
|
||||
break;
|
||||
case LIBRARY_CLIENT:
|
||||
if( COM_CheckStringEmpty( host.clientlib ))
|
||||
{
|
||||
Q_strncpy( out, host.clientlib, size );
|
||||
}
|
||||
else
|
||||
{
|
||||
COM_GenerateClientLibraryPath( "client", out, size );
|
||||
if( host.clientlib[0] == '@' )
|
||||
COM_GenerateClientLibraryPath( host.clientlib + 1, out, size );
|
||||
else Q_strncpy( out, host.clientlib, size );
|
||||
}
|
||||
else COM_GenerateClientLibraryPath( "client", out, size );
|
||||
break;
|
||||
case LIBRARY_SERVER:
|
||||
if( COM_CheckStringEmpty( host.gamedll ))
|
||||
{
|
||||
Q_strncpy( out, host.gamedll, size );
|
||||
}
|
||||
else
|
||||
{
|
||||
COM_GenerateServerLibraryPath( out, size );
|
||||
if( host.gamedll[0] == '@' )
|
||||
COM_GenerateClientLibraryPath( host.gamedll + 1, out, size );
|
||||
else Q_strncpy( out, host.gamedll, size );
|
||||
}
|
||||
else COM_GenerateServerLibraryPath( out, size );
|
||||
break;
|
||||
default:
|
||||
ASSERT( 0 );
|
||||
|
||||
@@ -19,44 +19,37 @@ GNU General Public License for more details.
|
||||
#include "platform/android/lib_android.h"
|
||||
#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];
|
||||
int i;
|
||||
void *pHandle = NULL;
|
||||
const char *libdir[2], *name = COM_FileWithoutPath( path );
|
||||
char fullpath[MAX_SYSPATH];
|
||||
void *handle;
|
||||
|
||||
libdir[0] = getenv("XASH3D_GAMELIBDIR");
|
||||
libdir[1] = getenv("XASH3D_ENGLIBDIR");
|
||||
libdir[0] = getenv( "XASH3D_GAMELIBDIR" ); // TODO: remove this once distributing games from APKs will be deprecated
|
||||
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] )
|
||||
continue;
|
||||
|
||||
Q_snprintf( path, MAX_SYSPATH, "%s/lib%s."OS_LIB_EXT, libdir[i], dllname );
|
||||
pHandle = dlopen( path, RTLD_NOW );
|
||||
if( pHandle )
|
||||
return pHandle;
|
||||
Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", libdir[i], p );
|
||||
|
||||
handle = dlopen( fullpath, RTLD_NOW );
|
||||
|
||||
if( handle )
|
||||
return handle;
|
||||
|
||||
COM_PushLibraryError( dlerror() );
|
||||
}
|
||||
|
||||
// HACKHACK: keep old behaviour for compatibility
|
||||
if( Q_strstr( dllname, "." OS_LIB_EXT ) || Q_strstr( dllname, "/" ))
|
||||
{
|
||||
pHandle = dlopen( dllname, RTLD_NOW );
|
||||
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;
|
||||
}
|
||||
|
||||
// find in system search path, that includes our APK
|
||||
handle = dlopen( name, RTLD_NOW );
|
||||
if( handle )
|
||||
return handle;
|
||||
COM_PushLibraryError( dlerror() );
|
||||
|
||||
return NULL;
|
||||
|
||||
@@ -19,10 +19,9 @@ def configure(conf):
|
||||
if conf.env.DEST_OS == 'android':
|
||||
conf.check_cc(lib='android')
|
||||
|
||||
# remove lib prefix for other systems than Android
|
||||
if conf.env.DEST_OS != 'android' or conf.env.TERMUX:
|
||||
if conf.env.cxxshlib_PATTERN.startswith('lib'):
|
||||
conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:]
|
||||
# remove lib prefix
|
||||
if conf.env.cxxshlib_PATTERN.startswith('lib'):
|
||||
conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:]
|
||||
|
||||
if conf.check_cc(fragment=MEMFD_CREATE_TEST, msg='Checking for memfd_create', mandatory=False):
|
||||
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
|
||||
|
||||
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 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
|
||||
|
||||
$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-key-alias androiddebugkey \
|
||||
--ks-pass pass:android \
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
cd $GITHUB_WORKSPACE
|
||||
|
||||
ANDROID_COMMANDLINE_TOOLS_VER="11076708"
|
||||
ANDROID_BUILD_TOOLS_VER="34.0.0"
|
||||
ANDROID_PLATFORM_VER="android-34"
|
||||
ANDROID_COMMANDLINE_TOOLS_VER="13114758"
|
||||
ANDROID_BUILD_TOOLS_VER="36.0.0"
|
||||
ANDROID_PLATFORM_VER="android-35"
|
||||
ANDROID_NDK_VERSION="28.2.13676358"
|
||||
|
||||
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
|
||||
export JAVA_HOME=$GITHUB_WORKSPACE/jdk-17.0.7+7
|
||||
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.15+6
|
||||
export PATH=$PATH:$JAVA_HOME/bin
|
||||
|
||||
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"
|
||||
pushd 3rdparty
|
||||
@@ -35,4 +36,4 @@ popd
|
||||
|
||||
echo "Download all needed tools and Android NDK"
|
||||
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_UNIFIED_SYSROOT_MIN = 15
|
||||
ANDROID_NDK_SYSROOT_FLAG_MAX = 19 # latest NDK that need --sysroot flag
|
||||
ANDROID_NDK_BUGGED_LINKER_MAX = 22
|
||||
ANDROID_NDK_API_MIN = {
|
||||
10: 3,
|
||||
19: 16,
|
||||
@@ -354,6 +355,12 @@ class Android:
|
||||
else: linkflags += ['-no-canonical-prefixes']
|
||||
|
||||
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
|
||||
|
||||
def ldflags(self):
|
||||
|
||||
4
wscript
4
wscript
@@ -128,7 +128,7 @@ REFDLLS = [
|
||||
]
|
||||
|
||||
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')
|
||||
|
||||
@@ -226,7 +226,7 @@ def configure(conf):
|
||||
if conf.env.COMPILER_CC == 'msvc':
|
||||
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.CONSOLE_SUBSYSTEM = 'CONSOLE'
|
||||
|
||||
Reference in New Issue
Block a user