Add 'android/' from commit 'add509bf9ebaa488e2afe2cdc158a0e6725cd654'
git-subtree-dir: android git-subtree-mainline:b9f4f1eba4
git-subtree-split:add509bf9e
14
android/.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
.gradle/
|
||||
build/
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
.idea/
|
||||
local.properties
|
||||
.project
|
||||
.classpath
|
||||
.gradle
|
||||
.settings
|
||||
release/
|
||||
*.hprof
|
||||
.vscode/
|
||||
*.bak
|
11
android/.gitmodules
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
[submodule "hlsdk-portable"]
|
||||
path = app/src/main/cpp/hlsdk-portable
|
||||
url = https://github.com/FWGS/hlsdk-portable
|
||||
branch = mobile_hacks
|
||||
[submodule "xash3d-fwgs"]
|
||||
path = app/src/main/cpp/xash3d-fwgs
|
||||
url = https://github.com/FWGS/xash3d-fwgs
|
||||
[submodule "SDL"]
|
||||
path = app/src/main/cpp/SDL
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
branch = release-2.24.1
|
3
android/Gemfile
Normal file
|
@ -0,0 +1,3 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
221
android/Gemfile.lock
Normal file
|
@ -0,0 +1,221 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.5)
|
||||
rexml
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.666.0)
|
||||
aws-sdk-core (3.168.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.59.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.117.1)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.5.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.4)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.94.0)
|
||||
faraday (1.10.2)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.214.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.31.0)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-apis-core (0.9.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.16.0)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.12.0)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-apis-storage_v1 (0.19.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.3.0)
|
||||
google-cloud-storage (1.44.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.19.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.3.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.2)
|
||||
json (2.6.2)
|
||||
jwt (2.5.0)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.1)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.6.0)
|
||||
public_suffix (5.0.0)
|
||||
rake (13.0.6)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.3.2)
|
||||
strscan
|
||||
rouge (2.0.7)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.17.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
CFPropertyList
|
||||
naturally
|
||||
strscan (3.1.0)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unf_ext (0.0.8.2-x64-mingw-ucrt)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.19.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
x64-mingw-ucrt
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.26
|
9
android/README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Xash3D Android
|
||||
|
||||

|
||||
|
||||
This repo is intended only to store Android launcher and wrapper code for
|
||||
[Xash3D FWGS](https://github.com/FWGS/xash3d-fwgs).
|
||||
Issues must be sent to Xash3D FWGS repository.
|
||||
|
||||
Translations to Android launcher are done through [Weblate](https://l10n.mentality.rip).
|
130
android/app/build.gradle.kts
Normal file
|
@ -0,0 +1,130 @@
|
|||
import java.time.LocalDateTime
|
||||
import java.time.Month
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "su.xash.engine"
|
||||
ndkVersion = "26.1.10909125"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "su.xash"
|
||||
applicationIdSuffix = "engine"
|
||||
versionName = "0.21"
|
||||
versionCode = getBuildNum()
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
compileSdk = 34
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
arguments("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.22.1"
|
||||
path = file("${project.projectDir}/src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
isDebuggable = true
|
||||
applicationIdSuffix = ".test"
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
|
||||
register("asan") {
|
||||
initWith(getByName("debug"))
|
||||
}
|
||||
|
||||
register("continuous") {
|
||||
initWith(getByName("release"))
|
||||
applicationIdSuffix = ".test"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("main") {
|
||||
assets.srcDir("${project.projectDir}/src/main/cpp/xash3d-fwgs/3rdparty/extras/xash-extras")
|
||||
assets.srcDir("${project.projectDir}/../moddb")
|
||||
java.srcDir("${project.projectDir}/src/main/cpp/SDL/android-project/app/src/main/java")
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
androidResources {
|
||||
noCompress += ""
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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("com.madgag.spongycastle:prov:1.58.0.0")
|
||||
implementation("in.dragonbra:javasteam:1.2.0")
|
||||
|
||||
implementation("ch.acra:acra-http:5.11.2")
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
21
android/app/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
# 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
|
5
android/app/src/asan/res/values/strings.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?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>
|
26
android/app/src/asan/resources/lib/arm64-v8a/wrap.sh
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
#!/system/bin/sh
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
cmd=$1
|
||||
shift
|
||||
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
|
||||
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
|
||||
# because it will preload a 32-bit ASan runtime.
|
||||
# https://github.com/android/ndk/issues/1744
|
||||
os_version=$(getprop ro.build.version.sdk)
|
||||
if [ "$os_version" -eq "27" ]; then
|
||||
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
elif [ "$os_version" -eq "28" ]; then
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
else
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
|
||||
fi
|
||||
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
|
||||
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
|
||||
if [ -f "$HERE/libc++_shared.so" ]; then
|
||||
# Workaround for https://github.com/android-ndk/ndk/issues/988.
|
||||
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
|
||||
else
|
||||
export LD_PRELOAD="$ASAN_LIB"
|
||||
fi
|
||||
exec $cmd
|
26
android/app/src/asan/resources/lib/armeabi-v7a/wrap.sh
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
#!/system/bin/sh
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
cmd=$1
|
||||
shift
|
||||
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
|
||||
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
|
||||
# because it will preload a 32-bit ASan runtime.
|
||||
# https://github.com/android/ndk/issues/1744
|
||||
os_version=$(getprop ro.build.version.sdk)
|
||||
if [ "$os_version" -eq "27" ]; then
|
||||
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
elif [ "$os_version" -eq "28" ]; then
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
else
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
|
||||
fi
|
||||
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
|
||||
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
|
||||
if [ -f "$HERE/libc++_shared.so" ]; then
|
||||
# Workaround for https://github.com/android-ndk/ndk/issues/988.
|
||||
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
|
||||
else
|
||||
export LD_PRELOAD="$ASAN_LIB"
|
||||
fi
|
||||
exec $cmd
|
26
android/app/src/asan/resources/lib/x86/wrap.sh
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
#!/system/bin/sh
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
cmd=$1
|
||||
shift
|
||||
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
|
||||
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
|
||||
# because it will preload a 32-bit ASan runtime.
|
||||
# https://github.com/android/ndk/issues/1744
|
||||
os_version=$(getprop ro.build.version.sdk)
|
||||
if [ "$os_version" -eq "27" ]; then
|
||||
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
elif [ "$os_version" -eq "28" ]; then
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
else
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
|
||||
fi
|
||||
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
|
||||
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
|
||||
if [ -f "$HERE/libc++_shared.so" ]; then
|
||||
# Workaround for https://github.com/android-ndk/ndk/issues/988.
|
||||
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
|
||||
else
|
||||
export LD_PRELOAD="$ASAN_LIB"
|
||||
fi
|
||||
exec $cmd
|
26
android/app/src/asan/resources/lib/x86_64/wrap.sh
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
#!/system/bin/sh
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
cmd=$1
|
||||
shift
|
||||
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
|
||||
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
|
||||
# because it will preload a 32-bit ASan runtime.
|
||||
# https://github.com/android/ndk/issues/1744
|
||||
os_version=$(getprop ro.build.version.sdk)
|
||||
if [ "$os_version" -eq "27" ]; then
|
||||
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
elif [ "$os_version" -eq "28" ]; then
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
else
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
|
||||
fi
|
||||
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
|
||||
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
|
||||
if [ -f "$HERE/libc++_shared.so" ]; then
|
||||
# Workaround for https://github.com/android-ndk/ndk/issues/988.
|
||||
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
|
||||
else
|
||||
export LD_PRELOAD="$ASAN_LIB"
|
||||
fi
|
||||
exec $cmd
|
5
android/app/src/continuous/res/values/strings.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?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>
|
5
android/app/src/debug/res/values/strings.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?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>
|
101
android/app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?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"/>
|
||||
|
||||
<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>
|
41
android/app/src/main/cpp/CMakeLists.txt
Normal file
|
@ -0,0 +1,41 @@
|
|||
cmake_minimum_required(VERSION 3.6)
|
||||
|
||||
project(XASH_ANDROID)
|
||||
|
||||
# armeabi-v7a requires cpufeatures library
|
||||
include(AndroidNdkModules)
|
||||
android_ndk_import_module_cpufeatures()
|
||||
|
||||
find_package(PythonInterp 2.7 REQUIRED)
|
||||
|
||||
get_filename_component(C_COMPILER_ID ${CMAKE_C_COMPILER} NAME_WE)
|
||||
get_filename_component(CXX_COMPILER_ID ${CMAKE_CXX_COMPILER} NAME_WE)
|
||||
|
||||
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
|
||||
set(BUILD_TYPE "debug")
|
||||
else()
|
||||
set(BUILD_TYPE "release")
|
||||
endif()
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
|
||||
set(WAF_CC "${CMAKE_C_COMPILER} --target=${CMAKE_C_COMPILER_TARGET}")
|
||||
set(WAF_CXX "${CMAKE_CXX_COMPILER} --target=${CMAKE_CXX_COMPILER_TARGET}")
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
CC=${WAF_CC} CXX=${WAF_CXX}
|
||||
AR=${CMAKE_AR} STRIP=${CMAKE_STRIP}
|
||||
${PYTHON_EXECUTABLE} waf configure -vvv -T ${BUILD_TYPE} cmake
|
||||
--check-c-compiler=${C_COMPILER_ID} --check-cxx-compiler=${CXX_COMPILER_ID}
|
||||
-s "${CMAKE_CURRENT_SOURCE_DIR}/SDL" --skip-sdl2-sanity-check
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/xash3d-fwgs"
|
||||
)
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P MATCHES "8")
|
||||
set(64BIT ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
add_subdirectory("hlsdk-portable")
|
||||
add_subdirectory("SDL")
|
||||
add_subdirectory("xash3d-fwgs")
|
||||
add_subdirectory("xash3d-fwgs/3rdparty/mainui")
|
1
android/app/src/main/cpp/SDL
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 2eef7ca475decd2b864214cdbfe72b143b16d459
|
1
android/app/src/main/cpp/hlsdk-portable
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 0d8a19fd82758746cc41af5e18946a9410e4533f
|
1
android/app/src/main/cpp/xash3d-fwgs
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 1c84a5c8ade7cfac17d6b558669d4f721a0bf11f
|
|
@ -0,0 +1,49 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package su.xash.engine
|
||||
|
||||
class DedicatedActivity {
|
||||
}
|
11
android/app/src/main/java/su/xash/engine/DedicatedService.kt
Normal file
|
@ -0,0 +1,11 @@
|
|||
package su.xash.engine
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
38
android/app/src/main/java/su/xash/engine/MainActivity.kt
Normal file
|
@ -0,0 +1,38 @@
|
|||
package su.xash.engine
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.PersistableBundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.navigateUp
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import su.xash.engine.databinding.ActivityMainBinding
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
private lateinit var navController: NavController
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
28
android/app/src/main/java/su/xash/engine/MainApplication.kt
Normal file
|
@ -0,0 +1,28 @@
|
|||
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)
|
||||
|
||||
if (!BuildConfig.DEBUG) {
|
||||
initAcra {
|
||||
buildConfigClass = BuildConfig::class.java
|
||||
reportFormat = StringFormat.JSON
|
||||
|
||||
httpSender {
|
||||
uri = "http://bodis.pp.ua:5000/report"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// enable strict mode to detect memory leaks etc.
|
||||
StrictMode.enableDefaults();
|
||||
}
|
||||
}
|
||||
}
|
138
android/app/src/main/java/su/xash/engine/XashActivity.java
Normal file
|
@ -0,0 +1,138 @@
|
|||
package su.xash.engine;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings.Secure;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
public class XashActivity extends SDLActivity {
|
||||
private boolean mUseVolumeKeys;
|
||||
private String mPackageName;
|
||||
private static final String TAG = "XashActivity";
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
AndroidBug5497Workaround.assistActivity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[]{"SDL2", "xash"};
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
private String loadAndroidID() {
|
||||
return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCallingPackage() {
|
||||
if (mPackageName != null) {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
return super.getCallingPackage();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
return am;
|
||||
}
|
||||
|
||||
private String[] getAssetsList(boolean isEngine, String path) {
|
||||
AssetManager am = getAssets(isEngine);
|
||||
|
||||
try {
|
||||
return am.list(path);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return new String[]{};
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
|
||||
mPackageName = getIntent().getStringExtra("package");
|
||||
|
||||
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 = "-dev 2 -log";
|
||||
return argv.split(" ");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package su.xash.engine.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.databinding.CardGameBinding
|
||||
import su.xash.engine.model.Game
|
||||
import su.xash.engine.ui.library.LibraryViewModel
|
||||
|
||||
|
||||
class GameAdapter(private val libraryViewModel: LibraryViewModel) :
|
||||
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 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
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem.basedir.name == newItem.basedir.name && oldItem.installed == newItem.installed
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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.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)
|
||||
}
|
||||
|
||||
root.setOnClickListener { libraryViewModel.startEngine(it.context, game) }
|
||||
launchButton.setOnClickListener {
|
||||
libraryViewModel.startEngine(it.context, game)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
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.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
|
||||
|
||||
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
|
||||
|
||||
var bgLayout = file.findFile("resource")?.findFile("HD_BackgroundLayout.txt")
|
||||
if (bgLayout == null) {
|
||||
bgLayout = file.findFile("resource")?.findFile("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!!)
|
||||
|
||||
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
|
||||
x += bmpImage.width
|
||||
height = bmpImage.height
|
||||
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
val width = TGAReader.getWidth(buffer)
|
||||
val height = TGAReader.getHeight(buffer)
|
||||
|
||||
return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888)
|
||||
}
|
||||
}
|
||||
}
|
129
android/app/src/main/java/su/xash/engine/model/Game.kt
Normal file
|
@ -0,0 +1,129 @@
|
|||
package su.xash.engine.model
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import su.xash.engine.XashActivity
|
||||
|
||||
|
||||
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
|
||||
|
||||
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) }
|
||||
|
||||
basedir.findFile(iconName)
|
||||
?.let { icon = MediaStore.Images.Media.getBitmap(ctx.contentResolver, it.uri) }
|
||||
|
||||
try {
|
||||
cover = BackgroundBitmap.createBackground(ctx, 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", "-dev 2 -log"))
|
||||
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
|
||||
//.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('"')
|
||||
|
||||
if (k == "title" || k == "game") title = v
|
||||
if (k == "icon") iconName = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPackageName(): String? {
|
||||
// return if (mDbEntry != null) {
|
||||
// mDbEntry.getPackageName()
|
||||
// } else 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
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getGames(ctx: Context, file: DocumentFile): 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return games
|
||||
}
|
||||
|
||||
fun checkIfGamedir(file: DocumentFile): Boolean {
|
||||
file.findFile("liblist.gam")?.let { return true }
|
||||
file.findFile("gameinfo.txt")?.let { return true }
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Intent intent = new Intent("su.xash.engine.MOD");
|
||||
// for (ResolveInfo info : context.getPackageManager()
|
||||
// .queryIntentActivities(intent, PackageManager.GET_META_DATA)) {
|
||||
// String packageName = info.activityInfo.applicationInfo.packageName;
|
||||
// String gameDir = info.activityInfo.applicationInfo.metaData.getString(
|
||||
// "su.xash.engine.gamedir");
|
||||
// Log.d(TAG, "package = " + packageName + " gamedir = " + gameDir);
|
||||
// }
|
||||
|
||||
//public void startEngine(Context context) {
|
||||
// context.startActivity(new Intent(context, XashActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP).putExtra("gamedir", getGameDir()).putExtra("argv", getArguments()).putExtra("usevolume", getVolumeState()).putExtra("gamelibdir", getGameLibDir(context)).putExtra("package", getPackageName()));
|
||||
//}
|
|
@ -0,0 +1,59 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package su.xash.engine.ui.library
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
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 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 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 val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
|
||||
|
||||
val adapter = GameAdapter(libraryViewModel)
|
||||
binding.gamesList.adapter = adapter
|
||||
|
||||
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }
|
||||
|
||||
libraryViewModel.isReloading.observe(viewLifecycleOwner) {
|
||||
binding.swipeRefresh.isRefreshing = it
|
||||
}
|
||||
|
||||
libraryViewModel.installedGames.observe(viewLifecycleOwner) {
|
||||
(binding.gamesList.adapter as GameAdapter).submitList(it)
|
||||
}
|
||||
|
||||
libraryViewModel.workInfos.observe(viewLifecycleOwner) {
|
||||
libraryViewModel.refreshDownloads(requireContext())
|
||||
}
|
||||
|
||||
libraryViewModel.downloads.observe(viewLifecycleOwner) {
|
||||
libraryViewModel.reloadGames(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_library, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_browse -> {
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW).setDataAndType(
|
||||
null, "vnd.android.document/directory"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
R.id.action_install -> {
|
||||
findNavController().navigate(R.id.action_libraryFragment_to_setupFragment)
|
||||
}
|
||||
|
||||
R.id.action_settings -> {
|
||||
findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
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 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"
|
||||
|
||||
class LibraryViewModel(application: Application) : AndroidViewModel(application) {
|
||||
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)
|
||||
|
||||
private val workManager = WorkManager.getInstance(application.applicationContext)
|
||||
val workInfos: LiveData<List<WorkInfo>> = workManager.getWorkInfosByTagLiveData(TAG_INSTALL)
|
||||
|
||||
val selectedItem: LiveData<Game> get() = _selectedItem
|
||||
private val _selectedItem = MutableLiveData<Game>()
|
||||
|
||||
val appPreferences = application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
||||
val modDb: ModDatabase
|
||||
|
||||
init {
|
||||
modDb = application.assets.open(ModDatabase.getFilename()).use { ModDatabase(it) }
|
||||
reloadGames(application.applicationContext)
|
||||
}
|
||||
|
||||
fun reloadGames(ctx: Context) {
|
||||
if (isReloading.value == true) {
|
||||
return
|
||||
}
|
||||
_isReloading.value = true
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package su.xash.engine.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.adapters.GameAdapter
|
||||
import su.xash.engine.databinding.FragmentAppSettingsBinding
|
||||
import su.xash.engine.databinding.FragmentGameSettingsBinding
|
||||
import su.xash.engine.databinding.FragmentLibraryBinding
|
||||
|
||||
class AppSettingsFragment : Fragment() {
|
||||
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 onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
childFragmentManager.beginTransaction()
|
||||
.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package su.xash.engine.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import su.xash.engine.BuildConfig
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.model.Game
|
||||
|
||||
class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.sharedPreferencesName = "app_preferences";
|
||||
setPreferencesFromResource(R.xml.app_preferences, rootKey);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package su.xash.engine.ui.settings
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
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 androidx.core.view.MenuProvider
|
||||
import androidx.core.view.get
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
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 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 onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val game = libraryViewModel.selectedItem.value!!
|
||||
|
||||
binding.gameCard.apply {
|
||||
gameTitle.text = game.title
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
buttonsContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package su.xash.engine.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import su.xash.engine.BuildConfig
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package su.xash.engine.ui.setup
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
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 androidx.viewbinding.ViewBinding
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.databinding.FragmentLibraryBinding
|
||||
import su.xash.engine.databinding.FragmentSetupBinding
|
||||
import su.xash.engine.databinding.PageLocationBinding
|
||||
import su.xash.engine.databinding.PageWelcomeBinding
|
||||
import su.xash.engine.ui.library.LibraryViewModel
|
||||
import su.xash.engine.ui.setup.pages.LocationPageFragment
|
||||
import su.xash.engine.ui.setup.pages.WelcomePageFragment
|
||||
import su.xash.engine.workers.FileCopyWorker
|
||||
|
||||
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]
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
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 androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import su.xash.engine.MainApplication
|
||||
import su.xash.engine.model.Game
|
||||
import su.xash.engine.workers.FileCopyWorker
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
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 androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.databinding.PageLocationBinding
|
||||
import su.xash.engine.databinding.PageWelcomeBinding
|
||||
import su.xash.engine.ui.library.LibraryViewModel
|
||||
import su.xash.engine.ui.setup.SetupFragment
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
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 androidx.viewbinding.ViewBinding
|
||||
import su.xash.engine.databinding.PageLocationBinding
|
||||
import su.xash.engine.databinding.PageWelcomeBinding
|
||||
import su.xash.engine.ui.setup.SetupFragment
|
||||
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
|
||||
}
|
||||
}
|
589
android/app/src/main/java/su/xash/engine/util/TGAReader.java
Normal file
|
@ -0,0 +1,589 @@
|
|||
/**
|
||||
* TGAReader.java
|
||||
* <p>
|
||||
* Copyright (c) 2014 Kenji Sasaki
|
||||
* Released under the MIT license.
|
||||
* https://github.com/npedotnet/TGAReader/blob/master/LICENSE
|
||||
* <p>
|
||||
* English document
|
||||
* https://github.com/npedotnet/TGAReader/blob/master/README.md
|
||||
* <p>
|
||||
* Japanese document
|
||||
* http://3dtech.jp/wiki/index.php?TGAReader
|
||||
*/
|
||||
|
||||
package su.xash.engine.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class TGAReader {
|
||||
|
||||
public static final Order ARGB = new Order(16, 8, 0, 24);
|
||||
public static final Order ABGR = new Order(0, 8, 16, 24);
|
||||
|
||||
public static int getWidth(byte[] buffer) {
|
||||
return (buffer[12] & 0xFF) | (buffer[13] & 0xFF) << 8;
|
||||
}
|
||||
|
||||
public static int getHeight(byte[] buffer) {
|
||||
return (buffer[14] & 0xFF) | (buffer[15] & 0xFF) << 8;
|
||||
}
|
||||
|
||||
public static int[] read(byte[] buffer, Order order) throws IOException {
|
||||
|
||||
// header
|
||||
// int idFieldLength = buffer[0] & 0xFF;
|
||||
// int colormapType = buffer[1] & 0xFF;
|
||||
int type = buffer[2] & 0xFF;
|
||||
int colormapOrigin = (buffer[3] & 0xFF) | (buffer[4] & 0xFF) << 8;
|
||||
int colormapLength = (buffer[5] & 0xFF) | (buffer[6] & 0xFF) << 8;
|
||||
int colormapDepth = buffer[7] & 0xFF;
|
||||
// int originX = (buffer[8] & 0xFF) | (buffer[9] & 0xFF) << 8; // unsupported
|
||||
// int originY = (buffer[10] & 0xFF) | (buffer[11] & 0xFF) << 8; // unsupported
|
||||
int width = getWidth(buffer);
|
||||
int height = getHeight(buffer);
|
||||
int depth = buffer[16] & 0xFF;
|
||||
int descriptor = buffer[17] & 0xFF;
|
||||
|
||||
int[] pixels;
|
||||
|
||||
// data
|
||||
switch (type) {
|
||||
case COLORMAP: {
|
||||
int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength;
|
||||
pixels = createPixelsFromColormap(width, height, colormapDepth, buffer, imageDataOffset, buffer, colormapOrigin, descriptor, order);
|
||||
}
|
||||
break;
|
||||
case RGB:
|
||||
pixels = createPixelsFromRGB(width, height, depth, buffer, 18, descriptor, order);
|
||||
break;
|
||||
case GRAYSCALE:
|
||||
pixels = createPixelsFromGrayscale(width, height, depth, buffer, 18, descriptor, order);
|
||||
break;
|
||||
case COLORMAP_RLE: {
|
||||
int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength;
|
||||
byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, imageDataOffset);
|
||||
pixels = createPixelsFromColormap(width, height, colormapDepth, decodeBuffer, 0, buffer, colormapOrigin, descriptor, order);
|
||||
}
|
||||
break;
|
||||
case RGB_RLE: {
|
||||
byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18);
|
||||
pixels = createPixelsFromRGB(width, height, depth, decodeBuffer, 0, descriptor, order);
|
||||
}
|
||||
break;
|
||||
case GRAYSCALE_RLE: {
|
||||
byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18);
|
||||
pixels = createPixelsFromGrayscale(width, height, depth, decodeBuffer, 0, descriptor, order);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported image type: " + type);
|
||||
}
|
||||
|
||||
return pixels;
|
||||
|
||||
}
|
||||
|
||||
private static final int COLORMAP = 1;
|
||||
private static final int RGB = 2;
|
||||
private static final int GRAYSCALE = 3;
|
||||
private static final int COLORMAP_RLE = 9;
|
||||
private static final int RGB_RLE = 10;
|
||||
private static final int GRAYSCALE_RLE = 11;
|
||||
|
||||
private static final int RIGHT_ORIGIN = 0x10;
|
||||
private static final int UPPER_ORIGIN = 0x20;
|
||||
|
||||
private static byte[] decodeRLE(int width, int height, int depth, byte[] buffer, int offset) {
|
||||
int elementCount = depth / 8;
|
||||
byte[] elements = new byte[elementCount];
|
||||
int decodeBufferLength = elementCount * width * height;
|
||||
byte[] decodeBuffer = new byte[decodeBufferLength];
|
||||
int decoded = 0;
|
||||
while (decoded < decodeBufferLength) {
|
||||
int packet = buffer[offset++] & 0xFF;
|
||||
if ((packet & 0x80) != 0) { // RLE
|
||||
for (int i = 0; i < elementCount; i++) {
|
||||
elements[i] = buffer[offset++];
|
||||
}
|
||||
int count = (packet & 0x7F) + 1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int j = 0; j < elementCount; j++) {
|
||||
decodeBuffer[decoded++] = elements[j];
|
||||
}
|
||||
}
|
||||
} else { // RAW
|
||||
int count = (packet + 1) * elementCount;
|
||||
for (int i = 0; i < count; i++) {
|
||||
decodeBuffer[decoded++] = buffer[offset++];
|
||||
}
|
||||
}
|
||||
}
|
||||
return decodeBuffer;
|
||||
}
|
||||
|
||||
private static int[] createPixelsFromColormap(int width, int height, int depth, byte[] bytes, int offset, byte[] palette, int colormapOrigin, int descriptor, Order order) throws IOException {
|
||||
int[] pixels;
|
||||
int rs = order.redShift;
|
||||
int gs = order.greenShift;
|
||||
int bs = order.blueShift;
|
||||
int as = order.alphaShift;
|
||||
switch (depth) {
|
||||
case 24:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 3 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * i + (width - j - 1)] = color;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 3 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 3 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * i + j] = color;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 3 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * (height - i - 1) + j] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 32:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 4 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = palette[index + 3] & 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * i + (width - j - 1)] = color;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 4 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = palette[index + 3] & 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 4 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = palette[index + 3] & 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * i + j] = color;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 4 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = palette[index + 3] & 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * (height - i - 1) + j] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported depth:" + depth);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private static int[] createPixelsFromRGB(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException {
|
||||
int[] pixels;
|
||||
int rs = order.redShift;
|
||||
int gs = order.greenShift;
|
||||
int bs = order.blueShift;
|
||||
int as = order.alphaShift;
|
||||
switch (depth) {
|
||||
case 24:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 3 * width * i + 3 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * i + (width - j - 1)] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 3 * width * i + 3 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 3 * width * i + 3 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * i + j] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 3 * width * i + 3 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * (height - i - 1) + j] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 32:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 4 * width * i + 4 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = bytes[index + 3] & 0xFF;
|
||||
pixels[width * i + (width - j - 1)] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 4 * width * i + 4 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = bytes[index + 3] & 0xFF;
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 4 * width * i + 4 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = bytes[index + 3] & 0xFF;
|
||||
pixels[width * i + j] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 4 * width * i + 4 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = bytes[index + 3] & 0xFF;
|
||||
pixels[width * (height - i - 1) + j] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported depth:" + depth);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private static int[] createPixelsFromGrayscale(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException {
|
||||
int[] pixels;
|
||||
int rs = order.redShift;
|
||||
int gs = order.greenShift;
|
||||
int bs = order.blueShift;
|
||||
int as = order.alphaShift;
|
||||
switch (depth) {
|
||||
case 8:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + width * i + j] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * i + (width - j - 1)] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + width * i + j] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + width * i + j] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * i + j] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + width * i + j] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * (height - i - 1) + j] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 16:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
|
||||
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
|
||||
pixels[width * i + (width - j - 1)] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
|
||||
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
|
||||
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
|
||||
pixels[width * i + j] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
|
||||
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
|
||||
pixels[width * (height - i - 1) + j] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported depth:" + depth);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private TGAReader() {
|
||||
}
|
||||
|
||||
public static final class Order {
|
||||
Order(int redShift, int greenShift, int blueShift, int alphaShift) {
|
||||
this.redShift = redShift;
|
||||
this.greenShift = greenShift;
|
||||
this.blueShift = blueShift;
|
||||
this.alphaShift = alphaShift;
|
||||
}
|
||||
|
||||
public int redShift;
|
||||
public int greenShift;
|
||||
public int blueShift;
|
||||
public int alphaShift;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package su.xash.engine.workers
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import android.util.Log
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import su.xash.engine.model.Game
|
||||
import java.io.FileInputStream
|
||||
|
||||
const val KEY_FILE_URI = "KEY_FILE_URI"
|
||||
|
||||
class FileCopyWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
|
||||
companion object {
|
||||
const val Input = "Input"
|
||||
}
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
withContext(Dispatchers.IO) {
|
||||
val fileUri = inputData.getString(KEY_FILE_URI)
|
||||
setProgress(workDataOf(Input to fileUri))
|
||||
|
||||
val target = DocumentFile.fromFile(applicationContext.getExternalFilesDir(null)!!)
|
||||
val source = DocumentFile.fromTreeUri(applicationContext, Uri.parse(fileUri))
|
||||
|
||||
source?.copyDirTo(applicationContext, target) ?: return@withContext Result.failure()
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
|
||||
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!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun DocumentFile.copyDirTo(ctx: Context, dir: DocumentFile) {
|
||||
val outDir = dir.createDirectory(name!!)!!
|
||||
|
||||
listFiles().forEach {
|
||||
if (it.isDirectory) {
|
||||
it.copyDirTo(ctx, outDir)
|
||||
} else {
|
||||
it.copyFileTo(ctx, outDir)
|
||||
}
|
||||
}
|
||||
}
|
10
android/app/src/main/res/drawable/ic_baseline_add_24.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</vector>
|
10
android/app/src/main/res/drawable/ic_baseline_delete_24.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z" />
|
||||
</vector>
|
5
android/app/src/main/res/drawable/ic_baseline_key_24.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<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>
|
|
@ -0,0 +1,5 @@
|
|||
<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>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8,5v14l11,-7z" />
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z" />
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,4H4C2.89,4 2,4.9 2,6v12c0,1.1 0.89,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.11,4 20,4zM20,18H4V8h16V18zM18,17h-6v-2h6V17zM7.5,17l-1.41,-1.41L8.67,13l-2.59,-2.59L7.5,9l4,4L7.5,17z" />
|
||||
</vector>
|
15
android/app/src/main/res/drawable/steam_button_gradient.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?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>
|
9
android/app/src/main/res/drawable/steam_icon.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<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>
|
33
android/app/src/main/res/drawable/steam_logo.xml
Normal file
|
@ -0,0 +1,33 @@
|
|||
<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>
|
35
android/app/src/main/res/layout/activity_main.xml
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?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">
|
||||
|
||||
<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" />
|
||||
|
||||
</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>
|
111
android/app/src/main/res/layout/card_game.xml
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?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">
|
||||
|
||||
<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" />
|
||||
|
||||
<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">
|
||||
|
||||
<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.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">
|
||||
|
||||
<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/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>
|
30
android/app/src/main/res/layout/edit_text_preference.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?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.textfield.TextInputLayout-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="parent">-->
|
||||
|
||||
<!-- <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/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>
|
14
android/app/src/main/res/layout/fragment_app_settings.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?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">
|
||||
|
||||
<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>
|
39
android/app/src/main/res/layout/fragment_game_settings.xml
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?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">
|
||||
|
||||
<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"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
|
||||
</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>
|
23
android/app/src/main/res/layout/fragment_library.xml
Normal file
|
@ -0,0 +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">
|
||||
|
||||
<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.swiperefreshlayout.widget.SwipeRefreshLayout>
|
11
android/app/src/main/res/layout/fragment_setup.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?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>
|
58
android/app/src/main/res/layout/fragment_steam_login.xml
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?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>
|
19
android/app/src/main/res/layout/list_preference.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?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"/>
|
||||
|
||||
<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>
|
51
android/app/src/main/res/layout/page_location.xml
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?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>
|
51
android/app/src/main/res/layout/page_welcome.xml
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?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>
|
33
android/app/src/main/res/layout/steam_guard_dialog.xml
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?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>
|
23
android/app/src/main/res/layout/switch_preference.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?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="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"/>
|
||||
|
||||
<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>
|
14
android/app/src/main/res/menu/menu_game_settings.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<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>
|
19
android/app/src/main/res/menu/menu_library.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<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>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="108dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="108dp">
|
||||
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M352.3,335.9l0,64.8l45.7,0l69.9,113l-109.9,171.8l87.3,0l68.4,-103.5l65.5,103.5l90,0l0,-62l-42,-0.7l-70.4,-112.1l109.3,-174.8l-84.1,0l-68.3,106.4l-69,-106.4z"/>
|
||||
|
||||
</vector>
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="108dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="108dp">
|
||||
|
||||
<path android:fillColor="#FF000000" android:pathData="M352.3,335.9l0,64.8l45.7,0l69.9,113l-109.9,171.8l87.3,0l68.4,-103.5l65.5,103.5l90,0l0,-62l-42,-0.7l-70.4,-112.1l109.3,-174.8l-84.1,0l-68.3,106.4l-69,-106.4z"/>
|
||||
|
||||
</vector>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 4.9 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 6.4 KiB |
38
android/app/src/main/res/navigation/nav_graph.xml
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?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>
|
5
android/app/src/main/res/values-es/strings.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="ok">Ok</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
</resources>
|
5
android/app/src/main/res/values-it/strings.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="cancel">Annulla</string>
|
||||
<string name="ok">Ok</string>
|
||||
</resources>
|
33
android/app/src/main/res/values-pt-rBR/strings.xml
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?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>
|
||||
</resources>
|
33
android/app/src/main/res/values-ru/strings.xml
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?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>
|
5
android/app/src/main/res/values-tr/strings.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="ok">Tamam</string>
|
||||
<string name="cancel">İptal</string>
|
||||
</resources>
|
5
android/app/src/main/res/values-uk/strings.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="ok">Ок</string>
|
||||
<string name="cancel">Скасувати</string>
|
||||
</resources>
|
12
android/app/src/main/res/values/colors.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?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>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#F74843</color>
|
||||
</resources>
|
36
android/app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?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>
|
||||
</resources>
|
14
android/app/src/main/res/values/styles.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?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>
|
||||
|
||||
</resources>
|
17
android/app/src/main/res/values/themes.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<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>
|
8
android/app/src/main/res/xml/app_preferences.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?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>
|
42
android/app/src/main/res/xml/game_preferences.xml
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?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="-dev 2 -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="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="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"/>
|
||||
</PreferenceScreen>
|