Skip to content

Commit

Permalink
Add settings to set custom environment variables (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
nitanmarcel authored Jan 25, 2025
1 parent 16f7b94 commit 4afca3b
Show file tree
Hide file tree
Showing 13 changed files with 403 additions and 0 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ dependencies {
implementation("com.google.code.gson:gson:2.10.1")
implementation("com.github.vatbub:mslinks:1.0.6.2")
implementation(project(":app:stub"))
implementation("androidx.preference:preference:1.2.1")
}

fun getGitShortSHA(): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.micewine.emu.databinding.ActivityGeneralSettingsBinding
import com.micewine.emu.fragments.Box64SettingsFragment
import com.micewine.emu.fragments.DisplaySettingsFragment
import com.micewine.emu.fragments.DriversSettingsFragment
import com.micewine.emu.fragments.EnvironmentVarsSettingsFragment
import com.micewine.emu.fragments.GeneralSettingsFragment
import com.micewine.emu.fragments.WineSettingsFragment

Expand All @@ -27,6 +28,7 @@ class GeneralSettings : AppCompatActivity() {
private val wineSettingsFragment = WineSettingsFragment()
private val displaySettingsFragment = DisplaySettingsFragment()
private val driversSettingsFragment = DriversSettingsFragment()
private val environmentVariablesSettings = EnvironmentVarsSettingsFragment()
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val preference = intent.getStringExtra("preference")
Expand Down Expand Up @@ -56,6 +58,12 @@ class GeneralSettings : AppCompatActivity() {

fragmentLoader(driversSettingsFragment, false)
}

context.resources.getString(R.string.env_settings_title) -> {
generalSettingsToolbar?.title = getString(R.string.env_settings_title)

fragmentLoader(environmentVariablesSettings, false)
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/micewine/emu/activities/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ import com.micewine.emu.activities.GeneralSettings.Companion.WINE_ESYNC
import com.micewine.emu.activities.GeneralSettings.Companion.WINE_ESYNC_DEFAULT_VALUE
import com.micewine.emu.activities.GeneralSettings.Companion.WINE_LOG_LEVEL
import com.micewine.emu.activities.GeneralSettings.Companion.WINE_LOG_LEVEL_DEFAULT_VALUE
import com.micewine.emu.core.EnvVars
import com.micewine.emu.core.EnvVars.getEnv
import com.micewine.emu.core.RatPackageManager
import com.micewine.emu.core.RatPackageManager.installRat
Expand Down Expand Up @@ -263,6 +264,8 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

EnvVars.initialize(this)

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding!!.root)

Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/com/micewine/emu/core/EnvVars.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.micewine.emu.core

import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import androidx.preference.PreferenceManager
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.micewine.emu.activities.MainActivity.Companion.appLang
import com.micewine.emu.activities.MainActivity.Companion.appRootDir
import com.micewine.emu.activities.MainActivity.Companion.box64Avx
Expand Down Expand Up @@ -36,9 +41,16 @@ import com.micewine.emu.activities.MainActivity.Companion.tmpDir
import com.micewine.emu.activities.MainActivity.Companion.usrDir
import com.micewine.emu.activities.MainActivity.Companion.wineESync
import com.micewine.emu.activities.MainActivity.Companion.wineLogLevel
import com.micewine.emu.fragments.EnvironmentVariable

object EnvVars {
private val vars = LinkedHashMap<String, String>()
private lateinit var sharedPreferences: SharedPreferences

fun initialize(context: Context) {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
}

private fun putVar(name: String, value: String?) {
vars[name] = "$name=\"$value\""
}
Expand All @@ -48,6 +60,13 @@ object EnvVars {

setEnv()

val savedVarsJson = sharedPreferences.getString("environment_variables", null)
if (savedVarsJson != null) {
val type = object : TypeToken<List<EnvironmentVariable>>() {}.type
val savedVars = Gson().fromJson<List<EnvironmentVariable>>(savedVarsJson, type)
savedVars.forEach { putVar(it.key, it.value) }
}

return "env ${vars.values.joinToString(" ")} "
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package com.micewine.emu.fragments

import com.micewine.emu.R
import android.app.AlertDialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.ImageButton
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

class EnvironmentVarsSettingsFragment : Fragment() {
private lateinit var btnAddEnvVar: Button
private lateinit var rvEnvVars: RecyclerView

private val envVarsList = mutableListOf<EnvironmentVariable>()
private lateinit var envVarsAdapter: EnvironmentVarsAdapter

companion object {
private const val PREFS_NAME = "EnvironmentVariablesPrefs"
private const val ENV_VARS_KEY = "environment_variables"
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_env_vars_settings, container, false)

btnAddEnvVar = view.findViewById(R.id.btnAddEnvVar)
rvEnvVars = view.findViewById(R.id.rvEnvVars)

loadEnvironmentVariables()
setupRecyclerView()
setupAddButton()

return view
}

private fun loadEnvironmentVariables() {
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val savedVarsJson = preferences.getString(ENV_VARS_KEY, null)
savedVarsJson?.let {
val type = object : TypeToken<List<EnvironmentVariable>>() {}.type
val savedVars = Gson().fromJson<List<EnvironmentVariable>>(it, type)
envVarsList.clear()
envVarsList.addAll(savedVars)
}
}

private fun saveEnvironmentVariables() {
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = preferences.edit()
val varsJson = Gson().toJson(envVarsList)
editor.putString(ENV_VARS_KEY, varsJson)
editor.apply()
}

private fun setupRecyclerView() {
envVarsAdapter = EnvironmentVarsAdapter(envVarsList,
onItemClick = { position -> showEditDialog(position) },
onDeleteClick = { position -> deleteEnvironmentVar(position) }
)
rvEnvVars.layoutManager = LinearLayoutManager(context)
rvEnvVars.adapter = envVarsAdapter
}

private fun setupAddButton() {
btnAddEnvVar.setOnClickListener {
showAddDialog()
}
}

private fun showAddDialog() {
val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_env_var, null)
val keyInput = dialogView.findViewById<EditText>(R.id.etDialogKey)
val valueInput = dialogView.findViewById<EditText>(R.id.etDialogValue)

AlertDialog.Builder(context)
.setTitle(getString(R.string.env_add_action))
.setView(dialogView)
.setPositiveButton(getString(R.string.save_text)) { _, _ ->
val key = keyInput.text.toString().trim()
val value = valueInput.text.toString().trim()

if (key.isNotEmpty() && value.isNotEmpty()) {
val envVar = EnvironmentVariable(key, value)
envVarsList.add(envVar)
envVarsAdapter.notifyItemInserted(envVarsList.size - 1)
saveEnvironmentVariables()
}
}
.setNegativeButton(getString(R.string.cancel_text), null)
.create()
.show()
}

private fun showEditDialog(position: Int) {
val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_env_var, null)
val keyInput = dialogView.findViewById<EditText>(R.id.etDialogKey)
val valueInput = dialogView.findViewById<EditText>(R.id.etDialogValue)

val currentVar = envVarsList[position]
keyInput.setText(currentVar.key)
valueInput.setText(currentVar.value)

AlertDialog.Builder(context)
.setTitle(getString(R.string.env_edit_action))
.setView(dialogView)
.setPositiveButton("Save") { _, _ ->
val key = keyInput.text.toString().trim()
val value = valueInput.text.toString().trim()

if (key.isNotEmpty()) {
envVarsList[position] = EnvironmentVariable(key, value)
envVarsAdapter.notifyItemChanged(position)
saveEnvironmentVariables()
}
}
.setNegativeButton("Cancel", null)
.create()
.show()
}

private fun deleteEnvironmentVar(position: Int) {
envVarsList.removeAt(position)
envVarsAdapter.notifyItemRemoved(position)
saveEnvironmentVariables()
}
}

data class EnvironmentVariable(val key: String, val value: String)

class EnvironmentVarsAdapter(
private val envVars: List<EnvironmentVariable>,
private val onItemClick: (Int) -> Unit,
private val onDeleteClick: (Int) -> Unit
) : RecyclerView.Adapter<EnvironmentVarsAdapter.ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_env_vars, parent, false)
return ViewHolder(view, onItemClick, onDeleteClick)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(envVars[position])
}

override fun getItemCount() = envVars.size

class ViewHolder(
itemView: View,
private val onItemClick: (Int) -> Unit,
private val onDeleteClick: (Int) -> Unit
) : RecyclerView.ViewHolder(itemView) {
private val tvKey: TextView = itemView.findViewById(R.id.tvEnvVarKey)
private val tvValue: TextView = itemView.findViewById(R.id.tvEnvVarValue)
private val btnDelete: ImageButton = itemView.findViewById(R.id.btnDeleteEnvVar)

init {
itemView.setOnClickListener { onItemClick(adapterPosition) }
btnDelete.setOnClickListener { onDeleteClick(adapterPosition) }
}

fun bind(envVar: EnvironmentVariable) {
tvKey.text = envVar.key
tvValue.text = envVar.value
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class GeneralSettingsFragment : Fragment() {
addToAdapter(R.string.wine_settings_title, R.string.wine_settings_description, R.drawable.ic_wine)
addToAdapter(R.string.display_settings_title, R.string.display_settings_description, R.drawable.ic_display)
addToAdapter(R.string.driver_settings_title, R.string.driver_settings_description, R.drawable.ic_gpu)
addToAdapter(R.string.env_settings_title, R.string.env_settings_description, R.drawable.ic_settings)
}

private fun addToAdapter(titleId: Int, descriptionId: Int, icon: Int) {
Expand Down
32 changes: 32 additions & 0 deletions app/src/main/res/layout/dialog_env_var.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Key">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etDialogKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Value">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etDialogValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>

</LinearLayout>
22 changes: 22 additions & 0 deletions app/src/main/res/layout/fragment_env_vars_settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:padding="16dp"
tools:context=".fragments.EnvironmentVarsSettingsFragment">

<Button
android:id="@+id/btnAddEnvVar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add Environment Variable"/>

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvEnvVars"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="16dp"/>

</LinearLayout>
Loading

0 comments on commit 4afca3b

Please sign in to comment.