Browse Source
put external content in "Manage game folders" and show a modal. Also handles logic for android now.
pull/2862/head
put external content in "Manage game folders" and show a modal. Also handles logic for android now.
pull/2862/head
17 changed files with 205 additions and 401 deletions
-
53src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/ExternalContentAdapter.kt
-
8src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
-
105src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ExternalContentFragment.kt
-
24src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt
-
22src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
-
11src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
-
56src/android/app/src/main/java/org/yuzu/yuzu_emu/model/ExternalContentViewModel.kt
-
13src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt
-
63src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
-
20src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
-
38src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
-
45src/android/app/src/main/jni/native.cpp
-
42src/android/app/src/main/res/layout/card_external_content_dir.xml
-
17src/android/app/src/main/res/layout/card_folder.xml
-
69src/android/app/src/main/res/layout/fragment_external_content.xml
-
11src/android/app/src/main/res/navigation/home_navigation.xml
-
9src/android/app/src/main/res/values/strings.xml
@ -1,53 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later |
|
||||
|
|
||||
package org.yuzu.yuzu_emu.adapters |
|
||||
|
|
||||
import android.view.LayoutInflater |
|
||||
import android.view.ViewGroup |
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig |
|
||||
import androidx.recyclerview.widget.DiffUtil |
|
||||
import androidx.recyclerview.widget.ListAdapter |
|
||||
import androidx.recyclerview.widget.RecyclerView |
|
||||
import org.yuzu.yuzu_emu.databinding.CardExternalContentDirBinding |
|
||||
import org.yuzu.yuzu_emu.model.ExternalContentViewModel |
|
||||
|
|
||||
class ExternalContentAdapter( |
|
||||
private val viewModel: ExternalContentViewModel |
|
||||
) : ListAdapter<String, ExternalContentAdapter.DirectoryViewHolder>( |
|
||||
AsyncDifferConfig.Builder(DiffCallback()).build() |
|
||||
) { |
|
||||
|
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DirectoryViewHolder { |
|
||||
val binding = CardExternalContentDirBinding.inflate( |
|
||||
LayoutInflater.from(parent.context), |
|
||||
parent, |
|
||||
false |
|
||||
) |
|
||||
return DirectoryViewHolder(binding) |
|
||||
} |
|
||||
|
|
||||
override fun onBindViewHolder(holder: DirectoryViewHolder, position: Int) { |
|
||||
holder.bind(getItem(position)) |
|
||||
} |
|
||||
|
|
||||
inner class DirectoryViewHolder(val binding: CardExternalContentDirBinding) : |
|
||||
RecyclerView.ViewHolder(binding.root) { |
|
||||
fun bind(path: String) { |
|
||||
binding.textPath.text = path |
|
||||
binding.buttonRemove.setOnClickListener { |
|
||||
viewModel.removeDirectory(path) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private class DiffCallback : DiffUtil.ItemCallback<String>() { |
|
||||
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean { |
|
||||
return oldItem == newItem |
|
||||
} |
|
||||
|
|
||||
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean { |
|
||||
return oldItem == newItem |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,105 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later |
|
||||
|
|
||||
package org.yuzu.yuzu_emu.fragments |
|
||||
|
|
||||
import android.content.Intent |
|
||||
import android.os.Bundle |
|
||||
import android.view.LayoutInflater |
|
||||
import android.view.View |
|
||||
import android.view.ViewGroup |
|
||||
import androidx.core.view.ViewCompat |
|
||||
import androidx.core.view.WindowInsetsCompat |
|
||||
import androidx.core.view.updatePadding |
|
||||
import androidx.fragment.app.Fragment |
|
||||
import androidx.fragment.app.activityViewModels |
|
||||
import androidx.navigation.findNavController |
|
||||
import androidx.recyclerview.widget.LinearLayoutManager |
|
||||
import com.google.android.material.transition.MaterialSharedAxis |
|
||||
import org.yuzu.yuzu_emu.R |
|
||||
import org.yuzu.yuzu_emu.adapters.ExternalContentAdapter |
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentExternalContentBinding |
|
||||
import org.yuzu.yuzu_emu.model.ExternalContentViewModel |
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel |
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity |
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins |
|
||||
import org.yuzu.yuzu_emu.utils.collect |
|
||||
|
|
||||
class ExternalContentFragment : Fragment() { |
|
||||
private var _binding: FragmentExternalContentBinding? = null |
|
||||
private val binding get() = _binding!! |
|
||||
|
|
||||
private val homeViewModel: HomeViewModel by activityViewModels() |
|
||||
private val externalContentViewModel: ExternalContentViewModel by activityViewModels() |
|
||||
|
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
|
||||
super.onCreate(savedInstanceState) |
|
||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) |
|
||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) |
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) |
|
||||
} |
|
||||
|
|
||||
override fun onCreateView( |
|
||||
inflater: LayoutInflater, |
|
||||
container: ViewGroup?, |
|
||||
savedInstanceState: Bundle? |
|
||||
): View { |
|
||||
_binding = FragmentExternalContentBinding.inflate(inflater) |
|
||||
return binding.root |
|
||||
} |
|
||||
|
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
|
||||
super.onViewCreated(view, savedInstanceState) |
|
||||
homeViewModel.setStatusBarShadeVisibility(visible = false) |
|
||||
|
|
||||
binding.toolbarExternalContent.setNavigationOnClickListener { |
|
||||
binding.root.findNavController().popBackStack() |
|
||||
} |
|
||||
|
|
||||
binding.listExternalDirs.apply { |
|
||||
layoutManager = LinearLayoutManager(requireContext()) |
|
||||
adapter = ExternalContentAdapter(externalContentViewModel) |
|
||||
} |
|
||||
|
|
||||
externalContentViewModel.directories.collect(viewLifecycleOwner) { dirs -> |
|
||||
(binding.listExternalDirs.adapter as ExternalContentAdapter).submitList(dirs) |
|
||||
binding.textEmpty.visibility = if (dirs.isEmpty()) View.VISIBLE else View.GONE |
|
||||
} |
|
||||
|
|
||||
val mainActivity = requireActivity() as MainActivity |
|
||||
binding.buttonAdd.setOnClickListener { |
|
||||
mainActivity.getExternalContentDirectory.launch(null) |
|
||||
} |
|
||||
|
|
||||
setInsets() |
|
||||
} |
|
||||
|
|
||||
private fun setInsets() = |
|
||||
ViewCompat.setOnApplyWindowInsetsListener( |
|
||||
binding.root |
|
||||
) { _: View, windowInsets: WindowInsetsCompat -> |
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
|
||||
|
|
||||
val leftInsets = barInsets.left + cutoutInsets.left |
|
||||
val rightInsets = barInsets.right + cutoutInsets.right |
|
||||
|
|
||||
binding.toolbarExternalContent.updateMargins(left = leftInsets, right = rightInsets) |
|
||||
|
|
||||
val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab) |
|
||||
binding.buttonAdd.updateMargins( |
|
||||
left = leftInsets + fabSpacing, |
|
||||
right = rightInsets + fabSpacing, |
|
||||
bottom = barInsets.bottom + fabSpacing |
|
||||
) |
|
||||
|
|
||||
binding.listExternalDirs.updateMargins(left = leftInsets, right = rightInsets) |
|
||||
|
|
||||
binding.listExternalDirs.updatePadding( |
|
||||
bottom = barInsets.bottom + |
|
||||
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab) |
|
||||
) |
|
||||
|
|
||||
windowInsets |
|
||||
} |
|
||||
} |
|
||||
@ -1,56 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later |
|
||||
|
|
||||
package org.yuzu.yuzu_emu.model |
|
||||
|
|
||||
import androidx.documentfile.provider.DocumentFile |
|
||||
import androidx.lifecycle.ViewModel |
|
||||
import androidx.lifecycle.viewModelScope |
|
||||
import kotlinx.coroutines.Dispatchers |
|
||||
import kotlinx.coroutines.flow.MutableStateFlow |
|
||||
import kotlinx.coroutines.flow.StateFlow |
|
||||
import kotlinx.coroutines.launch |
|
||||
import kotlinx.coroutines.withContext |
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig |
|
||||
|
|
||||
class ExternalContentViewModel : ViewModel() { |
|
||||
private val _directories = MutableStateFlow(listOf<String>()) |
|
||||
val directories: StateFlow<List<String>> get() = _directories |
|
||||
|
|
||||
init { |
|
||||
loadDirectories() |
|
||||
} |
|
||||
|
|
||||
private fun loadDirectories() { |
|
||||
viewModelScope.launch { |
|
||||
withContext(Dispatchers.IO) { |
|
||||
_directories.value = NativeConfig.getExternalContentDirs().toList() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
fun addDirectory(dir: DocumentFile) { |
|
||||
viewModelScope.launch { |
|
||||
withContext(Dispatchers.IO) { |
|
||||
val path = dir.uri.toString() |
|
||||
val currentDirs = _directories.value.toMutableList() |
|
||||
if (!currentDirs.contains(path)) { |
|
||||
currentDirs.add(path) |
|
||||
NativeConfig.setExternalContentDirs(currentDirs.toTypedArray()) |
|
||||
_directories.value = currentDirs |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
fun removeDirectory(path: String) { |
|
||||
viewModelScope.launch { |
|
||||
withContext(Dispatchers.IO) { |
|
||||
val currentDirs = _directories.value.toMutableList() |
|
||||
currentDirs.remove(path) |
|
||||
NativeConfig.setExternalContentDirs(currentDirs.toTypedArray()) |
|
||||
_directories.value = currentDirs |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,42 +0,0 @@ |
|||||
<?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" |
|
||||
android:layout_width="match_parent" |
|
||||
android:layout_height="wrap_content" |
|
||||
android:layout_marginHorizontal="16dp" |
|
||||
android:layout_marginVertical="8dp" |
|
||||
app:cardElevation="0dp"> |
|
||||
|
|
||||
<LinearLayout |
|
||||
android:layout_width="match_parent" |
|
||||
android:layout_height="wrap_content" |
|
||||
android:orientation="horizontal" |
|
||||
android:padding="16dp"> |
|
||||
|
|
||||
<LinearLayout |
|
||||
android:layout_width="0dp" |
|
||||
android:layout_height="wrap_content" |
|
||||
android:layout_weight="1" |
|
||||
android:orientation="vertical"> |
|
||||
|
|
||||
<TextView |
|
||||
android:id="@+id/text_path" |
|
||||
android:layout_width="match_parent" |
|
||||
android:layout_height="wrap_content" |
|
||||
android:ellipsize="middle" |
|
||||
android:singleLine="true" |
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge" /> |
|
||||
|
|
||||
</LinearLayout> |
|
||||
|
|
||||
<com.google.android.material.button.MaterialButton |
|
||||
android:id="@+id/button_remove" |
|
||||
style="@style/Widget.Material3.Button.TextButton" |
|
||||
android:layout_width="wrap_content" |
|
||||
android:layout_height="wrap_content" |
|
||||
android:layout_marginStart="8dp" |
|
||||
android:text="@string/remove_external_content_dir" /> |
|
||||
|
|
||||
</LinearLayout> |
|
||||
|
|
||||
</com.google.android.material.card.MaterialCardView> |
|
||||
@ -1,69 +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:id="@+id/coordinator_external_content" |
|
||||
android:layout_width="match_parent" |
|
||||
android:layout_height="match_parent" |
|
||||
android:background="?attr/colorSurface"> |
|
||||
|
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout |
|
||||
android:layout_width="match_parent" |
|
||||
android:layout_height="match_parent"> |
|
||||
|
|
||||
<com.google.android.material.appbar.AppBarLayout |
|
||||
android:id="@+id/appbar_external_content" |
|
||||
android:layout_width="match_parent" |
|
||||
android:layout_height="wrap_content" |
|
||||
android:fitsSystemWindows="true" |
|
||||
android:touchscreenBlocksFocus="false" |
|
||||
app:liftOnScrollTargetViewId="@id/list_external_dirs"> |
|
||||
|
|
||||
<com.google.android.material.appbar.MaterialToolbar |
|
||||
android:id="@+id/toolbar_external_content" |
|
||||
android:layout_width="match_parent" |
|
||||
android:layout_height="?attr/actionBarSize" |
|
||||
android:touchscreenBlocksFocus="false" |
|
||||
app:navigationIcon="@drawable/ic_back" |
|
||||
app:title="@string/external_content_directories" /> |
|
||||
|
|
||||
</com.google.android.material.appbar.AppBarLayout> |
|
||||
|
|
||||
<FrameLayout |
|
||||
android:layout_width="match_parent" |
|
||||
android:layout_height="match_parent" |
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"> |
|
||||
|
|
||||
<androidx.recyclerview.widget.RecyclerView |
|
||||
android:id="@+id/list_external_dirs" |
|
||||
android:layout_width="match_parent" |
|
||||
android:layout_height="match_parent" |
|
||||
android:clipToPadding="false" |
|
||||
android:defaultFocusHighlightEnabled="false" /> |
|
||||
|
|
||||
<TextView |
|
||||
android:id="@+id/text_empty" |
|
||||
android:layout_width="match_parent" |
|
||||
android:layout_height="wrap_content" |
|
||||
android:layout_gravity="center" |
|
||||
android:gravity="center" |
|
||||
android:padding="16dp" |
|
||||
android:text="@string/no_external_content_dirs" |
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge" |
|
||||
android:visibility="gone" /> |
|
||||
|
|
||||
</FrameLayout> |
|
||||
|
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
|
||||
|
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton |
|
||||
android:id="@+id/button_add" |
|
||||
android:layout_width="wrap_content" |
|
||||
android:layout_height="wrap_content" |
|
||||
android:layout_marginEnd="16dp" |
|
||||
android:layout_marginBottom="16dp" |
|
||||
android:contentDescription="@string/add_external_content_dir" |
|
||||
app:srcCompat="@drawable/ic_add" |
|
||||
app:layout_constraintBottom_toBottomOf="parent" |
|
||||
app:layout_constraintEnd_toEndOf="parent" /> |
|
||||
|
|
||||
</androidx.constraintlayout.widget.ConstraintLayout> |
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue