android: update codebase to use java File API

This commit is contained in:
Bohdan Shulyar
2025-02-26 14:10:19 +02:00
committed by a1batross
parent d87cdd7ff8
commit 4f7a216191
37 changed files with 247 additions and 780 deletions

8
.gitignore vendored
View File

@@ -345,3 +345,11 @@ enc_temp_folder/
# ccls langauge server
.ccls-*
# JetBrains
.idea/
cmake-build-*
# some Android-specific build stuff
3rdparty/SDL
3rdparty/hlsdk-portable

View File

@@ -1,29 +1,29 @@
cmake_minimum_required(VERSION 3.22)
cmake_minimum_required(VERSION 3.22.1)
# Only used to build Android project
project(XASH_ANDROID)
project(xash3d-fwgs-android)
# armeabi-v7a requires cpufeatures library
if(ANDROID)
include_directories(${ANDROID_NDK}/sources/android/cpufeatures)
add_library(cpufeatures ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
target_link_libraries(cpufeatures dl)
endif()
if (ANDROID)
include_directories(${ANDROID_NDK}/sources/android/cpufeatures)
add_library(cpufeatures ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
target_link_libraries(cpufeatures dl)
endif ()
include(FindPython)
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
set(BUILD_TYPE "debug")
set(BUILD_TYPE "debug")
else()
set(BUILD_TYPE "release")
list(APPEND WAF_EXTRA_ARGS --enable-poly-opt --enable-lto --enable-limited-debuginfo)
set(BUILD_TYPE "release")
list(APPEND WAF_EXTRA_ARGS --enable-poly-opt --enable-lto --enable-limited-debuginfo)
endif()
if(ANDROID_ABI STREQUAL "x86")
# HACKHACK: I don't know why but engine gets built as 64-bit binary here
list(APPEND WAF_EXTRA_ARGS -4)
endif()
if (ANDROID_ABI STREQUAL "x86")
# HACKHACK: I don't know why but engine gets built as 64-bit binary here
list(APPEND WAF_EXTRA_ARGS -4)
endif ()
set(CMAKE_VERBOSE_MAKEFILE ON)
@@ -36,15 +36,9 @@ set(WAF_CXX "${CMAKE_CXX_COMPILER} --target=${CMAKE_CXX_COMPILER_TARGET}")
set(WAF ${Python_EXECUTABLE} ${ENGINE_SOURCE_DIR}waf -t ${ENGINE_SOURCE_DIR} -o ${CMAKE_CURRENT_BINARY_DIR}/xash3d-fwgs)
# try to build minimal SDL. Enable features as we're gonna use them
set(SDL_RENDER OFF)
set(SDL_POWER OFF)
set(SDL_VULKAN OFF)
set(SDL_DISKAUDIO OFF)
set(SDL_DUMMYAUDIO OFF)
set(SDL_DUMMYVIDEO OFF)
set(SDL_VULKAN OFF)
set(SDL_OFFSCREEN OFF)
set(SDL_STATIC OFF)
foreach (X IN ITEMS SDL_RENDER SDL_POWER SDL_VULKAN SDL_DISKAUDIO SDL_DUMMYAUDIO SDL_DUMMYVIDEO SDL_VULKAN SDL_OFFSCREEN SDL_STATIC)
set(${X} OFF CACHE BOOL "" FORCE)
endforeach ()
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/SDL" SDL)
include(ExternalProject)

View File

@@ -49,6 +49,12 @@ android {
noCompress += ""
}
packaging {
jniLibs {
keepDebugSymbols.add("**/*.so")
}
}
sourceSets {
getByName("main") {
assets.srcDirs("../../3rdparty/extras/xash-extras")
@@ -56,12 +62,6 @@ android {
}
}
packaging {
jniLibs {
keepDebugSymbols.add("**/*.so")
}
}
buildTypes {
debug {
isMinifyEnabled = false

View File

@@ -1,4 +1,3 @@
package su.xash.engine
class DedicatedActivity {
}
class DedicatedActivity {}

View File

@@ -3,23 +3,20 @@ 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"
}
}
// 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();

View File

@@ -1,10 +1,13 @@
package su.xash.engine;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings.Secure;
import android.util.Log;
import android.view.KeyEvent;
@@ -12,6 +15,8 @@ import android.view.WindowManager;
import org.libsdl.app.SDLActivity;
import su.xash.engine.util.AndroidBug5497Workaround;
public class XashActivity extends SDLActivity {
private boolean mUseVolumeKeys;
private String mPackageName;
@@ -106,11 +111,7 @@ public class XashActivity extends SDLActivity {
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) {
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;
}
}
@@ -131,6 +132,14 @@ public class XashActivity extends SDLActivity {
String pakfile = getIntent().getStringExtra("pakfile");
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);
String basedir = getIntent().getStringExtra("basedir");
if (basedir != null) {
nativeSetenv("XASH3D_BASEDIR", basedir);
} else {
String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/xash";
nativeSetenv("XASH3D_BASEDIR", rootPath);
}
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
mPackageName = getIntent().getStringExtra("package");

View File

@@ -31,7 +31,7 @@ class GameAdapter(private val libraryViewModel: LibraryViewModel) :
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem.basedir.name == newItem.basedir.name && oldItem.installed == newItem.installed
return oldItem.basedir.name == newItem.basedir.name
}
}
@@ -54,13 +54,6 @@ class GameAdapter(private val libraryViewModel: LibraryViewModel) :
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()

View File

@@ -1,10 +1,10 @@
package su.xash.engine.model
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import androidx.documentfile.provider.DocumentFile
import su.xash.engine.util.TGAReader
import java.io.File
import java.io.FileInputStream
import java.util.Scanner
@@ -14,7 +14,7 @@ object BackgroundBitmap {
private const val BACKGROUND_WIDTH = 800
private const val BACKGROUND_HEIGHT = 600
fun createBackground(ctx: Context, file: DocumentFile): Bitmap {
fun createBackground(file: File): Bitmap {
var bitmap =
Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)
var canvas = Canvas(bitmap)
@@ -23,19 +23,20 @@ object BackgroundBitmap {
var width: Int
var height = 0
var bgLayout = file.findFile("resource")?.findFile("HD_BackgroundLayout.txt")
if (bgLayout == null) {
bgLayout = file.findFile("resource")?.findFile("BackgroundLayout.txt")
val resourceFolder = File(file, "resource")
var bgLayout = File(resourceFolder, "HD_BackgroundLayout.txt")
if (!bgLayout.exists()) {
bgLayout = File(resourceFolder, "BackgroundLayout.txt")
}
if (bgLayout == null) {
val dir = file.findFile("resource")?.findFile("background")
if (!bgLayout.exists()) {
val dir = File(resourceFolder, "background")
for (i in 0 until BACKGROUND_ROWS) {
x = 0
for (j in 0 until BACKGROUND_COLUMNS) {
val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga"
val bmpFile = dir?.findFile(filename)
val bmpImage = loadTga(ctx, bmpFile!!)
val bmpFile = File(dir, filename)
val bmpImage = loadTga(bmpFile)
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
x += bmpImage.width
@@ -47,7 +48,7 @@ object BackgroundBitmap {
return bitmap
}
ctx.contentResolver.openInputStream(bgLayout.uri).use { inputStream ->
FileInputStream(bgLayout).use { inputStream ->
Scanner(inputStream).use { scanner ->
while (scanner.hasNext()) {
when (val str = scanner.next()) {
@@ -60,12 +61,12 @@ object BackgroundBitmap {
else -> {
var bmpFile = file
str.split("/").forEach { bmpFile = bmpFile.findFile(it)!! }
str.split("/").forEach { bmpFile = File(bmpFile, it) }
//skip
scanner.next()
x = scanner.nextInt()
y = scanner.nextInt()
val bmp = loadTga(ctx, bmpFile)
val bmp = loadTga(bmpFile)
canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)
}
}
@@ -75,9 +76,9 @@ object BackgroundBitmap {
return bitmap
}
private fun loadTga(ctx: Context, file: DocumentFile): Bitmap {
ctx.contentResolver.openInputStream(file.uri).use {
val buffer = it?.readBytes()
private fun loadTga(file: File): Bitmap {
FileInputStream(file).use {
val buffer = it.readBytes()
val pixels = TGAReader.read(buffer, TGAReader.ARGB)
val width = TGAReader.getWidth(buffer)

View File

@@ -5,13 +5,14 @@ import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import androidx.documentfile.provider.DocumentFile
import su.xash.engine.XashActivity
import java.io.File
import java.io.FileInputStream
class Game(val ctx: Context, val basedir: DocumentFile, var installed: Boolean = true) {
class Game(val ctx: Context, val basedir: File) {
private var iconName = "game.ico"
var title = "Unknown Game"
var icon: Bitmap? = null
@@ -20,15 +21,21 @@ class Game(val ctx: Context, val basedir: DocumentFile, var installed: Boolean =
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) }
val gameInfo = File(basedir, "gameinfo.txt")
if (gameInfo.exists()) {
parseGameInfo(gameInfo)
} else {
val libListGam = File(basedir, "liblist.gam")
if (libListGam.exists()) parseGameInfo(libListGam)
}
basedir.findFile(iconName)
?.let { icon = MediaStore.Images.Media.getBitmap(ctx.contentResolver, it.uri) }
val iconFile = File(basedir, iconName)
if (iconFile.exists()) {
icon = BitmapFactory.decodeFile(iconFile.path)
}
try {
cover = BackgroundBitmap.createBackground(ctx, basedir)
cover = BackgroundBitmap.createBackground(basedir)
} catch (e: Exception) {
e.printStackTrace()
}
@@ -40,15 +47,16 @@ class Game(val ctx: Context, val basedir: DocumentFile, var installed: Boolean =
putExtra("gamedir", basedir.name)
putExtra("argv", pref.getString("arguments", "-console -log"))
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
putExtra("basedir", basedir.parent)
//.putExtra("gamelibdir", getGameLibDir(context))
//.putExtra("package", getPackageName()) }
})
}
private fun parseGameInfo(file: DocumentFile) {
ctx.contentResolver.openInputStream(file.uri).use { inputStream ->
inputStream?.bufferedReader().use { reader ->
reader?.forEachLine {
private fun parseGameInfo(file: File) {
FileInputStream(file).use { inputStream ->
inputStream.bufferedReader().use { reader ->
reader.forEachLine {
val tokens = it.split("\\s+".toRegex(), limit = 2)
if (tokens.size >= 2) {
val k = tokens[0]
@@ -83,19 +91,19 @@ class Game(val ctx: Context, val basedir: DocumentFile, var installed: Boolean =
)
return null
}
return pkgInfo.applicationInfo.nativeLibraryDir
return pkgInfo.applicationInfo?.nativeLibraryDir
}
return ctx.applicationInfo.nativeLibraryDir
}
companion object {
fun getGames(ctx: Context, file: DocumentFile): List<Game> {
fun getGames(ctx: Context, file: File): List<Game> {
val games = mutableListOf<Game>()
if (checkIfGamedir(file)) {
games.add(Game(ctx, file))
} else {
file.listFiles().forEach {
file.listFiles()?.forEach {
if (it.isDirectory) {
if (checkIfGamedir(it)) {
games.add(Game(ctx, it))
@@ -107,13 +115,10 @@ class Game(val ctx: Context, val basedir: DocumentFile, var installed: Boolean =
return games
}
fun checkIfGamedir(file: DocumentFile): Boolean {
// exclude unfinished downloads
if (file.name?.startsWith('.') == true)
return false
fun checkIfGamedir(file: File): Boolean {
if (File(file, "liblist.gam").exists()) return true
if (File(file, "gameinfo.txt").exists()) return true
file.findFile("liblist.gam")?.let { return true }
file.findFile("gameinfo.txt")?.let { return true }
return false
}
}

View File

@@ -1,59 +0,0 @@
package su.xash.engine.model
import org.json.JSONArray
import org.json.JSONObject
import java.io.InputStream
class ModDatabase(inputStream: InputStream) {
val entries = mutableListOf<Entry>()
companion object {
const val VERSION = 1
fun getFilename(): String {
return "v${VERSION}.json"
}
}
init {
inputStream.bufferedReader().use {
val jsonArray = JSONArray(it.readText())
for (i in 0..<jsonArray.length()) {
entries.add(Entry(jsonArray.getJSONObject(i)))
}
}
}
fun getByGameDir(gamedir: String): Entry? {
return entries.filter { it.gamedir.equals(gamedir) }.firstOrNull()
}
inner class Entry(jsonObject: JSONObject) {
var name: String? = null
var appid: Int? = null
var gamedir: String? = null
// TODO Depots
var pkgname: String? = null
init {
if (jsonObject.has("name")) {
name = jsonObject.getString("name");
}
if (jsonObject.has("app_id")) {
appid = jsonObject.getInt("app_id");
}
if (jsonObject.has("gamedir")) {
gamedir = jsonObject.getString("gamedir");
}
if (jsonObject.has("package_name")) {
pkgname = jsonObject.getString("package_name");
}
}
}
}

View File

@@ -1,18 +1,26 @@
package su.xash.engine.ui.library
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import su.xash.engine.BuildConfig
import su.xash.engine.R
import su.xash.engine.adapters.GameAdapter
import su.xash.engine.databinding.FragmentLibraryBinding
@@ -23,9 +31,38 @@ class LibraryFragment : Fragment(), MenuProvider {
private val libraryViewModel: LibraryViewModel by activityViewModels()
private val startActivityForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (checkStoragePermissions()) {
libraryViewModel.reloadGames(requireContext())
}
}
private fun checkStoragePermissions(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
MaterialAlertDialogBuilder(requireContext()).apply {
setTitle(R.string.file_access_required)
setMessage(R.string.file_access_message)
setPositiveButton(android.R.string.ok) { _, _ ->
startActivityForResult.launch(
Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).setData(
Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
)
)
}
setCancelable(false)
show()
}
return false
} else {
return true
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
): View {
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
val adapter = GameAdapter(libraryViewModel)
@@ -47,11 +84,7 @@ class LibraryFragment : Fragment(), MenuProvider {
(binding.gamesList.adapter as GameAdapter).submitList(it)
}
libraryViewModel.workInfos.observe(viewLifecycleOwner) {
libraryViewModel.refreshDownloads(requireContext())
}
libraryViewModel.downloads.observe(viewLifecycleOwner) {
if (checkStoragePermissions()) {
libraryViewModel.reloadGames(requireContext())
}
}
@@ -67,18 +100,6 @@ class LibraryFragment : Fragment(), MenuProvider {
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)
}

View File

@@ -2,49 +2,30 @@ package su.xash.engine.ui.library
import android.app.Application
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import android.content.SharedPreferences
import android.os.Environment
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.Dispatchers
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
const val TAG_INSTALL = "TAG_INSTALL"
import java.io.File
class LibraryViewModel(application: Application) : AndroidViewModel(application) {
val installedGames: LiveData<List<Game>> get() = _installedGames
private val _installedGames = MutableLiveData(emptyList<Game>())
val 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)
}
private val appPreferences: SharedPreferences =
application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
fun reloadGames(ctx: Context) {
if (isReloading.value == true) {
@@ -54,65 +35,20 @@ class LibraryViewModel(application: Application) : AndroidViewModel(application)
viewModelScope.launch {
withContext(Dispatchers.IO) {
val games = mutableListOf<Game>()
val root = DocumentFile.fromFile(ctx.getExternalFilesDir(null)!!)
val rootPath = appPreferences.getString("game_path", null)
?: (Environment.getExternalStorageDirectory().absolutePath + "/xash")
val root = File(rootPath)
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)
_installedGames.postValue(Game.getGames(ctx, root))
_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)
}

View File

@@ -13,7 +13,7 @@ class AppSettingsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
): View {
_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)
return binding.root
}

View File

@@ -6,8 +6,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import su.xash.engine.R
import su.xash.engine.databinding.FragmentGameSettingsBinding
import su.xash.engine.ui.library.LibraryViewModel
@@ -19,7 +17,7 @@ class GameSettingsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
): View {
_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)
return binding.root
}
@@ -50,11 +48,6 @@ class GameSettingsFragment : Fragment() {
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() {

View File

@@ -1,48 +0,0 @@
package su.xash.engine.ui.setup
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.viewpager2.adapter.FragmentStateAdapter
import su.xash.engine.databinding.FragmentSetupBinding
import su.xash.engine.ui.setup.pages.LocationPageFragment
import su.xash.engine.ui.setup.pages.WelcomePageFragment
class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null
private val binding get() = _binding!!
private val setupViewModel: SetupViewModel by activityViewModels()
private lateinit var setupPageAdapter: SetupPageAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = FragmentSetupBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setupPageAdapter = SetupPageAdapter(this)
binding.viewPager.isUserInputEnabled = false
binding.viewPager.adapter = setupPageAdapter
setupViewModel.pageNumber.observe(viewLifecycleOwner) {
binding.viewPager.setCurrentItem(it, true)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
class SetupPageAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
val pages = listOf(WelcomePageFragment(), LocationPageFragment())
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment = pages[position]
}

View File

@@ -1,25 +0,0 @@
package su.xash.engine.ui.setup
import android.app.Application
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import su.xash.engine.MainApplication
import su.xash.engine.model.Game
class SetupViewModel(application: Application) : AndroidViewModel(application) {
val pageNumber: LiveData<Int> get() = _pageNumber
private val _pageNumber = MutableLiveData(0)
fun checkIfGameDir(uri: Uri): Boolean {
val ctx = getApplication<MainApplication>().applicationContext
val file = DocumentFile.fromTreeUri(ctx, uri)!!
return Game.checkIfGamedir(file)
}
fun setPageNumber(pos: Int) {
_pageNumber.value = pos
}
}

View File

@@ -1,62 +0,0 @@
package su.xash.engine.ui.setup.pages
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import su.xash.engine.R
import su.xash.engine.databinding.PageLocationBinding
import su.xash.engine.ui.library.LibraryViewModel
import su.xash.engine.ui.setup.SetupViewModel
class LocationPageFragment : Fragment() {
private var _binding: PageLocationBinding? = null
private val binding get() = _binding!!
private val setupViewModel: SetupViewModel by activityViewModels()
private val libraryViewModel: LibraryViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = PageLocationBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.pageButton.setOnClickListener {
getGamesDirectory.launch(null)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
it?.let {
if (!setupViewModel.checkIfGameDir(it)) {
MaterialAlertDialogBuilder(requireContext()).apply {
setTitle(R.string.error)
setMessage(R.string.setup_location_empty)
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
show()
}
} else {
requireContext().contentResolver.takePersistableUriPermission(
it,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
libraryViewModel.installGame(it)
findNavController().navigate(R.id.action_setupFragment_to_libraryFragment)
}
}
}
}

View File

@@ -1,35 +0,0 @@
package su.xash.engine.ui.setup.pages
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import su.xash.engine.databinding.PageWelcomeBinding
import su.xash.engine.ui.setup.SetupViewModel
class WelcomePageFragment : Fragment() {
private var _binding: PageWelcomeBinding? = null
private val binding get() = _binding!!
private val setupViewModel: SetupViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = PageWelcomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.pageButton.setOnClickListener {
setupViewModel.setPageNumber(1)
}
setupViewModel.setPageNumber(0)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@@ -1,4 +1,4 @@
package su.xash.engine;
package su.xash.engine.util;
import android.app.Activity;
import android.graphics.Rect;

View File

@@ -1,98 +0,0 @@
package su.xash.engine.workers
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
const val KEY_FILE_URI = "KEY_FILE_URI"
class FileCopyWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
companion object {
const val Input = "Input"
const val Progress = "Progress"
}
private var fileCount = 0
private var fileCopied = 0
override suspend fun doWork(): Result {
withContext(Dispatchers.IO) {
val fileUri = inputData.getString(KEY_FILE_URI)
setProgress(workDataOf(Input to fileUri))
val uri = Uri.parse(fileUri)
val source = DocumentFile.fromTreeUri(applicationContext, uri)
fileCount = source?.countDirFiles() ?: return@withContext Result.failure()
setProgress(workDataOf(Progress to 0f))
val gamedir = source.name!!
val externalFilesDir =
DocumentFile.fromFile(applicationContext.getExternalFilesDir(null)!!)
// create a directory to store staged files
val target = externalFilesDir.createDirectory(".$gamedir")!!
source.copyDirTo(applicationContext, this@FileCopyWorker, target)
target.renameTo(gamedir)
setProgress(workDataOf(Progress to 1f))
}
return Result.success()
}
suspend fun fileCopied(count: Int) {
if (count == 0)
return
fileCopied += count
val percentage: Float = fileCopied.toFloat() / fileCount.toFloat();
setProgress(workDataOf(Progress to percentage))
}
}
fun DocumentFile.countDirFiles(): Int {
var count: Int = 0
listFiles().forEach {
if (it.isDirectory)
count += it.countDirFiles()
else
count++
}
return count
}
fun DocumentFile.copyFileTo(ctx: Context, file: DocumentFile) {
val outFile = file.createFile("application", name!!)!!
ctx.contentResolver.openOutputStream(outFile.uri).use { os ->
ctx.contentResolver.openInputStream(uri).use {
it?.copyTo(os!!)
}
}
}
suspend fun DocumentFile.copyDirTo(ctx: Context, worker: FileCopyWorker, dir: DocumentFile) {
var count: Int = 0
listFiles().forEach {
if (it.isDirectory) {
val outDir = dir.createDirectory(it.name!!)!!
it.copyDirTo(ctx, worker, outDir)
} else {
it.copyFileTo(ctx, dir)
count++
}
}
worker.fileCopied(count)
}

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"

View File

@@ -77,19 +77,6 @@
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"

View File

@@ -1,30 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- <com.google.android.material.textfield.TextInputLayout-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:layout_constraintBottom_toBottomOf="parent">-->
<!-- <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"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium" />
android:padding="10dp">
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
app:layout_constraintTop_toTopOf="parent"
tools:text="EditText Preference" />
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@android:id/title"
tools:text="Summary" />
<!-- <com.google.android.material.button.MaterialButton-->
<!-- style="@style/Widget.Material3.Button.IconButton"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:icon="@drawable/baseline_edit_24"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent" />-->
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@android:id/title"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -2,8 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="8dp">
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settingsFragment"

View File

@@ -13,27 +13,20 @@
android:layout_margin="8dp"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Filled"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settingsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:contentPadding="16dp"
app:layout_constraintTop_toBottomOf="@id/gameCard">
app:layout_constraintTop_toBottomOf="@id/gameCard"
tools:layout="@android:layout/list_content">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settingsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@android:layout/list_content" />
</androidx.fragment.app.FragmentContainerView>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/menu_game_settings" />
<!-- <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>

View File

@@ -2,9 +2,9 @@
<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:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/swipeRefresh">
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
@@ -14,9 +14,9 @@
android:id="@+id/gamesList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"
android:clipToPadding="false"
android:paddingHorizontal="8dp"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/card_game" />
</RelativeLayout>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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>

View File

@@ -8,13 +8,13 @@
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" />
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@android:id/title"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium" />
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintTop_toBottomOf="@android:id/title" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<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>

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<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>

View File

@@ -1,23 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
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" />
android:padding="10dp">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Switch preference" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -4,11 +4,6 @@
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"
android:visible="false"
app:showAsAction="always|withText" />
</menu>

View File

@@ -1,19 +1,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_browse"
android:icon="@drawable/ic_baseline_folder_open_24"
android:title="@string/browse"
app:showAsAction="always|withText" />
<item
android:id="@+id/action_install"
android:icon="@drawable/ic_baseline_add_24"
android:title="@string/install"
app:showAsAction="always|withText" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_baseline_settings_24"
android:title="@string/app_settings"
app:showAsAction="always|withText"
android:visible="false" />
app:showAsAction="always|withText" />
</menu>

View File

@@ -8,9 +8,6 @@
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" />
@@ -18,14 +15,6 @@
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"

View File

@@ -1,23 +1,11 @@
<?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="library">Biblioteca</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>

View File

@@ -1,23 +1,11 @@
<?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="library">Библиотека</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>

View File

@@ -1,28 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Xash3D FWGS</string>
<string name="cancel">Cancel</string>
<string name="ok">OK</string>
<string name="next">Next</string>
<string name="browse">Browse</string>
<string name="library">Library</string>
<string name="dedicated_server">Dedicated</string>
<string name="error">Error!</string>
<string name="setup">Setup</string>
<string name="app_settings">Settings</string>
<string name="game_settings">Game Settings</string>
<string name="game_settings_command_line">Command-line arguments</string>
<string name="game_settings_volume_buttons">Use volume buttons in-game</string>
<string name="setup_welcome_title">Welcome to Xash3D FWGS!</string>
<string name="setup_welcome_message">Now, we will guide you through the installation process.</string>
<string name="setup_location_title">Games</string>
<string name="setup_location_message">Select the folder, where your games are located.</string>
<string name="setup_location_empty">Selected directory doesn\'t contain any games.</string>
<string name="setup_location_invalid">Selected directory is invalid.</string>
<string name="preferences_package_name">Libraries package</string>
<string name="preferences_separate_libraries">Libraries from separate packages</string>
<string name="preferences_client_package">Client package</string>
<string name="preferences_server_package">Server package</string>
<string name="preferences_use_icons">Use icons instead of backgrounds</string>
<string name="game_data_location">Game data location</string>
<string name="file_access_required">All-files access required</string>
<string name="file_access_message">All-files access is required for the app to function properly.</string>
<string name="select_current_directory">Select current directory</string>
</resources>