diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000000..c94f2bccfe
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,44 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Disclamer**
+In order to keep this bug tracker manageable, you need to keep a few things in mind:
+* Any issue not following this template will be closed as invalid
+* Is the issue reproducible on RetroArch with the same core? If so, it's very likely a core issue and the upstream core repository is a better place to file it
+* Is the issue already opened? Try out a search before filing one.
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser [e.g. stock browser, safari]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000000..1b87167a35
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,25 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Disclamer**
+In order to keep this bug tracker manageable, you need to keep a few things in mind:
+*Any feature request not following this template will be closed as invalid
+*Is the issue already opened? Try out a search before filing one.
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/README.md b/README.md
index d86bd417f8..a09a749b34 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@ It originated from a rib of [Retrograde](https://github.com/retrograde/retrograd
- NEC PC Engine (PCE) ([beetle_pce_fast](https://docs.libretro.com/library/beetle_pce_fast/))
- Neo Geo Pocket (NGP) ([mednafen_ngp](https://docs.libretro.com/library/beetle_neopop/))
- Neo Geo Pocket Color (NGC) ([mednafen_ngp](https://docs.libretro.com/library/beetle_neopop/))
+- Nintendo 3DS (3DS) ([citra](https://docs.libretro.com/library/citra/))
### Features:
- Android TV support
diff --git a/buildSrc/src/main/java/deps.kt b/buildSrc/src/main/java/deps.kt
index 8dcd3b95b1..d05d7d088d 100644
--- a/buildSrc/src/main/java/deps.kt
+++ b/buildSrc/src/main/java/deps.kt
@@ -1,10 +1,10 @@
/* ktlint-disable no-multi-spaces max-line-length */
object deps {
object android {
- const val targetSdkVersion = 29
- const val compileSdkVersion = 29
+ const val targetSdkVersion = 31
+ const val compileSdkVersion = 31
const val minSdkVersion = 23
- const val buildToolsVersion = "29.0.3"
+ const val buildToolsVersion = "30.0.2"
}
object versions {
@@ -15,7 +15,7 @@ object deps {
const val kotlin = "1.4.30"
const val okHttp = "4.9.1"
const val retrofit = "2.9.0"
- const val work = "2.5.0"
+ const val work = "2.7.1"
const val navigation = "2.3.5"
const val rxbindings = "3.1.0"
const val lifecycle = "2.3.1"
@@ -25,8 +25,8 @@ object deps {
const val room = "2.3.0"
const val epoxy = "4.6.3-vinay-compose"
const val serialization = "1.2.2"
- const val libretrodroid = "0.6.1"
- const val radialgamepad = "0.5.0"
+ const val libretrodroid = "0.7.0"
+ const val radialgamepad = "0.6.1"
}
object libs {
@@ -76,6 +76,7 @@ object deps {
const val runtime = "androidx.work:work-runtime:${versions.work}"
const val runtimeKtx = "androidx.work:work-runtime-ktx:${versions.work}"
const val rxjava2 ="androidx.work:work-rxjava2:${versions.work}"
+ const val multiprocess ="androidx.work:work-multiprocess:${versions.work}"
}
}
object autodispose {
@@ -126,7 +127,7 @@ object deps {
const val okio = "com.squareup.okio:okio:2.10.0"
const val okHttp3 = "com.squareup.okhttp3:okhttp:${versions.okHttp}"
const val okHttp3Logging = "com.squareup.okhttp3:logging-interceptor:${versions.okHttp}"
- const val coil = "io.coil-kt:coil:1.3.2"
+ const val coil = "io.coil-kt:coil:1.4.0"
const val retrofit = "com.squareup.retrofit2:retrofit:${versions.retrofit}"
const val retrofitRxJava2 = "com.squareup.retrofit2:adapter-rxjava2:${versions.retrofit}"
const val rxAndroid2 = "io.reactivex.rxjava2:rxandroid:2.1.1"
@@ -136,10 +137,11 @@ object deps {
const val rxPreferences = "com.f2prateek.rx.preferences2:rx-preferences:2.0.1"
const val rxRelay2 = "com.jakewharton.rxrelay2:rxrelay:2.1.1"
const val timber = "com.jakewharton.timber:timber:5.0.1"
- const val material = "com.google.android.material:material:1.4.0"
+ const val material = "com.google.android.material:material:1.5.0"
const val multitouchGestures = "com.dinuscxj:multitouchgesturedetector:1.0.0"
const val guava = "com.google.guava:guava:30.1.1-android"
const val harmony = "com.frybits.harmony:harmony:1.1.9"
+ const val startup = "androidx.startup:startup-runtime:1.1.1"
const val radialgamepad = "com.github.Swordfish90:RadialGamePad:${versions.radialgamepad}"
const val libretrodroid = "com.github.Swordfish90:LibretroDroid:${versions.libretrodroid}"
}
diff --git a/lemuroid-app-ext-free/build.gradle.kts b/lemuroid-app-ext-free/build.gradle.kts
index 70c981f710..7e34231c20 100644
--- a/lemuroid-app-ext-free/build.gradle.kts
+++ b/lemuroid-app-ext-free/build.gradle.kts
@@ -4,6 +4,12 @@ plugins {
id("kotlin-kapt")
}
+android {
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
dependencies {
implementation(project(":retrograde-util"))
implementation(project(":retrograde-app-shared"))
diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/core/CoreUpdaterImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/core/CoreUpdaterImpl.kt
index d47b999663..eb59bde738 100644
--- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/core/CoreUpdaterImpl.kt
+++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/core/CoreUpdaterImpl.kt
@@ -43,7 +43,7 @@ class CoreUpdaterImpl(
// This is the last tagged versions of cores.
companion object {
- private const val CORES_VERSION = "1.12"
+ private const val CORES_VERSION = "1.13"
}
private val baseUri = Uri.parse("https://github.com/Swordfish90/LemuroidCores/")
diff --git a/lemuroid-app/build.gradle.kts b/lemuroid-app/build.gradle.kts
index 95ac77ba23..b2282f768f 100644
--- a/lemuroid-app/build.gradle.kts
+++ b/lemuroid-app/build.gradle.kts
@@ -1,15 +1,14 @@
plugins {
id("com.android.application")
id("kotlin-android")
- id("kotlin-android-extensions")
id("kotlin-kapt")
id("androidx.navigation.safeargs.kotlin")
}
android {
defaultConfig {
- versionCode = 155
- versionName = "1.12.1"
+ versionCode = 161
+ versionName = "1.13.0" // Always remember to update Cores Tag!
applicationId = "com.swordfish.lemuroid"
}
@@ -34,7 +33,8 @@ android {
":lemuroid_core_ppsspp",
":lemuroid_core_prosystem",
":lemuroid_core_snes9x",
- ":lemuroid_core_stella"
+ ":lemuroid_core_stella",
+ ":lemuroid_core_citra"
)
}
@@ -88,15 +88,11 @@ android {
signingConfig = signingConfigs["release"]
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
resValue("string", "lemuroid_name", "Lemuroid")
- resValue("color", "main_color", "#00c64e")
- resValue("color", "main_color_light", "#9de3aa")
}
getByName("debug") {
applicationIdSuffix = ".debug"
versionNameSuffix = "-DEBUG"
resValue("string", "lemuroid_name", "LemuroiDebug")
- resValue("color", "main_color", "#f44336")
- resValue("color", "main_color_light", "#ef9a9a")
}
}
@@ -174,6 +170,7 @@ dependencies {
implementation(deps.libs.androidx.documentfile)
implementation(deps.libs.androidx.leanback.tvProvider)
implementation(deps.libs.harmony)
+ implementation(deps.libs.startup)
implementation(deps.libs.libretrodroid)
diff --git a/lemuroid-app/src/debug/res/values/leanback-colors.xml b/lemuroid-app/src/debug/res/values/leanback-colors.xml
new file mode 100644
index 0000000000..8a88477a2c
--- /dev/null
+++ b/lemuroid-app/src/debug/res/values/leanback-colors.xml
@@ -0,0 +1,5 @@
+
+ @color/surface_elevation_0dp
+ @color/surface_elevation_3dp
+ @color/surface_elevation_1dp
+
diff --git a/lemuroid-app/src/debug/res/values/material-colors.xml b/lemuroid-app/src/debug/res/values/material-colors.xml
new file mode 100644
index 0000000000..977056abc0
--- /dev/null
+++ b/lemuroid-app/src/debug/res/values/material-colors.xml
@@ -0,0 +1,62 @@
+
+ #f44336
+ #ef9a9a
+
+
+ #BC1714
+ #FFFFFF
+ #FFDAD3
+ #410001
+ #775652
+ #FFFFFF
+ #FFDAD4
+ #2C1512
+ #715C2E
+ #FFFFFF
+ #FCDFA6
+ #261A00
+ #BA1B1B
+ #FFDAD4
+ #FFFFFF
+ #410001
+ #FCFCFC
+ #211A19
+ #FCFCFC
+ #211A19
+ #F4DDDA
+ #534341
+ #857370
+ #FBEEEC
+ #362F2E
+ #FFB4A8
+ #000000
+ #FFB4A8
+ #FFB4A8
+ #680001
+ #940002
+ #FFDAD3
+ #E7BCB6
+ #442A26
+ #5D3F3B
+ #FFDAD4
+ #DFC38C
+ #3F2E04
+ #574419
+ #FCDFA6
+ #FFB4A9
+ #930006
+ #680003
+ #FFDAD4
+ #211A19
+ #EDE0DE
+ #211A19
+ #EDE0DE
+ #534341
+ #D8C2BF
+ #A08C89
+ #211A19
+ #EDE0DE
+ #BC1714
+ #000000
+ #BC1714
+
diff --git a/lemuroid-app/src/main/AndroidManifest.xml b/lemuroid-app/src/main/AndroidManifest.xml
index 468ebe640b..3d3c10a155 100644
--- a/lemuroid-app/src/main/AndroidManifest.xml
+++ b/lemuroid-app/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
@@ -8,6 +9,7 @@
+
+ android:theme="@style/LemuroidMaterialTheme">
+ android:name="com.swordfish.lemuroid.app.mobile.feature.main.MainActivity"
+ android:exported="true">
@@ -33,7 +36,8 @@
+ android:exported="true"
+ android:theme="@style/LemuroidMaterialTheme.Game">
@@ -45,21 +49,22 @@
android:process=":game"
android:launchMode="singleInstance"
android:configChanges="orientation|keyboardHidden|screenSize"
- android:theme="@style/GameTheme"/>
+ android:theme="@style/LemuroidMaterialTheme.Game"/>
+ android:theme="@style/LemuroidMaterialTheme.Menu"/>
+ android:theme="@style/LemuroidMaterialTheme.Invisible"/>
+ android:exported="true"
+ android:theme="@style/LemuroidLeanbackTheme">
@@ -72,7 +77,7 @@
+ android:theme="@style/LemuroidLeanbackPreferencesTheme" />
@@ -80,22 +85,27 @@
+ android:theme="@style/LemuroidMaterialTheme.Game" />
+
+
-
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ android:exported="false"
+ tools:node="merge">
+
+
+
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplication.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplication.kt
index d3ceacf345..3042989905 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplication.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplication.kt
@@ -1,29 +1,17 @@
package com.swordfish.lemuroid.app
import android.annotation.SuppressLint
-import android.app.ActivityManager
import android.content.Context
-import android.os.Build
-import android.os.Process
-import android.os.StrictMode
-import androidx.work.Configuration
import androidx.work.ListenableWorker
-import androidx.work.WorkManager
-import com.swordfish.lemuroid.BuildConfig
-import com.swordfish.lemuroid.app.shared.library.LibraryIndexScheduler
-import com.swordfish.lemuroid.app.shared.savesync.SaveSyncWork
+import com.google.android.material.color.DynamicColors
import com.swordfish.lemuroid.ext.feature.context.ContextHandler
import com.swordfish.lemuroid.lib.injection.HasWorkerInjector
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.DaggerApplication
-import timber.log.Timber
import javax.inject.Inject
class LemuroidApplication : DaggerApplication(), HasWorkerInjector {
- companion object {
- fun get(context: Context) = context.applicationContext as LemuroidApplication
- }
/*@Inject
lateinit var rxTimberTree: RxTimberTree
@@ -38,12 +26,7 @@ class LemuroidApplication : DaggerApplication(), HasWorkerInjector {
override fun onCreate() {
super.onCreate()
- initializeWorkManager()
-
- if (BuildConfig.DEBUG) {
- Timber.plant(Timber.DebugTree())
- enableStrictMode()
- }
+ DynamicColors.applyToActivitiesIfAvailable(this)
// var isPlanted = false
/* rxPrefs.getBoolean(getString(R.string.pref_key_flags_logging)).asObservable()
@@ -62,44 +45,6 @@ class LemuroidApplication : DaggerApplication(), HasWorkerInjector {
}*/
}
- private fun enableStrictMode() {
- StrictMode.setThreadPolicy(
- StrictMode.ThreadPolicy.Builder()
- .detectAll()
- .penaltyLog()
- .build()
- )
- }
-
- private fun initializeWorkManager() {
- val config = Configuration.Builder()
- .setMinimumLoggingLevel(android.util.Log.INFO)
- .build()
-
- WorkManager.initialize(this, config)
-
- if (isMainProcess()) {
- SaveSyncWork.enqueueAutoWork(applicationContext, 0)
- LibraryIndexScheduler.scheduleCoreUpdate(applicationContext)
- }
- }
-
- private fun isMainProcess(): Boolean {
- return retrieveProcessName() == packageName
- }
-
- private fun retrieveProcessName(): String? {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- return getProcessName()
- }
-
- val currentPID = Process.myPid()
- val manager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
- return manager.runningAppProcesses
- .firstOrNull { it.pid == currentPID }
- ?.processName
- }
-
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
ContextHandler.attachBaseContext(base)
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplicationComponent.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplicationComponent.kt
index cd6096275e..5d0fc0375d 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplicationComponent.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplicationComponent.kt
@@ -22,6 +22,7 @@ package com.swordfish.lemuroid.app
import com.swordfish.lemuroid.app.shared.library.CoreUpdateWork
import com.swordfish.lemuroid.app.shared.library.LibraryIndexWork
import com.swordfish.lemuroid.app.shared.savesync.SaveSyncWork
+import com.swordfish.lemuroid.app.shared.storage.cache.CacheCleanerWork
import com.swordfish.lemuroid.app.tv.LemuroidTVApplicationModule
import com.swordfish.lemuroid.app.tv.channel.ChannelUpdateWork
import com.swordfish.lemuroid.lib.injection.AndroidWorkerInjectionModule
@@ -39,6 +40,7 @@ import dagger.android.support.AndroidSupportInjectionModule
SaveSyncWork.Module::class,
ChannelUpdateWork.Module::class,
CoreUpdateWork.Module::class,
+ CacheCleanerWork.Module::class,
LemuroidTVApplicationModule::class
]
)
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplicationModule.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplicationModule.kt
index 13d67ab3d0..7b7b502487 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplicationModule.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplicationModule.kt
@@ -27,13 +27,15 @@ import com.swordfish.lemuroid.app.mobile.feature.gamemenu.GameMenuActivity
import com.swordfish.lemuroid.app.mobile.feature.main.MainActivity
import com.swordfish.lemuroid.app.mobile.feature.settings.RxSettingsManager
import com.swordfish.lemuroid.app.mobile.feature.shortcuts.ShortcutsGenerator
+import com.swordfish.lemuroid.app.shared.covers.CoverLoader
+import com.swordfish.lemuroid.app.shared.rumble.RumbleManager
import com.swordfish.lemuroid.app.shared.game.ExternalGameLauncherActivity
import com.swordfish.lemuroid.app.shared.game.GameLauncher
-import com.swordfish.lemuroid.app.shared.main.PostGameHandler
+import com.swordfish.lemuroid.app.shared.main.GameLaunchTaskHandler
import com.swordfish.lemuroid.app.shared.settings.BiosPreferences
import com.swordfish.lemuroid.app.shared.settings.ControllerConfigsManager
import com.swordfish.lemuroid.app.shared.settings.CoresSelectionPreferences
-import com.swordfish.lemuroid.app.shared.settings.GamePadManager
+import com.swordfish.lemuroid.app.shared.input.InputDeviceManager
import com.swordfish.lemuroid.app.shared.settings.GamePadPreferencesHelper
import com.swordfish.lemuroid.app.shared.settings.StorageFrameworkPickerLauncher
import com.swordfish.lemuroid.app.tv.channel.ChannelHandler
@@ -281,7 +283,7 @@ abstract class LemuroidApplicationModule {
@PerApp
@JvmStatic
fun gamepadsManager(context: Context, sharedPreferences: Lazy) =
- GamePadManager(context, sharedPreferences)
+ InputDeviceManager(context, sharedPreferences)
@Provides
@PerApp
@@ -306,8 +308,8 @@ abstract class LemuroidApplicationModule {
@Provides
@PerApp
@JvmStatic
- fun gamepadSettingsPreferences(gamePadManager: GamePadManager) =
- GamePadPreferencesHelper(gamePadManager)
+ fun inputDeviceManager(inputDeviceManager: InputDeviceManager) =
+ GamePadPreferencesHelper(inputDeviceManager)
@Provides
@PerApp
@@ -327,7 +329,7 @@ abstract class LemuroidApplicationModule {
@PerApp
@JvmStatic
fun postGameHandler(retrogradeDatabase: RetrogradeDatabase) =
- PostGameHandler(ReviewManager(), retrogradeDatabase)
+ GameLaunchTaskHandler(ReviewManager(), retrogradeDatabase)
@Provides
@PerApp
@@ -366,7 +368,27 @@ abstract class LemuroidApplicationModule {
@Provides
@PerApp
@JvmStatic
- fun gameLauncher(coresSelection: CoresSelection) =
- GameLauncher(coresSelection)
+ fun gameLauncher(
+ coresSelection: CoresSelection,
+ gameLaunchTaskHandler: GameLaunchTaskHandler
+ ) =
+ GameLauncher(coresSelection, gameLaunchTaskHandler)
+
+ @Provides
+ @PerApp
+ @JvmStatic
+ fun rumbleManager(
+ context: Context,
+ rxSettingsManager: RxSettingsManager,
+ inputDeviceManager: InputDeviceManager
+ ) =
+ RumbleManager(context, rxSettingsManager, inputDeviceManager)
+
+ @Provides
+ @PerApp
+ @JvmStatic
+ fun coverLoader(
+ context: Context
+ ) = CoverLoader(context)
}
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/favorites/FavoritesFragment.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/favorites/FavoritesFragment.kt
index 7a3c5271cf..ac0311586b 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/favorites/FavoritesFragment.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/favorites/FavoritesFragment.kt
@@ -10,14 +10,16 @@ import com.swordfish.lemuroid.app.mobile.shared.GamesAdapter
import com.swordfish.lemuroid.app.mobile.shared.GridSpaceDecoration
import com.swordfish.lemuroid.app.mobile.shared.RecyclerViewFragment
import com.swordfish.lemuroid.app.shared.GameInteractor
+import com.swordfish.lemuroid.app.shared.covers.CoverLoader
import com.swordfish.lemuroid.lib.library.db.RetrogradeDatabase
-import com.swordfish.lemuroid.lib.ui.setVisibleOrGone
+import com.swordfish.lemuroid.common.view.setVisibleOrGone
import javax.inject.Inject
class FavoritesFragment : RecyclerViewFragment() {
@Inject lateinit var retrogradeDb: RetrogradeDatabase
@Inject lateinit var gameInteractor: GameInteractor
+ @Inject lateinit var coverLoader: CoverLoader
private lateinit var favoritesViewModel: FavoritesViewModel
@@ -27,7 +29,7 @@ class FavoritesFragment : RecyclerViewFragment() {
favoritesViewModel = ViewModelProvider(this, FavoritesViewModel.Factory(retrogradeDb))
.get(FavoritesViewModel::class.java)
- val gamesAdapter = GamesAdapter(R.layout.layout_game_grid, gameInteractor)
+ val gamesAdapter = GamesAdapter(R.layout.layout_game_grid, gameInteractor, coverLoader)
favoritesViewModel.favorites.cachedIn(lifecycle).observe(viewLifecycleOwner) {
gamesAdapter.submitData(lifecycle, it)
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/game/GameActivity.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/game/GameActivity.kt
index a622add241..e41281c3ce 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/game/GameActivity.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/game/GameActivity.kt
@@ -19,7 +19,6 @@
package com.swordfish.lemuroid.app.mobile.feature.game
-import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.Configuration
@@ -45,34 +44,32 @@ import com.swordfish.lemuroid.common.math.linearInterpolation
import com.swordfish.lemuroid.common.rx.BehaviorRelayNullableProperty
import com.swordfish.lemuroid.common.rx.BehaviorRelayProperty
import com.swordfish.lemuroid.common.rx.RXUtils
+import com.swordfish.lemuroid.common.view.setVisibleOrGone
import com.swordfish.lemuroid.lib.controller.ControllerConfig
-import com.swordfish.lemuroid.lib.ui.setVisibleOrGone
+import com.swordfish.lemuroid.lib.controller.TouchControllerCustomizer
+import com.swordfish.lemuroid.lib.controller.TouchControllerSettingsManager
import com.swordfish.lemuroid.lib.util.subscribeBy
import com.swordfish.libretrodroid.GLRetroView
import com.swordfish.radialgamepad.library.RadialGamePad
-import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
-import com.swordfish.radialgamepad.library.config.RadialGamePadTheme
import com.swordfish.radialgamepad.library.event.Event
import com.swordfish.radialgamepad.library.event.GestureType
-import com.swordfish.touchinput.radial.RadialPadConfigs
-import com.swordfish.lemuroid.lib.controller.TouchControllerCustomizer
-import com.swordfish.lemuroid.lib.controller.TouchControllerSettingsManager
import com.swordfish.radialgamepad.library.haptics.HapticConfig
+import com.swordfish.touchinput.radial.LemuroidTouchConfigs
import com.swordfish.touchinput.radial.sensors.TiltSensor
import com.uber.autodispose.android.lifecycle.scope
import com.uber.autodispose.autoDispose
+import dagger.Lazy
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxkotlin.Observables
import io.reactivex.rxkotlin.subscribeBy
+import io.reactivex.schedulers.Schedulers
import timber.log.Timber
+import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.roundToInt
-import dagger.Lazy
-import io.reactivex.rxkotlin.Observables
-import io.reactivex.schedulers.Schedulers
-import java.util.concurrent.TimeUnit
class GameActivity : BaseGameActivity() {
@Inject lateinit var sharedPreferences: Lazy
@@ -100,6 +97,7 @@ class GameActivity : BaseGameActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
orientation = getCurrentOrientation()
tiltSensor = TiltSensor(applicationContext)
@@ -154,8 +152,8 @@ class GameActivity : BaseGameActivity() {
}
private fun isVirtualGamePadVisible(): Observable {
- return gamePadManager
- .getEnabledGamePadsObservable()
+ return inputDeviceManager
+ .getEnabledInputsObservable()
.map { it.isEmpty() }
}
@@ -182,31 +180,36 @@ class GameActivity : BaseGameActivity() {
else -> HapticConfig.OFF
}
- val leftPad = RadialGamePad(
- wrapGamePadConfig(
- applicationContext,
- touchControllerConfig.leftConfig,
- hapticConfig
- ),
- DEFAULT_MARGINS_DP,
- this
+ val leftConfig = LemuroidTouchConfigs.getRadialGamePadConfig(
+ touchControllerConfig.leftConfig,
+ hapticConfig,
+ leftGamePadContainer
)
+ val leftPad = RadialGamePad(leftConfig, DEFAULT_MARGINS_DP, this)
leftGamePadContainer.addView(leftPad)
- val rightPad = RadialGamePad(
- wrapGamePadConfig(
- applicationContext,
- touchControllerConfig.rightConfig,
- hapticConfig
- ),
- DEFAULT_MARGINS_DP,
- this
+ val rightConfig = LemuroidTouchConfigs.getRadialGamePadConfig(
+ touchControllerConfig.rightConfig,
+ hapticConfig,
+ leftGamePadContainer
)
+ val rightPad = RadialGamePad(rightConfig, DEFAULT_MARGINS_DP, this)
rightGamePadContainer.addView(rightPad)
val virtualPadEvents = Observable.merge(leftPad.events(), rightPad.events())
.share()
+ setupDefaultActions(virtualPadEvents)
+ setupTiltActions(virtualPadEvents)
+ setupVirtualMenuActions(virtualPadEvents)
+
+ this.leftPad = leftPad
+ this.rightPad = rightPad
+
+ this.touchControllerConfig = controllerConfig
+ }
+
+ private fun setupDefaultActions(virtualPadEvents: Observable) {
virtualControllerDisposables.add(
virtualPadEvents
.subscribeBy {
@@ -217,13 +220,12 @@ class GameActivity : BaseGameActivity() {
is Event.Direction -> {
handleGamePadDirection(it)
}
- is Event.Gesture -> {
- handleGamePadGesture(it)
- }
}
}
)
+ }
+ private fun setupTiltActions(virtualPadEvents: Observable) {
virtualControllerDisposables.add(
virtualPadEvents
.ofType(Event.Gesture::class.java)
@@ -249,29 +251,54 @@ class GameActivity : BaseGameActivity() {
}
}
)
+ }
- this.leftPad = leftPad
- this.rightPad = rightPad
+ private fun setupVirtualMenuActions(virtualPadEvents: Observable) {
+ VirtualLongPressHandler.initializeTheme(this)
- this.touchControllerConfig = controllerConfig
+ val allMenuButtonEvents = virtualPadEvents
+ .ofType(Event.Button::class.java)
+ .filter { it.id == KeyEvent.KEYCODE_BUTTON_MODE }
+ .share()
+
+ val cancelMenuButtonEvents = allMenuButtonEvents
+ .filter { it.action == KeyEvent.ACTION_UP }
+ .map { Unit }
+
+ virtualControllerDisposables.add(
+ allMenuButtonEvents
+ .filter { it.action == KeyEvent.ACTION_DOWN }
+ .concatMapMaybe {
+ VirtualLongPressHandler.displayLoading(
+ this,
+ R.drawable.ic_menu,
+ cancelMenuButtonEvents
+ )
+ .doOnSuccess {
+ displayOptionsDialog()
+ simulateVirtualGamepadHaptic()
+ }
+ }
+ .subscribeBy(Timber::e) { }
+ )
}
private fun handleTripleTaps(events: MutableList) {
val eventsTracker = when (events.map { it.id }.toSet()) {
- setOf(RadialPadConfigs.MOTION_SOURCE_LEFT_STICK) -> StickTiltTracker(
- RadialPadConfigs.MOTION_SOURCE_LEFT_STICK
+ setOf(LemuroidTouchConfigs.MOTION_SOURCE_LEFT_STICK) -> StickTiltTracker(
+ LemuroidTouchConfigs.MOTION_SOURCE_LEFT_STICK
)
- setOf(RadialPadConfigs.MOTION_SOURCE_RIGHT_STICK) -> StickTiltTracker(
- RadialPadConfigs.MOTION_SOURCE_RIGHT_STICK
+ setOf(LemuroidTouchConfigs.MOTION_SOURCE_RIGHT_STICK) -> StickTiltTracker(
+ LemuroidTouchConfigs.MOTION_SOURCE_RIGHT_STICK
)
- setOf(RadialPadConfigs.MOTION_SOURCE_DPAD) -> CrossTiltTracker(
- RadialPadConfigs.MOTION_SOURCE_DPAD
+ setOf(LemuroidTouchConfigs.MOTION_SOURCE_DPAD) -> CrossTiltTracker(
+ LemuroidTouchConfigs.MOTION_SOURCE_DPAD
)
- setOf(RadialPadConfigs.MOTION_SOURCE_DPAD_AND_LEFT_STICK) -> CrossTiltTracker(
- RadialPadConfigs.MOTION_SOURCE_DPAD_AND_LEFT_STICK
+ setOf(LemuroidTouchConfigs.MOTION_SOURCE_DPAD_AND_LEFT_STICK) -> CrossTiltTracker(
+ LemuroidTouchConfigs.MOTION_SOURCE_DPAD_AND_LEFT_STICK
)
- setOf(RadialPadConfigs.MOTION_SOURCE_RIGHT_DPAD) -> CrossTiltTracker(
- RadialPadConfigs.MOTION_SOURCE_RIGHT_DPAD
+ setOf(LemuroidTouchConfigs.MOTION_SOURCE_RIGHT_DPAD) -> CrossTiltTracker(
+ LemuroidTouchConfigs.MOTION_SOURCE_RIGHT_DPAD
)
setOf(
KeyEvent.KEYCODE_BUTTON_L1,
@@ -312,53 +339,30 @@ class GameActivity : BaseGameActivity() {
stopGameService()
}
- private fun getGamePadTheme(context: Context): RadialGamePadTheme {
- val accentColor = GraphicsUtils.colorToRgb(context.getColor(R.color.colorPrimary))
- val alpha = (255 * PRESSED_COLOR_ALPHA).roundToInt()
- val pressedColor = GraphicsUtils.rgbaToColor(accentColor + listOf(alpha))
- val simulatedColor = GraphicsUtils.rgbaToColor(accentColor + (255 * 0.25f).roundToInt())
- return RadialGamePadTheme(
- normalColor = context.getColor(R.color.touch_control_normal),
- pressedColor = pressedColor,
- simulatedColor = simulatedColor,
- primaryDialBackground = context.getColor(R.color.touch_control_background),
- textColor = context.getColor(R.color.touch_control_text)
- )
- }
-
- private fun wrapGamePadConfig(
- context: Context,
- config: RadialGamePadConfig,
- hapticConfig: HapticConfig
- ): RadialGamePadConfig {
- val padTheme = getGamePadTheme(context)
- return config.copy(theme = padTheme, haptic = hapticConfig)
- }
-
private fun handleGamePadButton(it: Event.Button) {
retroGameView?.sendKeyEvent(it.action, it.id)
}
private fun handleGamePadDirection(it: Event.Direction) {
when (it.id) {
- RadialPadConfigs.MOTION_SOURCE_DPAD -> {
+ LemuroidTouchConfigs.MOTION_SOURCE_DPAD -> {
retroGameView?.sendMotionEvent(GLRetroView.MOTION_SOURCE_DPAD, it.xAxis, it.yAxis)
}
- RadialPadConfigs.MOTION_SOURCE_LEFT_STICK -> {
+ LemuroidTouchConfigs.MOTION_SOURCE_LEFT_STICK -> {
retroGameView?.sendMotionEvent(
GLRetroView.MOTION_SOURCE_ANALOG_LEFT,
it.xAxis,
it.yAxis
)
}
- RadialPadConfigs.MOTION_SOURCE_RIGHT_STICK -> {
+ LemuroidTouchConfigs.MOTION_SOURCE_RIGHT_STICK -> {
retroGameView?.sendMotionEvent(
GLRetroView.MOTION_SOURCE_ANALOG_RIGHT,
it.xAxis,
it.yAxis
)
}
- RadialPadConfigs.MOTION_SOURCE_DPAD_AND_LEFT_STICK -> {
+ LemuroidTouchConfigs.MOTION_SOURCE_DPAD_AND_LEFT_STICK -> {
retroGameView?.sendMotionEvent(
GLRetroView.MOTION_SOURCE_ANALOG_LEFT,
it.xAxis,
@@ -366,7 +370,7 @@ class GameActivity : BaseGameActivity() {
)
retroGameView?.sendMotionEvent(GLRetroView.MOTION_SOURCE_DPAD, it.xAxis, it.yAxis)
}
- RadialPadConfigs.MOTION_SOURCE_RIGHT_DPAD -> {
+ LemuroidTouchConfigs.MOTION_SOURCE_RIGHT_DPAD -> {
retroGameView?.sendMotionEvent(
GLRetroView.MOTION_SOURCE_ANALOG_RIGHT,
it.xAxis,
@@ -376,13 +380,6 @@ class GameActivity : BaseGameActivity() {
}
}
- private fun handleGamePadGesture(it: Event.Gesture) {
- if (it.id == KeyEvent.KEYCODE_BUTTON_MODE) {
- displayOptionsDialog()
- simulateVirtualGamepadHaptic()
- }
- }
-
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
@@ -722,6 +719,9 @@ class GameActivity : BaseGameActivity() {
leftPad.gravityX = -1f
rightPad.gravityX = 1f
+ leftPad.secondaryDialSpacing = 0.1f
+ rightPad.secondaryDialSpacing = 0.1f
+
val constrainHeight = if (orientation == Configuration.ORIENTATION_PORTRAIT) {
ConstraintSet.WRAP_CONTENT
} else {
@@ -767,9 +767,6 @@ class GameActivity : BaseGameActivity() {
companion object {
const val DEFAULT_MARGINS_DP = 8f
-
- const val PRESSED_COLOR_ALPHA = 0.5f
-
const val DEFAULT_PRIMARY_DIAL_SIZE = 160f
}
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/game/VirtualLongPressHandler.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/game/VirtualLongPressHandler.kt
new file mode 100644
index 0000000000..30e1ed6d84
--- /dev/null
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/game/VirtualLongPressHandler.kt
@@ -0,0 +1,74 @@
+package com.swordfish.lemuroid.app.mobile.feature.game
+
+import android.graphics.drawable.GradientDrawable
+import android.view.View
+import android.view.ViewConfiguration
+import android.widget.ImageView
+import com.google.android.material.progressindicator.CircularProgressIndicator
+import com.swordfish.lemuroid.R
+import com.swordfish.lemuroid.common.view.animateProgress
+import com.swordfish.lemuroid.common.view.animateVisibleOrGone
+import com.swordfish.lemuroid.common.view.setVisibleOrGone
+import com.swordfish.touchinput.radial.LemuroidTouchOverlayThemes
+import io.reactivex.Maybe
+import io.reactivex.Observable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import java.util.concurrent.TimeUnit
+
+object VirtualLongPressHandler {
+
+ private val APPEAR_ANIMATION = (ViewConfiguration.getLongPressTimeout() * 0.1f).toLong()
+ private val DISAPPEAR_ANIMATION = (ViewConfiguration.getLongPressTimeout() * 2f).toLong()
+ private val LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong()
+
+ fun initializeTheme(gameActivity: GameActivity) {
+ val palette = LemuroidTouchOverlayThemes.getGamePadAlternate(longPressView(gameActivity))
+ longPressIconView(gameActivity).setColorFilter(palette.textColor)
+ longPressProgressBar(gameActivity).setIndicatorColor(palette.textColor)
+ longPressView(gameActivity).background = GradientDrawable().apply {
+ shape = GradientDrawable.OVAL
+ setColor(palette.normalColor)
+ }
+ }
+
+ fun displayLoading(
+ activity: GameActivity,
+ iconId: Int,
+ cancellation: Observable
+ ): Maybe {
+ return Observable.timer(LONG_PRESS_TIMEOUT, TimeUnit.MILLISECONDS)
+ .takeUntil(cancellation)
+ .firstElement()
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnSubscribe {
+ longPressView(activity).alpha = 0f
+ longPressIconView(activity).setImageResource(iconId)
+ }
+ .doAfterSuccess {
+ longPressView(activity).setVisibleOrGone(false)
+ }
+ .doOnSubscribe { displayLongPressView(activity) }
+ .doAfterTerminate { hideLongPressView(activity) }
+ .onErrorComplete()
+ .map { Unit }
+ }
+
+ private fun longPressIconView(activity: GameActivity) =
+ activity.findViewById(R.id.settings_loading_icon)
+
+ private fun longPressProgressBar(activity: GameActivity) =
+ activity.findViewById(R.id.settings_loading_progress)
+
+ private fun longPressView(activity: GameActivity) =
+ activity.findViewById(R.id.settings_loading)
+
+ private fun displayLongPressView(activity: GameActivity) {
+ longPressView(activity).animateVisibleOrGone(true, APPEAR_ANIMATION)
+ longPressProgressBar(activity).animateProgress(100, LONG_PRESS_TIMEOUT)
+ }
+
+ private fun hideLongPressView(activity: GameActivity) {
+ longPressView(activity).animateVisibleOrGone(false, DISAPPEAR_ANIMATION)
+ longPressProgressBar(activity).animateProgress(0, LONG_PRESS_TIMEOUT)
+ }
+}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/gamemenu/GameMenuCoreOptionsFragment.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/gamemenu/GameMenuCoreOptionsFragment.kt
index 2f09ca289a..9b3f3a302e 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/gamemenu/GameMenuCoreOptionsFragment.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/gamemenu/GameMenuCoreOptionsFragment.kt
@@ -7,7 +7,7 @@ import com.swordfish.lemuroid.R
import com.swordfish.lemuroid.app.shared.GameMenuContract
import com.swordfish.lemuroid.app.shared.coreoptions.CoreOptionsPreferenceHelper
import com.swordfish.lemuroid.app.shared.coreoptions.LemuroidCoreOption
-import com.swordfish.lemuroid.app.shared.settings.GamePadManager
+import com.swordfish.lemuroid.app.shared.input.InputDeviceManager
import com.swordfish.lemuroid.lib.library.SystemCoreConfig
import com.swordfish.lemuroid.lib.library.db.entity.Game
import com.swordfish.lemuroid.lib.preferences.SharedPreferencesHelper
@@ -21,7 +21,7 @@ import javax.inject.Inject
class GameMenuCoreOptionsFragment : PreferenceFragmentCompat() {
- @Inject lateinit var gamePadManager: GamePadManager
+ @Inject lateinit var inputDeviceManager: InputDeviceManager
override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this)
@@ -37,7 +37,7 @@ class GameMenuCoreOptionsFragment : PreferenceFragmentCompat() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- gamePadManager
+ inputDeviceManager
.getGamePadsObservable()
.observeOn(AndroidSchedulers.mainThread())
.autoDispose(scope())
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/games/GamesFragment.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/games/GamesFragment.kt
index 9b4b701abf..0f94680b9f 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/games/GamesFragment.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/games/GamesFragment.kt
@@ -9,6 +9,7 @@ import com.swordfish.lemuroid.R
import com.swordfish.lemuroid.app.mobile.shared.GamesAdapter
import com.swordfish.lemuroid.app.mobile.shared.RecyclerViewFragment
import com.swordfish.lemuroid.app.shared.GameInteractor
+import com.swordfish.lemuroid.app.shared.covers.CoverLoader
import com.swordfish.lemuroid.lib.library.db.RetrogradeDatabase
import javax.inject.Inject
@@ -16,6 +17,7 @@ class GamesFragment : RecyclerViewFragment() {
@Inject lateinit var retrogradeDb: RetrogradeDatabase
@Inject lateinit var gameInteractor: GameInteractor
+ @Inject lateinit var coverLoader: CoverLoader
private val args: GamesFragmentArgs by navArgs()
@@ -26,7 +28,7 @@ class GamesFragment : RecyclerViewFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- gamesAdapter = GamesAdapter(R.layout.layout_game_list, gameInteractor)
+ gamesAdapter = GamesAdapter(R.layout.layout_game_list, gameInteractor, coverLoader)
gamesViewModel = ViewModelProvider(this, GamesViewModel.Factory(retrogradeDb))
.get(GamesViewModel::class.java)
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/EpoxyGameView.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/EpoxyGameView.kt
index 65baf13fc6..86b5e39b41 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/EpoxyGameView.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/EpoxyGameView.kt
@@ -23,11 +23,14 @@ abstract class EpoxyGameView : EpoxyModelWithHolder() {
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
lateinit var gameInteractor: GameInteractor
+ @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
+ lateinit var coverLoader: CoverLoader
+
override fun bind(holder: Holder) {
holder.titleView?.text = game.title
holder.subtitleView?.let { it.text = GameUtils.getGameSubtitle(it.context, game) }
- CoverLoader.loadCover(game, holder.coverView)
+ coverLoader.loadCover(game, holder.coverView)
holder.itemView?.setOnClickListener { gameInteractor.onGamePlay(game) }
holder.itemView?.setOnCreateContextMenuListener(
@@ -38,7 +41,7 @@ abstract class EpoxyGameView : EpoxyModelWithHolder() {
override fun unbind(holder: Holder) {
holder.itemView?.setOnClickListener(null)
holder.coverView?.apply {
- CoverLoader.cancelRequest(this)
+ coverLoader.cancelRequest(this)
}
holder.itemView?.setOnCreateContextMenuListener(null)
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/EpoxyHomeController.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/EpoxyHomeController.kt
index 663355b21e..5062f7cfc0 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/EpoxyHomeController.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/EpoxyHomeController.kt
@@ -6,12 +6,14 @@ import com.swordfish.lemuroid.BuildConfig
import com.swordfish.lemuroid.R
import com.swordfish.lemuroid.app.mobile.shared.withModelsFrom
import com.swordfish.lemuroid.app.shared.GameInteractor
+import com.swordfish.lemuroid.app.shared.covers.CoverLoader
import com.swordfish.lemuroid.app.shared.settings.SettingsInteractor
import com.swordfish.lemuroid.lib.library.db.entity.Game
class EpoxyHomeController(
private val gameInteractor: GameInteractor,
- private val settingsInteractor: SettingsInteractor
+ private val settingsInteractor: SettingsInteractor,
+ private val coverLoader: CoverLoader
) : AsyncEpoxyController() {
private var recentGames = listOf()
@@ -71,6 +73,7 @@ class EpoxyHomeController(
.id(item.id)
.game(item)
.gameInteractor(this@EpoxyHomeController.gameInteractor)
+ .coverLoader(this@EpoxyHomeController.coverLoader)
}
}
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/HomeFragment.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/HomeFragment.kt
index fbbcd41abb..7205b06846 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/HomeFragment.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/HomeFragment.kt
@@ -12,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.Carousel
import com.swordfish.lemuroid.R
import com.swordfish.lemuroid.app.shared.GameInteractor
+import com.swordfish.lemuroid.app.shared.covers.CoverLoader
import com.swordfish.lemuroid.app.shared.settings.SettingsInteractor
import com.swordfish.lemuroid.lib.library.db.RetrogradeDatabase
import dagger.android.support.AndroidSupportInjection
@@ -21,6 +22,7 @@ class HomeFragment : Fragment() {
@Inject lateinit var retrogradeDb: RetrogradeDatabase
@Inject lateinit var gameInteractor: GameInteractor
+ @Inject lateinit var coverLoader: CoverLoader
@Inject lateinit var settingsInteractor: SettingsInteractor
override fun onAttach(context: Context) {
@@ -48,7 +50,7 @@ class HomeFragment : Fragment() {
// Disable snapping in carousel view
Carousel.setDefaultGlobalSnapHelperFactory(null)
- val pagingController = EpoxyHomeController(gameInteractor, settingsInteractor)
+ val pagingController = EpoxyHomeController(gameInteractor, settingsInteractor, coverLoader)
val recyclerView = view.findViewById(R.id.home_recyclerview)
val layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/HomeViewModel.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/HomeViewModel.kt
index ad4c81a495..e1b0617703 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/HomeViewModel.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/HomeViewModel.kt
@@ -3,7 +3,7 @@ package com.swordfish.lemuroid.app.mobile.feature.home
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
-import com.swordfish.lemuroid.app.shared.library.LibraryIndexMonitor
+import com.swordfish.lemuroid.app.shared.library.PendingOperationsMonitor
import com.swordfish.lemuroid.lib.library.db.RetrogradeDatabase
class HomeViewModel(appContext: Context, retrogradeDb: RetrogradeDatabase) : ViewModel() {
@@ -24,5 +24,5 @@ class HomeViewModel(appContext: Context, retrogradeDb: RetrogradeDatabase) : Vie
val recentGames = retrogradeDb.gameDao().selectFirstUnfavoriteRecents(CAROUSEL_MAX_ITEMS)
- val indexingInProgress = LibraryIndexMonitor(appContext).getLiveData()
+ val indexingInProgress = PendingOperationsMonitor(appContext).anyLibraryOperationInProgress()
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/main/MainActivity.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/main/MainActivity.kt
index d3b984fa74..45b33156af 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/main/MainActivity.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/main/MainActivity.kt
@@ -14,6 +14,7 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
+import com.google.android.material.elevation.SurfaceColors
import com.swordfish.lemuroid.R
import com.swordfish.lemuroid.app.mobile.feature.favorites.FavoritesFragment
import com.swordfish.lemuroid.app.mobile.feature.games.GamesFragment
@@ -31,7 +32,8 @@ import com.swordfish.lemuroid.app.shared.GameInteractor
import com.swordfish.lemuroid.app.shared.game.BaseGameActivity
import com.swordfish.lemuroid.app.shared.game.GameLauncher
import com.swordfish.lemuroid.app.shared.main.BusyActivity
-import com.swordfish.lemuroid.app.shared.main.PostGameHandler
+import com.swordfish.lemuroid.app.shared.main.GameLaunchTaskHandler
+import com.swordfish.lemuroid.app.shared.savesync.SaveSyncWork
import com.swordfish.lemuroid.app.shared.settings.SettingsInteractor
import com.swordfish.lemuroid.ext.feature.review.ReviewManager
import com.swordfish.lemuroid.lib.android.RetrogradeAppCompatActivity
@@ -40,7 +42,8 @@ import com.swordfish.lemuroid.lib.injection.PerFragment
import com.swordfish.lemuroid.lib.library.SystemID
import com.swordfish.lemuroid.lib.library.db.RetrogradeDatabase
import com.swordfish.lemuroid.lib.storage.DirectoriesManager
-import com.swordfish.lemuroid.lib.ui.setVisibleOrGone
+import com.swordfish.lemuroid.common.view.setVisibleOrGone
+import com.swordfish.lemuroid.lib.savesync.SaveSyncManager
import dagger.Provides
import dagger.android.ContributesAndroidInjector
import io.reactivex.rxkotlin.subscribeBy
@@ -49,13 +52,16 @@ import javax.inject.Inject
class MainActivity : RetrogradeAppCompatActivity(), BusyActivity {
- @Inject lateinit var postGameHandler: PostGameHandler
+ @Inject lateinit var gameLaunchTaskHandler: GameLaunchTaskHandler
+ @Inject lateinit var saveSyncManager: SaveSyncManager
private val reviewManager = ReviewManager()
private var mainViewModel: MainViewModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ window.navigationBarColor = SurfaceColors.SURFACE_2.getColor(this)
+ window.statusBarColor = SurfaceColors.SURFACE_2.getColor(this)
setContentView(R.layout.activity_main)
initializeActivity()
}
@@ -96,12 +102,19 @@ class MainActivity : RetrogradeAppCompatActivity(), BusyActivity {
when (requestCode) {
BaseGameActivity.REQUEST_PLAY_GAME -> {
- postGameHandler.handle(true, this, resultCode, data)
+ gameLaunchTaskHandler.handleGameFinish(true, this, resultCode, data)
.subscribeBy(Timber::e) { }
}
}
}
+ override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
+ val isSupported = saveSyncManager.isSupported()
+ val isConfigured = saveSyncManager.isConfigured()
+ menu?.findItem(R.id.menu_options_sync)?.isVisible = isSupported && isConfigured
+ return super.onPrepareOptionsMenu(menu)
+ }
+
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_mobile_settings, menu)
return super.onCreateOptionsMenu(menu)
@@ -113,6 +126,10 @@ class MainActivity : RetrogradeAppCompatActivity(), BusyActivity {
displayLemuroidHelp()
true
}
+ R.id.menu_options_sync -> {
+ SaveSyncWork.enqueueManualWork(this)
+ true
+ }
else -> super.onOptionsItemSelected(item)
}
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/main/MainViewModel.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/main/MainViewModel.kt
index db3a2fdf53..f860301491 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/main/MainViewModel.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/main/MainViewModel.kt
@@ -3,9 +3,7 @@ package com.swordfish.lemuroid.app.mobile.feature.main
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
-import com.swordfish.lemuroid.app.shared.library.LibraryIndexMonitor
-import com.swordfish.lemuroid.app.shared.savesync.SaveSyncMonitor
-import com.swordfish.lemuroid.app.utils.livedata.CombinedLiveData
+import com.swordfish.lemuroid.app.shared.library.PendingOperationsMonitor
class MainViewModel(appContext: Context) : ViewModel() {
@@ -15,7 +13,5 @@ class MainViewModel(appContext: Context) : ViewModel() {
}
}
- private val indexingInProgress = LibraryIndexMonitor(appContext).getLiveData()
- private val saveSyncInProgress = SaveSyncMonitor(appContext).getLiveData()
- val displayProgress = CombinedLiveData(indexingInProgress, saveSyncInProgress) { a, b -> a || b }
+ val displayProgress = PendingOperationsMonitor(appContext).anyOperationInProgress()
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/search/SearchFragment.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/search/SearchFragment.kt
index eac6a9e57f..f11b8cfda5 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/search/SearchFragment.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/search/SearchFragment.kt
@@ -15,8 +15,9 @@ import com.swordfish.lemuroid.R
import com.swordfish.lemuroid.app.mobile.shared.GamesAdapter
import com.swordfish.lemuroid.app.mobile.shared.RecyclerViewFragment
import com.swordfish.lemuroid.app.shared.GameInteractor
+import com.swordfish.lemuroid.app.shared.covers.CoverLoader
import com.swordfish.lemuroid.lib.library.db.RetrogradeDatabase
-import com.swordfish.lemuroid.lib.ui.setVisibleOrGone
+import com.swordfish.lemuroid.common.view.setVisibleOrGone
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
import com.uber.autodispose.android.lifecycle.scope
import com.uber.autodispose.autoDispose
@@ -29,6 +30,7 @@ class SearchFragment : RecyclerViewFragment() {
@Inject lateinit var retrogradeDb: RetrogradeDatabase
@Inject lateinit var gameInteractor: GameInteractor
+ @Inject lateinit var coverLoader: CoverLoader
private lateinit var searchViewModel: SearchViewModel
@@ -51,7 +53,7 @@ class SearchFragment : RecyclerViewFragment() {
searchViewModel = ViewModelProvider(this, SearchViewModel.Factory(retrogradeDb))
.get(SearchViewModel::class.java)
- val gamesAdapter = GamesAdapter(R.layout.layout_game_list, gameInteractor)
+ val gamesAdapter = GamesAdapter(R.layout.layout_game_list, gameInteractor, coverLoader)
searchViewModel.searchResults.cachedIn(lifecycle).observe(viewLifecycleOwner) {
gamesAdapter.submitData(lifecycle, it)
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/AdvancedSettingsFragment.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/AdvancedSettingsFragment.kt
index 4d07304e37..349e36f341 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/AdvancedSettingsFragment.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/AdvancedSettingsFragment.kt
@@ -6,6 +6,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.swordfish.lemuroid.R
+import com.swordfish.lemuroid.app.shared.settings.AdvancedSettingsPreferences
import com.swordfish.lemuroid.app.shared.settings.SettingsInteractor
import com.swordfish.lemuroid.lib.preferences.SharedPreferencesHelper
import dagger.android.support.AndroidSupportInjection
@@ -29,6 +30,7 @@ class AdvancedSettingsFragment : PreferenceFragmentCompat() {
preferenceManager.preferenceDataStore =
SharedPreferencesHelper.getSharedPreferencesDataStore(requireContext())
setPreferencesFromResource(R.xml.mobile_settings_advanced, rootKey)
+ AdvancedSettingsPreferences.updateCachePreferences(preferenceScreen)
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/GamepadSettingsFragment.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/GamepadSettingsFragment.kt
index 50fea6547a..f8415c668e 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/GamepadSettingsFragment.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/GamepadSettingsFragment.kt
@@ -6,7 +6,7 @@ import android.view.InputDevice
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.swordfish.lemuroid.R
-import com.swordfish.lemuroid.app.shared.settings.GamePadManager
+import com.swordfish.lemuroid.app.shared.input.InputDeviceManager
import com.swordfish.lemuroid.app.shared.settings.GamePadPreferencesHelper
import com.swordfish.lemuroid.lib.preferences.SharedPreferencesHelper
import com.uber.autodispose.android.lifecycle.scope
@@ -18,7 +18,7 @@ import javax.inject.Inject
class GamepadSettingsFragment : PreferenceFragmentCompat() {
@Inject lateinit var gamePadPreferencesHelper: GamePadPreferencesHelper
- @Inject lateinit var gamePadManager: GamePadManager
+ @Inject lateinit var inputDeviceManager: InputDeviceManager
override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this)
@@ -29,7 +29,7 @@ class GamepadSettingsFragment : PreferenceFragmentCompat() {
preferenceManager.preferenceDataStore =
SharedPreferencesHelper.getSharedPreferencesDataStore(requireContext())
setPreferencesFromResource(R.xml.empty_preference_screen, rootKey)
- gamePadManager.getGamePadsObservable()
+ inputDeviceManager.getGamePadsObservable()
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
.autoDispose(scope())
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/RxSettingsManager.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/RxSettingsManager.kt
index 92c5156a83..788409df23 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/RxSettingsManager.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/RxSettingsManager.kt
@@ -4,15 +4,14 @@ import android.content.Context
import android.content.SharedPreferences
import com.f2prateek.rx.preferences2.RxSharedPreferences
import com.swordfish.lemuroid.R
+import com.swordfish.lemuroid.lib.storage.cache.CacheCleaner
import dagger.Lazy
import io.reactivex.Single
+import io.reactivex.rxkotlin.Singles
import io.reactivex.schedulers.Schedulers
import kotlin.math.roundToInt
-class RxSettingsManager(
- private val context: Context,
- sharedPreferences: Lazy
-) {
+class RxSettingsManager(private val context: Context, sharedPreferences: Lazy) {
private val rxSharedPreferences = Single.fromCallable {
RxSharedPreferences.create(sharedPreferences.get())
@@ -39,6 +38,17 @@ class RxSettingsManager(
val syncStatesCores = stringSetPreference(R.string.pref_key_save_sync_cores, setOf())
+ val enableRumble = booleanPreference(R.string.pref_key_enable_rumble, false)
+
+ val enableDeviceRumble = booleanPreference(R.string.pref_key_enable_device_rumble, false)
+
+ val cacheSizeBytes = stringPreference(
+ R.string.pref_key_max_cache_size,
+ Single.fromCallable { CacheCleaner.getDefaultCacheLimit().toString() }
+ )
+
+ val allowDirectGameLoad = booleanPreference(R.string.pref_key_allow_direct_game_load, true)
+
private fun booleanPreference(keyId: Int, default: Boolean): Single {
return rxSharedPreferences.flatMap {
it.getBoolean(getString(keyId), default)
@@ -49,11 +59,15 @@ class RxSettingsManager(
}
private fun stringPreference(keyId: Int, default: String): Single {
- return rxSharedPreferences.flatMap {
- it.getString(getString(keyId), default)
+ return stringPreference(keyId, Single.just(default))
+ }
+
+ private fun stringPreference(keyId: Int, default: Single): Single {
+ return Singles.zip(rxSharedPreferences, default).flatMap { (preferences, defaultValue) ->
+ preferences.getString(getString(keyId), defaultValue)
.asObservable()
.subscribeOn(Schedulers.io())
- .first(default)
+ .first(defaultValue)
}
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SaveSyncFragment.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SaveSyncFragment.kt
index 84b6af9f43..57d5f623ff 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SaveSyncFragment.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SaveSyncFragment.kt
@@ -5,7 +5,7 @@ import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.swordfish.lemuroid.R
-import com.swordfish.lemuroid.app.shared.savesync.SaveSyncMonitor
+import com.swordfish.lemuroid.app.shared.library.PendingOperationsMonitor
import com.swordfish.lemuroid.app.shared.settings.SaveSyncPreferences
import com.swordfish.lemuroid.lib.preferences.SharedPreferencesHelper
import com.swordfish.lemuroid.lib.savesync.SaveSyncManager
@@ -43,7 +43,7 @@ class SaveSyncFragment : PreferenceFragmentCompat() {
override fun onResume() {
super.onResume()
saveSyncPreferences.updatePreferences(preferenceScreen, false)
- SaveSyncMonitor(requireContext()).getLiveData().observe(this) {
+ PendingOperationsMonitor(requireContext()).anySaveOperationInProgress().observe(this) {
saveSyncPreferences.updatePreferences(preferenceScreen, it)
}
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SettingsFragment.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SettingsFragment.kt
index 9d628f3bfb..9ccf5fbda1 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SettingsFragment.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SettingsFragment.kt
@@ -63,6 +63,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
val currentDirectory: Preference? = findPreference(getString(R.string.pref_key_extenral_folder))
val rescanPreference: Preference? = findPreference(getString(R.string.pref_key_rescan))
+ val stopRescanPreference: Preference? = findPreference(getString(R.string.pref_key_stop_rescan))
val displayBiosPreference: Preference? = findPreference(getString(R.string.pref_key_display_bios_info))
val resetSettings: Preference? = findPreference(getString(R.string.pref_key_reset_settings))
@@ -79,6 +80,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
displayBiosPreference?.isEnabled = !it
resetSettings?.isEnabled = !it
}
+
+ settingsViewModel.directoryScanInProgress.observe(this) {
+ stopRescanPreference?.isVisible = it
+ rescanPreference?.isVisible = !it
+ }
}
private fun getDisplayNameForFolderUri(uri: Uri) = DocumentFile.fromTreeUri(requireContext(), uri)?.name
@@ -86,6 +92,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when (preference?.key) {
getString(R.string.pref_key_rescan) -> rescanLibrary()
+ getString(R.string.pref_key_stop_rescan) -> stopRescanLibrary()
getString(R.string.pref_key_extenral_folder) -> handleChangeExternalFolder()
getString(R.string.pref_key_open_gamepad_settings) -> handleOpenGamePadSettings()
getString(R.string.pref_key_open_save_sync_settings) -> handleDisplaySaveSync()
@@ -139,7 +146,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
private fun rescanLibrary() {
- context?.let { LibraryIndexScheduler.scheduleFullSync(it) }
+ context?.let { LibraryIndexScheduler.scheduleLibrarySync(it) }
+ }
+
+ private fun stopRescanLibrary() {
+ context?.let { LibraryIndexScheduler.cancelLibrarySync(it) }
}
@dagger.Module
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SettingsViewModel.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SettingsViewModel.kt
index 22ba8f6db1..e0ca22d817 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SettingsViewModel.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/SettingsViewModel.kt
@@ -5,7 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.f2prateek.rx.preferences2.RxSharedPreferences
import com.swordfish.lemuroid.R
-import com.swordfish.lemuroid.app.shared.library.LibraryIndexMonitor
+import com.swordfish.lemuroid.app.shared.library.PendingOperationsMonitor
class SettingsViewModel(
context: Context,
@@ -28,5 +28,7 @@ class SettingsViewModel(
.asObservable()
.filter { it.isNotBlank() }
- val indexingInProgress = LibraryIndexMonitor(context).getLiveData()
+ val indexingInProgress = PendingOperationsMonitor(context).anyLibraryOperationInProgress()
+
+ val directoryScanInProgress = PendingOperationsMonitor(context).isDirectoryScanInProgress()
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/systems/MetaSystemsFragment.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/systems/MetaSystemsFragment.kt
index e96255cedb..413b974288 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/systems/MetaSystemsFragment.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/systems/MetaSystemsFragment.kt
@@ -10,7 +10,7 @@ import com.swordfish.lemuroid.app.mobile.shared.GridSpaceDecoration
import com.swordfish.lemuroid.app.mobile.shared.RecyclerViewFragment
import com.swordfish.lemuroid.lib.library.MetaSystemID
import com.swordfish.lemuroid.lib.library.db.RetrogradeDatabase
-import com.swordfish.lemuroid.lib.ui.setVisibleOrGone
+import com.swordfish.lemuroid.common.view.setVisibleOrGone
import com.swordfish.lemuroid.lib.util.subscribeBy
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
import com.uber.autodispose.autoDispose
@@ -29,7 +29,10 @@ class MetaSystemsFragment : RecyclerViewFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- metaSystemsViewModel = ViewModelProvider(this, MetaSystemsViewModel.Factory(retrogradeDb))
+ metaSystemsViewModel = ViewModelProvider(
+ this,
+ MetaSystemsViewModel.Factory(retrogradeDb, requireContext().applicationContext)
+ )
.get(MetaSystemsViewModel::class.java)
metaSystemsAdapter = MetaSystemsAdapter { navigateToGames(it) }
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/systems/MetaSystemsViewModel.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/systems/MetaSystemsViewModel.kt
index 89df810cf0..1b9508af8c 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/systems/MetaSystemsViewModel.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/systems/MetaSystemsViewModel.kt
@@ -1,5 +1,6 @@
package com.swordfish.lemuroid.app.mobile.feature.systems
+import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.swordfish.lemuroid.app.shared.systems.MetaSystemInfo
@@ -8,20 +9,26 @@ import com.swordfish.lemuroid.lib.library.db.RetrogradeDatabase
import com.swordfish.lemuroid.lib.library.metaSystemID
import io.reactivex.Observable
-class MetaSystemsViewModel(retrogradeDb: RetrogradeDatabase) : ViewModel() {
+class MetaSystemsViewModel(retrogradeDb: RetrogradeDatabase, appContext: Context) : ViewModel() {
- class Factory(val retrogradeDb: RetrogradeDatabase) : ViewModelProvider.Factory {
+ class Factory(
+ val retrogradeDb: RetrogradeDatabase,
+ val appContext: Context
+ ) : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
- return MetaSystemsViewModel(retrogradeDb) as T
+ return MetaSystemsViewModel(retrogradeDb, appContext) as T
}
}
val availableMetaSystems: Observable> = retrogradeDb.gameDao()
.selectSystemsWithCount()
.map { systemCounts ->
- systemCounts.filter { (_, count) -> count > 0 }
+ systemCounts.asSequence()
+ .filter { (_, count) -> count > 0 }
.map { (systemId, count) -> GameSystem.findById(systemId).metaSystemID() to count }
.groupBy { (metaSystemId, _) -> metaSystemId }
.map { (metaSystemId, counts) -> MetaSystemInfo(metaSystemId, counts.sumBy { it.second }) }
+ .sortedBy { it.getName(appContext) }
+ .toList()
}
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/shared/GamesAdapter.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/shared/GamesAdapter.kt
index 69b2bcb2fb..708c8d802e 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/shared/GamesAdapter.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/shared/GamesAdapter.kt
@@ -28,12 +28,12 @@ class GameViewHolder(parent: View) : RecyclerView.ViewHolder(parent) {
favoriteToggle = itemView.findViewById(R.id.favorite_toggle)
}
- fun bind(game: Game, gameInteractor: GameInteractor) {
+ fun bind(game: Game, gameInteractor: GameInteractor, coverLoader: CoverLoader) {
titleView?.text = game.title
subtitleView?.text = GameUtils.getGameSubtitle(itemView.context, game)
favoriteToggle?.isChecked = game.isFavorite
- CoverLoader.loadCover(game, coverView)
+ coverLoader.loadCover(game, coverView)
itemView.setOnClickListener { gameInteractor.onGamePlay(game) }
itemView.setOnCreateContextMenuListener(GameContextMenuListener(gameInteractor, game))
@@ -43,9 +43,9 @@ class GameViewHolder(parent: View) : RecyclerView.ViewHolder(parent) {
}
}
- fun unbind() {
+ fun unbind(coverLoader: CoverLoader) {
coverView?.apply {
- CoverLoader.cancelRequest(this)
+ coverLoader.cancelRequest(this)
this.setImageDrawable(null)
}
itemView.setOnClickListener(null)
@@ -56,7 +56,8 @@ class GameViewHolder(parent: View) : RecyclerView.ViewHolder(parent) {
class GamesAdapter(
private val baseLayout: Int,
- private val gameInteractor: GameInteractor
+ private val gameInteractor: GameInteractor,
+ private val coverLoader: CoverLoader
) : PagingDataAdapter(Game.DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
@@ -64,10 +65,10 @@ class GamesAdapter(
}
override fun onBindViewHolder(holder: GameViewHolder, position: Int) {
- getItem(position)?.let { holder.bind(it, gameInteractor) }
+ getItem(position)?.let { holder.bind(it, gameInteractor, coverLoader) }
}
override fun onViewRecycled(holder: GameViewHolder) {
- holder.unbind()
+ holder.unbind(coverLoader)
}
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/shared/NotificationsManager.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/shared/NotificationsManager.kt
index 4d4b4b8344..90fad1d601 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/shared/NotificationsManager.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/shared/NotificationsManager.kt
@@ -11,6 +11,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.swordfish.lemuroid.R
import com.swordfish.lemuroid.app.mobile.feature.game.GameActivity
+import com.swordfish.lemuroid.app.shared.library.LibraryIndexBroadcastReceiver
import com.swordfish.lemuroid.lib.library.db.entity.Game
class NotificationsManager(private val applicationContext: Context) {
@@ -19,7 +20,12 @@ class NotificationsManager(private val applicationContext: Context) {
createDefaultNotificationChannel()
val intent = Intent(applicationContext, GameActivity::class.java)
- val contentIntent = PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ val contentIntent = PendingIntent.getActivity(
+ applicationContext,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
val title = game?.let {
applicationContext.getString(R.string.game_running_notification_title, game.title)
@@ -41,12 +47,27 @@ class NotificationsManager(private val applicationContext: Context) {
fun libraryIndexingNotification(): Notification {
createDefaultNotificationChannel()
+ val broadcastIntent = Intent(applicationContext, LibraryIndexBroadcastReceiver::class.java)
+ val broadcastPendingIntent: PendingIntent = PendingIntent.getBroadcast(
+ applicationContext,
+ 0,
+ broadcastIntent,
+ PendingIntent.FLAG_IMMUTABLE
+ )
+
val builder = NotificationCompat.Builder(applicationContext, DEFAULT_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_lemuroid_tiny)
.setContentTitle(applicationContext.getString(R.string.library_index_notification_title))
.setContentText(applicationContext.getString(R.string.library_index_notification_message))
.setProgress(100, 0, true)
.setPriority(NotificationCompat.PRIORITY_LOW)
+ .addAction(
+ NotificationCompat.Action(
+ null,
+ applicationContext.getString(R.string.library_index_notification_action_cancel),
+ broadcastPendingIntent
+ )
+ )
return builder.build()
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/covers/CoverLoader.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/covers/CoverLoader.kt
index 016c464809..7ed744fae5 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/covers/CoverLoader.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/covers/CoverLoader.kt
@@ -1,51 +1,65 @@
package com.swordfish.lemuroid.app.shared.covers
+import android.content.Context
import android.widget.ImageView
+import coil.ImageLoader
import coil.load
+import coil.util.CoilUtils
import com.swordfish.lemuroid.common.drawable.TextDrawable
import com.swordfish.lemuroid.common.graphics.ColorUtils
import com.swordfish.lemuroid.lib.library.db.entity.Game
+import okhttp3.OkHttpClient
-object CoverLoader {
+class CoverLoader(applicationContext: Context) {
+
+ private val imageLoader = ImageLoader.Builder(applicationContext)
+ .crossfade(true)
+ .okHttpClient {
+ OkHttpClient.Builder()
+ .cache(CoilUtils.createDefaultCache(applicationContext))
+ .addNetworkInterceptor(ThrottleFailedThumbnailsInterceptor)
+ .build()
+ }
+ .build()
fun loadCover(game: Game, imageView: ImageView?) {
if (imageView == null) return
- imageView.load(game.coverFrontUrl) {
- crossfade(true)
-
+ imageView.load(game.coverFrontUrl, imageLoader) {
val fallbackDrawable = getFallbackDrawable(game)
fallback(fallbackDrawable)
error(fallbackDrawable)
}
}
- fun getFallbackRemoteUrl(game: Game): String {
- val color = Integer.toHexString(computeColor(game)).substring(2)
- val title = computeTitle(game)
- return "https://fakeimg.pl/512x512/$color/fff/?font=bebas&text=$title"
+ fun cancelRequest(imageView: ImageView) {
+ // coil-kt automatically does that for us.
}
- fun getFallbackDrawable(game: Game) =
- TextDrawable(computeTitle(game), computeColor(game))
+ companion object {
+ fun getFallbackDrawable(game: Game) =
+ TextDrawable(computeTitle(game), computeColor(game))
- private fun computeTitle(game: Game): String {
- val sanitizedName = game.title
- .replace(Regex("\\(.*\\)"), "")
+ fun getFallbackRemoteUrl(game: Game): String {
+ val color = Integer.toHexString(computeColor(game)).substring(2)
+ val title = computeTitle(game)
+ return "https://fakeimg.pl/512x512/$color/fff/?font=bebas&text=$title"
+ }
- return sanitizedName.asSequence()
- .filter { it.isDigit() or it.isUpperCase() or (it == '&') }
- .take(3)
- .joinToString("")
- .ifBlank { game.title.first().toString() }
- .capitalize()
- }
+ private fun computeTitle(game: Game): String {
+ val sanitizedName = game.title
+ .replace(Regex("\\(.*\\)"), "")
- private fun computeColor(game: Game): Int {
- return ColorUtils.randomColor(game.title)
- }
+ return sanitizedName.asSequence()
+ .filter { it.isDigit() or it.isUpperCase() or (it == '&') }
+ .take(3)
+ .joinToString("")
+ .ifBlank { game.title.first().toString() }
+ .capitalize()
+ }
- fun cancelRequest(imageView: ImageView) {
- // coil-kt automatically does that for us.
+ private fun computeColor(game: Game): Int {
+ return ColorUtils.randomColor(game.title)
+ }
}
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/covers/ThrottleFailedThumbnailsInterceptor.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/covers/ThrottleFailedThumbnailsInterceptor.kt
new file mode 100644
index 0000000000..9e549e3c82
--- /dev/null
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/covers/ThrottleFailedThumbnailsInterceptor.kt
@@ -0,0 +1,26 @@
+package com.swordfish.lemuroid.app.shared.covers
+
+import android.util.LruCache
+import okhttp3.Interceptor
+import okhttp3.Response
+import java.io.IOException
+
+object ThrottleFailedThumbnailsInterceptor : Interceptor {
+
+ private val failedThumbnailsStatusCode = LruCache(256 * 1024)
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val requestUrl = chain.request().url.toString()
+ val previousFailure = failedThumbnailsStatusCode[requestUrl]
+ if (previousFailure != null) {
+ throw IOException("Thumbnail previously failed with code: $previousFailure")
+ }
+
+ val response = chain.proceed(chain.request())
+ if (!response.isSuccessful) {
+ failedThumbnailsStatusCode.put(chain.request().url.toString(), response.code)
+ }
+
+ return response
+ }
+}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/BaseGameActivity.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/BaseGameActivity.kt
index cabd562234..d7ca81cb08 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/BaseGameActivity.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/BaseGameActivity.kt
@@ -25,21 +25,18 @@ import com.swordfish.lemuroid.app.mobile.feature.game.GameActivity
import com.swordfish.lemuroid.app.mobile.feature.settings.RxSettingsManager
import com.swordfish.lemuroid.app.shared.GameMenuContract
import com.swordfish.lemuroid.app.shared.ImmersiveActivity
+import com.swordfish.lemuroid.app.shared.rumble.RumbleManager
import com.swordfish.lemuroid.app.shared.coreoptions.CoreOption
import com.swordfish.lemuroid.app.shared.coreoptions.LemuroidCoreOption
-import com.swordfish.lemuroid.app.shared.savesync.SaveSyncWork
import com.swordfish.lemuroid.app.shared.settings.ControllerConfigsManager
-import com.swordfish.lemuroid.app.shared.settings.GamePadManager
+import com.swordfish.lemuroid.app.shared.input.InputDeviceManager
+import com.swordfish.lemuroid.app.shared.input.getInputClass
import com.swordfish.lemuroid.app.tv.game.TVGameActivity
import com.swordfish.lemuroid.common.animationDuration
import com.swordfish.lemuroid.common.displayToast
import com.swordfish.lemuroid.common.dump
import com.swordfish.lemuroid.common.graphics.GraphicsUtils
-import com.swordfish.lemuroid.common.graphics.takeScreenshot
import com.swordfish.lemuroid.common.kotlin.NTuple4
-import com.swordfish.lemuroid.common.kotlin.filterNotNullValues
-import com.swordfish.lemuroid.common.kotlin.toIndexedMap
-import com.swordfish.lemuroid.common.kotlin.zipOnKeys
import com.swordfish.lemuroid.common.rx.BehaviorRelayNullableProperty
import com.swordfish.lemuroid.common.rx.BehaviorRelayProperty
import com.swordfish.lemuroid.common.rx.RXUtils
@@ -60,8 +57,13 @@ import com.swordfish.lemuroid.lib.saves.SavesManager
import com.swordfish.lemuroid.lib.saves.StatesManager
import com.swordfish.lemuroid.lib.saves.StatesPreviewManager
import com.swordfish.lemuroid.lib.storage.RomFiles
-import com.swordfish.lemuroid.lib.storage.cache.CacheCleanerWork
-import com.swordfish.lemuroid.lib.ui.setVisibleOrGone
+import com.swordfish.lemuroid.common.graphics.takeScreenshot
+import com.swordfish.lemuroid.common.kotlin.NTuple5
+import com.swordfish.lemuroid.common.kotlin.filterNotNullValues
+import com.swordfish.lemuroid.common.kotlin.toIndexedMap
+import com.swordfish.lemuroid.common.kotlin.zipOnKeys
+import com.swordfish.lemuroid.common.longAnimationDuration
+import com.swordfish.lemuroid.common.view.setVisibleOrGone
import com.swordfish.lemuroid.lib.util.subscribeBy
import com.swordfish.libretrodroid.Controller
import com.swordfish.libretrodroid.GLRetroView
@@ -109,9 +111,10 @@ abstract class BaseGameActivity : ImmersiveActivity() {
@Inject lateinit var statesPreviewManager: StatesPreviewManager
@Inject lateinit var savesManager: SavesManager
@Inject lateinit var coreVariablesManager: CoreVariablesManager
- @Inject lateinit var gamePadManager: GamePadManager
+ @Inject lateinit var inputDeviceManager: InputDeviceManager
@Inject lateinit var gameLoader: GameLoader
@Inject lateinit var controllerConfigsManager: ControllerConfigsManager
+ @Inject lateinit var rumbleManager: RumbleManager
private var defaultExceptionHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler()
@@ -168,6 +171,15 @@ abstract class BaseGameActivity : ImmersiveActivity() {
}
}
+ private fun initializeRumble() {
+ retroGameViewMaybe()
+ .flatMapCompletable {
+ rumbleManager.processRumbleEvents(systemCoreConfig, it.getRumbleEvents())
+ }
+ .autoDispose(scope())
+ .subscribeBy(Timber::e) { }
+ }
+
private fun setUpExceptionsHandler() {
Thread.setDefaultUncaughtExceptionHandler { thread, exception ->
performUnsuccessfulActivityFinish(exception)
@@ -211,6 +223,7 @@ abstract class BaseGameActivity : ImmersiveActivity() {
gameData: GameLoader.GameData,
screenFilter: String,
lowLatencyAudio: Boolean,
+ enableRumble: Boolean
): GLRetroView {
val data = GLRetroViewData(this).apply {
coreFilePath = gameData.coreLibrary
@@ -231,6 +244,8 @@ abstract class BaseGameActivity : ImmersiveActivity() {
saveRAMState = gameData.saveRAMData
shader = getShaderForSystem(screenFilter, system)
preferLowLatencyAudio = lowLatencyAudio
+ rumbleEventsEnabled = enableRumble
+ skipDuplicateFrames = systemCoreConfig.skipDuplicateFrames
}
val retroGameView = GLRetroView(this, data)
@@ -366,6 +381,7 @@ abstract class BaseGameActivity : ImmersiveActivity() {
SystemID.NGC -> GLRetroView.SHADER_LCD
SystemID.WS -> GLRetroView.SHADER_LCD
SystemID.WSC -> GLRetroView.SHADER_LCD
+ SystemID.NINTENDO_3DS -> GLRetroView.SHADER_LCD
}
}
}
@@ -419,6 +435,8 @@ abstract class BaseGameActivity : ImmersiveActivity() {
.subscribeBy(Timber::e) {
controllerConfigs = it
}
+
+ initializeRumble()
}
private fun getCoreOptions(): List {
@@ -454,7 +472,7 @@ abstract class BaseGameActivity : ImmersiveActivity() {
}
private fun setupGamePadShortcuts() {
- gamePadManager.getGamePadMenuShortCutObservable()
+ inputDeviceManager.getInputMenuShortCutObservable()
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
.autoDispose(scope())
@@ -469,7 +487,7 @@ abstract class BaseGameActivity : ImmersiveActivity() {
private fun setupGamePadMotions() {
val events = Observables.combineLatest(
- gamePadManager.getGamePadsPortMapperObservable(),
+ inputDeviceManager.getGamePadsPortMapperObservable(),
motionEventsSubjects
).share()
@@ -484,7 +502,7 @@ abstract class BaseGameActivity : ImmersiveActivity() {
events
.flatMap { (ports, event) ->
val port = ports(event.device)
- val axes = GamePadManager.TRIGGER_MOTIONS_TO_KEYS.entries
+ val axes = event.device.getInputClass().getAxesMap().entries
Observable.fromIterable(axes).map { (axis, button) ->
val action = if (event.getAxisValue(axis) > 0.5) {
KeyEvent.ACTION_DOWN
@@ -511,16 +529,17 @@ abstract class BaseGameActivity : ImmersiveActivity() {
val pressedKeys = mutableSetOf()
val filteredKeyEvents = keyEventsSubjects
+ .filter { it.repeatCount == 0 }
.map { Triple(it.device, it.action, it.keyCode) }
.distinctUntilChanged()
- val shortcutKeys = gamePadManager.getGamePadMenuShortCutObservable()
+ val shortcutKeys = inputDeviceManager.getInputMenuShortCutObservable()
.map { it.toNullable()?.keys ?: setOf() }
val combinedObservable = RXUtils.combineLatest(
shortcutKeys,
- gamePadManager.getGamePadsPortMapperObservable(),
- gamePadManager.getGamePadsBindingsObservable(),
+ inputDeviceManager.getGamePadsPortMapperObservable(),
+ inputDeviceManager.getInputBindingsObservable(),
filteredKeyEvents
)
@@ -659,7 +678,7 @@ abstract class BaseGameActivity : ImmersiveActivity() {
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
- if (event != null && keyCode in GamePadManager.INPUT_KEYS) {
+ if (event != null && keyCode in event.device.getInputClass().getInputKeys()) {
keyEventsSubjects.accept(event)
return true
}
@@ -667,7 +686,7 @@ abstract class BaseGameActivity : ImmersiveActivity() {
}
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
- if (event != null && keyCode in GamePadManager.INPUT_KEYS) {
+ if (event != null && keyCode in event.device.getInputClass().getInputKeys()) {
keyEventsSubjects.accept(event)
return true
}
@@ -703,8 +722,6 @@ abstract class BaseGameActivity : ImmersiveActivity() {
putExtra(PLAY_GAME_RESULT_LEANBACK, intent.getBooleanExtra(EXTRA_LEANBACK, false))
}
- rescheduleBackgroundWork()
-
setResult(Activity.RESULT_OK, resultIntent)
finishAndExitProcess()
@@ -732,18 +749,6 @@ abstract class BaseGameActivity : ImmersiveActivity() {
open fun onFinishTriggered() { }
- private fun cancelBackgroundWork() {
- SaveSyncWork.cancelAutoWork(applicationContext)
- SaveSyncWork.cancelManualWork(applicationContext)
- CacheCleanerWork.cancelCleanCacheLRU(applicationContext)
- }
-
- private fun rescheduleBackgroundWork() {
- // Let's slightly delay the sync. Maybe the user wants to play another game.
- SaveSyncWork.enqueueAutoWork(applicationContext, 5)
- CacheCleanerWork.enqueueCleanCacheLRU(applicationContext)
- }
-
private fun getAutoSaveCompletable(game: Game): Completable {
return isAutoSaveEnabled()
.filter { it }
@@ -831,8 +836,11 @@ abstract class BaseGameActivity : ImmersiveActivity() {
}
}
- private fun reset() {
- retroGameView?.reset()
+ private fun reset(): Completable {
+ return Completable.timer(longAnimationDuration().toLong(), TimeUnit.MILLISECONDS)
+ .doOnSubscribe { loading = true }
+ .doAfterTerminate { loading = false }
+ .doOnComplete { retroGameView?.reset() }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -841,6 +849,10 @@ abstract class BaseGameActivity : ImmersiveActivity() {
Timber.i("Game menu dialog response: ${data?.extras.dump()}")
if (data?.getBooleanExtra(GameMenuContract.RESULT_RESET, false) == true) {
reset()
+ .autoDispose(scope())
+ .subscribeBy(Timber::e) {
+ retroGameView?.reset()
+ }
}
if (data?.hasExtra(GameMenuContract.RESULT_SAVE) == true) {
saveSlot(data.getIntExtra(GameMenuContract.RESULT_SAVE, 0))
@@ -882,27 +894,38 @@ abstract class BaseGameActivity : ImmersiveActivity() {
private fun loadGame() {
val requestLoadSave = intent.getBooleanExtra(EXTRA_LOAD_SAVE, false)
- cancelBackgroundWork()
-
setupLoadingView()
- Singles.zip(settingsManager.autoSave, settingsManager.screenFilter, settingsManager.lowLatencyAudio, ::Triple)
- .flatMapObservable { (autoSaveEnabled, filter, lowLatencyAudio) ->
+ Singles.zip(
+ settingsManager.autoSave,
+ settingsManager.screenFilter,
+ settingsManager.lowLatencyAudio,
+ settingsManager.enableRumble,
+ settingsManager.allowDirectGameLoad,
+ ::NTuple5
+ )
+ .flatMapObservable { (autoSaveEnabled, filter, lowLatencyAudio, enableRumble, directLoad) ->
gameLoader.load(
applicationContext,
game,
requestLoadSave && autoSaveEnabled,
- systemCoreConfig
- ).map { Triple(it, filter, lowLatencyAudio) }
+ systemCoreConfig,
+ directLoad
+ ).map { NTuple4(it, filter, lowLatencyAudio, enableRumble) }
}
.subscribeOn(Schedulers.single())
.observeOn(AndroidSchedulers.mainThread())
.autoDispose(scope())
.subscribe(
- { (loadingState, filter, lowLatencyAudio) ->
+ { (loadingState, filter, lowLatencyAudio, enableRumble) ->
displayLoadingState(loadingState)
if (loadingState is GameLoader.LoadingState.Ready) {
- retroGameView = initializeRetroGameView(loadingState.gameData, filter, lowLatencyAudio)
+ retroGameView = initializeRetroGameView(
+ loadingState.gameData,
+ filter,
+ lowLatencyAudio,
+ systemCoreConfig.rumbleSupported && enableRumble
+ )
}
},
{
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/ExternalGameLauncherActivity.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/ExternalGameLauncherActivity.kt
index 1d8e8b98d7..2a4f234f3a 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/ExternalGameLauncherActivity.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/ExternalGameLauncherActivity.kt
@@ -4,26 +4,22 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.lifecycle.LiveData
-import androidx.lifecycle.LiveDataReactiveStreams
import com.swordfish.lemuroid.R
import com.swordfish.lemuroid.app.shared.ImmersiveActivity
-import com.swordfish.lemuroid.app.shared.library.LibraryIndexMonitor
-import com.swordfish.lemuroid.app.shared.main.PostGameHandler
-import com.swordfish.lemuroid.app.shared.savesync.SaveSyncMonitor
+import com.swordfish.lemuroid.app.shared.library.PendingOperationsMonitor
+import com.swordfish.lemuroid.app.shared.main.GameLaunchTaskHandler
import com.swordfish.lemuroid.app.tv.channel.ChannelUpdateWork
import com.swordfish.lemuroid.app.tv.shared.TVHelper
import com.swordfish.lemuroid.app.utils.android.displayErrorDialog
-import com.swordfish.lemuroid.app.utils.livedata.CombinedLiveData
+import com.swordfish.lemuroid.app.utils.livedata.toObservable
import com.swordfish.lemuroid.common.animationDuration
import com.swordfish.lemuroid.lib.core.CoresSelection
-import com.swordfish.lemuroid.lib.library.GameSystem
import com.swordfish.lemuroid.lib.library.db.RetrogradeDatabase
-import com.swordfish.lemuroid.lib.ui.setVisibleOrGone
+import com.swordfish.lemuroid.common.view.setVisibleOrGone
import com.swordfish.lemuroid.lib.util.subscribeBy
import com.uber.autodispose.android.lifecycle.scope
import com.uber.autodispose.autoDispose
import io.reactivex.Completable
-import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.schedulers.Schedulers
@@ -40,8 +36,9 @@ import javax.inject.Inject
class ExternalGameLauncherActivity : ImmersiveActivity() {
@Inject lateinit var retrogradeDatabase: RetrogradeDatabase
- @Inject lateinit var postGameHandler: PostGameHandler
+ @Inject lateinit var gameLaunchTaskHandler: GameLaunchTaskHandler
@Inject lateinit var coresSelection: CoresSelection
+ @Inject lateinit var gameLauncher: GameLauncher
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -51,20 +48,16 @@ class ExternalGameLauncherActivity : ImmersiveActivity() {
val gameId = intent.data?.pathSegments?.let { it[it.size - 1].toInt() }!!
- val publisher = LiveDataReactiveStreams.toPublisher(this, getLoadingLiveData())
-
val loadingSubject = BehaviorSubject.createDefault(true)
- Observable.fromPublisher(publisher)
+ getLoadingLiveData()
+ .toObservable(this)
.filter { !it }
.firstElement()
- .flatMapSingle {
+ .flatMap {
retrogradeDatabase.gameDao()
- .selectById(gameId).subscribeOn(Schedulers.io())
- .flatMapSingle { game ->
- coresSelection.getCoreConfigForSystem(GameSystem.findById(game.systemId))
- .map { game to it }
- }
+ .selectById(gameId)
+ .subscribeOn(Schedulers.io())
}
.subscribeOn(Schedulers.io())
.delay(animationDuration().toLong(), TimeUnit.MILLISECONDS)
@@ -74,10 +67,10 @@ class ExternalGameLauncherActivity : ImmersiveActivity() {
.autoDispose(scope())
.subscribeBy(
{ displayErrorMessage() },
- { (game, systemCoreConfig) ->
- BaseGameActivity.launchGame(
+ { },
+ { game ->
+ gameLauncher.launchGameAsync(
this,
- systemCoreConfig,
game,
true,
TVHelper.isTV(applicationContext)
@@ -100,12 +93,7 @@ class ExternalGameLauncherActivity : ImmersiveActivity() {
}
private fun getLoadingLiveData(): LiveData {
- return CombinedLiveData(
- LibraryIndexMonitor(applicationContext).getLiveData(),
- SaveSyncMonitor(applicationContext).getLiveData()
- ) { libraryIndex, saveSync ->
- libraryIndex || saveSync
- }
+ return PendingOperationsMonitor(applicationContext).anyOperationInProgress()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -121,9 +109,10 @@ class ExternalGameLauncherActivity : ImmersiveActivity() {
Completable.complete()
}
- postGameHandler.handle(false, this, resultCode, data)
- .andThen { updateChannelCallback }
- .doAfterTerminate { finish() }
+ gameLaunchTaskHandler.handleGameFinish(false, this, resultCode, data)
+ .andThen(updateChannelCallback)
+ .observeOn(AndroidSchedulers.mainThread())
+ .doFinally { finish() }
.subscribeBy(Timber::e) { }
}
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/GameLauncher.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/GameLauncher.kt
index 0d1913d556..e7f2ee9f75 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/GameLauncher.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/game/GameLauncher.kt
@@ -1,19 +1,24 @@
package com.swordfish.lemuroid.app.shared.game
import android.app.Activity
+import com.swordfish.lemuroid.app.shared.main.GameLaunchTaskHandler
import com.swordfish.lemuroid.lib.core.CoresSelection
import com.swordfish.lemuroid.lib.library.GameSystem
import com.swordfish.lemuroid.lib.library.db.entity.Game
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.rxkotlin.subscribeBy
-class GameLauncher(private val coresSelection: CoresSelection) {
+class GameLauncher(
+ private val coresSelection: CoresSelection,
+ private val gameLaunchTaskHandler: GameLaunchTaskHandler
+) {
fun launchGameAsync(activity: Activity, game: Game, loadSave: Boolean, leanback: Boolean) {
val system = GameSystem.findById(game.systemId)
coresSelection.getCoreConfigForSystem(system)
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy {
+ gameLaunchTaskHandler.handleGameStart(activity.applicationContext)
BaseGameActivity.launchGame(activity, it, game, loadSave, leanback)
}
}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/gamemenu/GameMenuHelper.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/gamemenu/GameMenuHelper.kt
index 89a8054f45..041565463a 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/gamemenu/GameMenuHelper.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/gamemenu/GameMenuHelper.kt
@@ -35,7 +35,7 @@ object GameMenuHelper {
) {
val preference = screen.findPreference(FAST_FORWARD)
preference?.isChecked = fastForwardEnabled
- preference?.isEnabled = fastForwardSupported
+ preference?.isVisible = fastForwardSupported
}
fun setupSaveOption(
@@ -43,17 +43,17 @@ object GameMenuHelper {
systemCoreConfig: SystemCoreConfig
) {
val savesOption = screen.findPreference(SECTION_SAVE_GAME)
- savesOption?.isEnabled = systemCoreConfig.statesSupported
+ savesOption?.isVisible = systemCoreConfig.statesSupported
val loadOption = screen.findPreference(SECTION_LOAD_GAME)
- loadOption?.isEnabled = systemCoreConfig.statesSupported
+ loadOption?.isVisible = systemCoreConfig.statesSupported
}
fun setupSettingsOption(
screen: PreferenceScreen,
systemCoreConfig: SystemCoreConfig
) {
- screen.findPreference(SECTION_CORE_OPTIONS)?.isEnabled = sequenceOf(
+ screen.findPreference(SECTION_CORE_OPTIONS)?.isVisible = sequenceOf(
systemCoreConfig.exposedSettings.isNotEmpty(),
systemCoreConfig.exposedAdvancedSettings.isNotEmpty(),
systemCoreConfig.controllerConfigs.values.any { it.size > 1 }
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClass.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClass.kt
new file mode 100644
index 0000000000..3ca9cd768c
--- /dev/null
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClass.kt
@@ -0,0 +1,29 @@
+package com.swordfish.lemuroid.app.shared.input
+
+import android.content.Context
+import android.view.InputDevice
+import com.swordfish.lemuroid.app.shared.settings.GameMenuShortcut
+
+interface InputClass {
+ fun getInputKeys(): List
+
+ fun getAxesMap(): Map
+
+ fun getDefaultBindings(): Map
+
+ fun isSupported(device: InputDevice): Boolean
+
+ fun isEnabledByDefault(appContext: Context): Boolean
+
+ fun getCustomizableKeys(): List
+
+ fun getSupportedShortcuts(): List
+}
+
+fun InputDevice.getInputClass(): InputClass {
+ return when {
+ (sources and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD -> InputClassGamePad
+ (sources and InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD -> InputClassKeyboard
+ else -> InputClassUnknown
+ }
+}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClassGamePad.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClassGamePad.kt
new file mode 100644
index 0000000000..8d82c86cf5
--- /dev/null
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClassGamePad.kt
@@ -0,0 +1,137 @@
+package com.swordfish.lemuroid.app.shared.input
+
+import android.content.Context
+import android.view.InputDevice
+import android.view.KeyEvent
+import android.view.MotionEvent
+import com.swordfish.lemuroid.app.shared.settings.GameMenuShortcut
+
+object InputClassGamePad : InputClass {
+
+ private val MINIMAL_SUPPORTED_KEYS = intArrayOf(
+ KeyEvent.KEYCODE_BUTTON_A,
+ KeyEvent.KEYCODE_BUTTON_B,
+ KeyEvent.KEYCODE_BUTTON_X,
+ KeyEvent.KEYCODE_BUTTON_Y,
+ )
+
+ private val INPUT_KEYS = listOf(
+ KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.KEYCODE_DPAD_UP_LEFT,
+ KeyEvent.KEYCODE_DPAD_UP_RIGHT,
+ KeyEvent.KEYCODE_DPAD_DOWN_LEFT,
+ KeyEvent.KEYCODE_DPAD_DOWN_RIGHT,
+ KeyEvent.KEYCODE_BUTTON_A,
+ KeyEvent.KEYCODE_BUTTON_B,
+ KeyEvent.KEYCODE_BUTTON_X,
+ KeyEvent.KEYCODE_BUTTON_Y,
+ KeyEvent.KEYCODE_BUTTON_START,
+ KeyEvent.KEYCODE_BUTTON_SELECT,
+ KeyEvent.KEYCODE_BUTTON_L1,
+ KeyEvent.KEYCODE_BUTTON_R1,
+ KeyEvent.KEYCODE_BUTTON_L2,
+ KeyEvent.KEYCODE_BUTTON_R2,
+ KeyEvent.KEYCODE_BUTTON_THUMBL,
+ KeyEvent.KEYCODE_BUTTON_THUMBR,
+ KeyEvent.KEYCODE_BUTTON_C,
+ KeyEvent.KEYCODE_BUTTON_Z,
+ KeyEvent.KEYCODE_BUTTON_1,
+ KeyEvent.KEYCODE_BUTTON_2,
+ KeyEvent.KEYCODE_BUTTON_3,
+ KeyEvent.KEYCODE_BUTTON_4,
+ KeyEvent.KEYCODE_BUTTON_5,
+ KeyEvent.KEYCODE_BUTTON_6,
+ KeyEvent.KEYCODE_BUTTON_7,
+ KeyEvent.KEYCODE_BUTTON_8,
+ KeyEvent.KEYCODE_BUTTON_9,
+ KeyEvent.KEYCODE_BUTTON_10,
+ KeyEvent.KEYCODE_BUTTON_11,
+ KeyEvent.KEYCODE_BUTTON_12,
+ KeyEvent.KEYCODE_BUTTON_13,
+ KeyEvent.KEYCODE_BUTTON_14,
+ KeyEvent.KEYCODE_BUTTON_15,
+ KeyEvent.KEYCODE_BUTTON_16,
+ KeyEvent.KEYCODE_BUTTON_MODE
+ )
+
+ private val CUSTOMIZABLE_KEYS = listOf(
+ KeyEvent.KEYCODE_BUTTON_A,
+ KeyEvent.KEYCODE_BUTTON_B,
+ KeyEvent.KEYCODE_BUTTON_X,
+ KeyEvent.KEYCODE_BUTTON_Y,
+ KeyEvent.KEYCODE_BUTTON_START,
+ KeyEvent.KEYCODE_BUTTON_SELECT,
+ KeyEvent.KEYCODE_BUTTON_L1,
+ KeyEvent.KEYCODE_BUTTON_R1,
+ KeyEvent.KEYCODE_BUTTON_L2,
+ KeyEvent.KEYCODE_BUTTON_R2,
+ KeyEvent.KEYCODE_BUTTON_THUMBL,
+ KeyEvent.KEYCODE_BUTTON_THUMBR,
+ KeyEvent.KEYCODE_BUTTON_C,
+ KeyEvent.KEYCODE_BUTTON_Z,
+ KeyEvent.KEYCODE_BUTTON_1,
+ KeyEvent.KEYCODE_BUTTON_2,
+ KeyEvent.KEYCODE_BUTTON_3,
+ KeyEvent.KEYCODE_BUTTON_4,
+ KeyEvent.KEYCODE_BUTTON_5,
+ KeyEvent.KEYCODE_BUTTON_6,
+ KeyEvent.KEYCODE_BUTTON_7,
+ KeyEvent.KEYCODE_BUTTON_8,
+ KeyEvent.KEYCODE_BUTTON_9,
+ KeyEvent.KEYCODE_BUTTON_10,
+ KeyEvent.KEYCODE_BUTTON_11,
+ KeyEvent.KEYCODE_BUTTON_12,
+ KeyEvent.KEYCODE_BUTTON_13,
+ KeyEvent.KEYCODE_BUTTON_14,
+ KeyEvent.KEYCODE_BUTTON_15,
+ KeyEvent.KEYCODE_BUTTON_16,
+ KeyEvent.KEYCODE_BUTTON_MODE
+ )
+
+ private val AXES_MAP = mapOf(
+ MotionEvent.AXIS_BRAKE to KeyEvent.KEYCODE_BUTTON_L2,
+ MotionEvent.AXIS_THROTTLE to KeyEvent.KEYCODE_BUTTON_R2,
+ MotionEvent.AXIS_LTRIGGER to KeyEvent.KEYCODE_BUTTON_L2,
+ MotionEvent.AXIS_RTRIGGER to KeyEvent.KEYCODE_BUTTON_R2
+ )
+
+ private val DEFAULT_BINDINGS = mapOf(
+ KeyEvent.KEYCODE_BUTTON_A to KeyEvent.KEYCODE_BUTTON_B,
+ KeyEvent.KEYCODE_BUTTON_B to KeyEvent.KEYCODE_BUTTON_A,
+ KeyEvent.KEYCODE_BUTTON_X to KeyEvent.KEYCODE_BUTTON_Y,
+ KeyEvent.KEYCODE_BUTTON_Y to KeyEvent.KEYCODE_BUTTON_X
+ ).withDefault { if (it in InputDeviceManager.OUTPUT_KEYS) it else KeyEvent.KEYCODE_UNKNOWN }
+
+ override fun getInputKeys() = INPUT_KEYS
+
+ override fun getAxesMap() = AXES_MAP
+
+ override fun getDefaultBindings() = DEFAULT_BINDINGS
+
+ override fun isEnabledByDefault(appContext: Context): Boolean = true
+
+ override fun getCustomizableKeys(): List = CUSTOMIZABLE_KEYS
+
+ override fun getSupportedShortcuts(): List = listOf(
+ GameMenuShortcut(
+ "L3 + R3",
+ setOf(KeyEvent.KEYCODE_BUTTON_THUMBL, KeyEvent.KEYCODE_BUTTON_THUMBR)
+ ),
+ GameMenuShortcut(
+ "Select + Start",
+ setOf(KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_BUTTON_SELECT)
+ )
+ )
+
+ override fun isSupported(device: InputDevice): Boolean {
+ return sequenceOf(
+ device.sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD,
+ device.hasKeys(*MINIMAL_SUPPORTED_KEYS).all { it },
+ device.isVirtual.not(),
+ device.controllerNumber > 0
+ ).all { it }
+ }
+}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClassKeyboard.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClassKeyboard.kt
new file mode 100644
index 0000000000..30256b11cb
--- /dev/null
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClassKeyboard.kt
@@ -0,0 +1,152 @@
+package com.swordfish.lemuroid.app.shared.input
+
+import android.content.Context
+import android.view.InputDevice
+import android.view.KeyEvent
+import com.swordfish.lemuroid.app.shared.settings.GameMenuShortcut
+
+object InputClassKeyboard : InputClass {
+
+ private val INPUT_KEYS = listOf(
+ KeyEvent.KEYCODE_Q,
+ KeyEvent.KEYCODE_W,
+ KeyEvent.KEYCODE_E,
+ KeyEvent.KEYCODE_R,
+ KeyEvent.KEYCODE_T,
+ KeyEvent.KEYCODE_Y,
+ KeyEvent.KEYCODE_U,
+ KeyEvent.KEYCODE_I,
+ KeyEvent.KEYCODE_O,
+ KeyEvent.KEYCODE_P,
+ KeyEvent.KEYCODE_A,
+ KeyEvent.KEYCODE_S,
+ KeyEvent.KEYCODE_D,
+ KeyEvent.KEYCODE_F,
+ KeyEvent.KEYCODE_G,
+ KeyEvent.KEYCODE_H,
+ KeyEvent.KEYCODE_J,
+ KeyEvent.KEYCODE_K,
+ KeyEvent.KEYCODE_L,
+ KeyEvent.KEYCODE_Z,
+ KeyEvent.KEYCODE_X,
+ KeyEvent.KEYCODE_C,
+ KeyEvent.KEYCODE_V,
+ KeyEvent.KEYCODE_B,
+ KeyEvent.KEYCODE_N,
+ KeyEvent.KEYCODE_M,
+ KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.KEYCODE_ENTER,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_ESCAPE,
+ )
+
+ private val MINIMAL_SUPPORTED_KEYS = listOf(
+ KeyEvent.KEYCODE_Q,
+ KeyEvent.KEYCODE_W,
+ KeyEvent.KEYCODE_E,
+ KeyEvent.KEYCODE_R,
+ KeyEvent.KEYCODE_T,
+ KeyEvent.KEYCODE_Y,
+ KeyEvent.KEYCODE_U,
+ KeyEvent.KEYCODE_I,
+ KeyEvent.KEYCODE_O,
+ KeyEvent.KEYCODE_P,
+ KeyEvent.KEYCODE_A,
+ KeyEvent.KEYCODE_S,
+ KeyEvent.KEYCODE_D,
+ KeyEvent.KEYCODE_F,
+ KeyEvent.KEYCODE_G,
+ KeyEvent.KEYCODE_H,
+ KeyEvent.KEYCODE_J,
+ KeyEvent.KEYCODE_K,
+ KeyEvent.KEYCODE_L,
+ KeyEvent.KEYCODE_Z,
+ KeyEvent.KEYCODE_X,
+ KeyEvent.KEYCODE_C,
+ KeyEvent.KEYCODE_V,
+ KeyEvent.KEYCODE_B,
+ KeyEvent.KEYCODE_N,
+ KeyEvent.KEYCODE_M,
+ KeyEvent.KEYCODE_ENTER,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_ESCAPE,
+ ).toIntArray()
+
+ private val CUSTOMIZABLE_KEYS = listOf(
+ KeyEvent.KEYCODE_Q,
+ KeyEvent.KEYCODE_W,
+ KeyEvent.KEYCODE_E,
+ KeyEvent.KEYCODE_R,
+ KeyEvent.KEYCODE_T,
+ KeyEvent.KEYCODE_Y,
+ KeyEvent.KEYCODE_U,
+ KeyEvent.KEYCODE_I,
+ KeyEvent.KEYCODE_O,
+ KeyEvent.KEYCODE_P,
+ KeyEvent.KEYCODE_A,
+ KeyEvent.KEYCODE_S,
+ KeyEvent.KEYCODE_D,
+ KeyEvent.KEYCODE_F,
+ KeyEvent.KEYCODE_G,
+ KeyEvent.KEYCODE_H,
+ KeyEvent.KEYCODE_J,
+ KeyEvent.KEYCODE_K,
+ KeyEvent.KEYCODE_L,
+ KeyEvent.KEYCODE_Z,
+ KeyEvent.KEYCODE_X,
+ KeyEvent.KEYCODE_C,
+ KeyEvent.KEYCODE_V,
+ KeyEvent.KEYCODE_B,
+ KeyEvent.KEYCODE_N,
+ KeyEvent.KEYCODE_M,
+ KeyEvent.KEYCODE_ENTER,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ )
+
+ private val DEFAULT_BINDINGS = mapOf(
+ KeyEvent.KEYCODE_DPAD_UP to KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.KEYCODE_DPAD_DOWN to KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT to KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.KEYCODE_DPAD_RIGHT to KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.KEYCODE_W to KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.KEYCODE_A to KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.KEYCODE_S to KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.KEYCODE_D to KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.KEYCODE_I to KeyEvent.KEYCODE_BUTTON_X,
+ KeyEvent.KEYCODE_J to KeyEvent.KEYCODE_BUTTON_Y,
+ KeyEvent.KEYCODE_K to KeyEvent.KEYCODE_BUTTON_B,
+ KeyEvent.KEYCODE_L to KeyEvent.KEYCODE_BUTTON_A,
+ KeyEvent.KEYCODE_Q to KeyEvent.KEYCODE_BUTTON_L1,
+ KeyEvent.KEYCODE_E to KeyEvent.KEYCODE_BUTTON_L2,
+ KeyEvent.KEYCODE_U to KeyEvent.KEYCODE_BUTTON_R1,
+ KeyEvent.KEYCODE_O to KeyEvent.KEYCODE_BUTTON_R2,
+ KeyEvent.KEYCODE_ENTER to KeyEvent.KEYCODE_BUTTON_START,
+ KeyEvent.KEYCODE_SHIFT_LEFT to KeyEvent.KEYCODE_BUTTON_SELECT,
+ KeyEvent.KEYCODE_ESCAPE to KeyEvent.KEYCODE_BUTTON_MODE,
+ ).withDefault { KeyEvent.KEYCODE_UNKNOWN }
+
+ override fun getInputKeys() = INPUT_KEYS
+
+ override fun getAxesMap() = emptyMap()
+
+ override fun getDefaultBindings() = DEFAULT_BINDINGS
+
+ override fun getCustomizableKeys(): List = CUSTOMIZABLE_KEYS
+
+ override fun isEnabledByDefault(appContext: Context): Boolean {
+ return !appContext.packageManager.hasSystemFeature("android.hardware.touchscreen")
+ }
+
+ override fun getSupportedShortcuts(): List = emptyList()
+
+ override fun isSupported(device: InputDevice): Boolean {
+ return sequenceOf(
+ (device.sources and InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD,
+ device.hasKeys(*MINIMAL_SUPPORTED_KEYS).all { it },
+ device.isVirtual.not()
+ ).all { it }
+ }
+}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClassUnknown.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClassUnknown.kt
new file mode 100644
index 0000000000..1fa9b78ee6
--- /dev/null
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputClassUnknown.kt
@@ -0,0 +1,21 @@
+package com.swordfish.lemuroid.app.shared.input
+
+import android.content.Context
+import android.view.InputDevice
+import com.swordfish.lemuroid.app.shared.settings.GameMenuShortcut
+
+object InputClassUnknown : InputClass {
+ override fun getInputKeys(): List = emptyList()
+
+ override fun getAxesMap(): Map = emptyMap()
+
+ override fun getDefaultBindings(): Map = emptyMap()
+
+ override fun getCustomizableKeys(): List = emptyList()
+
+ override fun isSupported(device: InputDevice): Boolean = false
+
+ override fun isEnabledByDefault(appContext: Context): Boolean = false
+
+ override fun getSupportedShortcuts(): List = emptyList()
+}
diff --git a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/settings/GamePadManager.kt b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputDeviceManager.kt
similarity index 64%
rename from lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/settings/GamePadManager.kt
rename to lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputDeviceManager.kt
index d231f66fd9..05af7e3f9a 100644
--- a/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/settings/GamePadManager.kt
+++ b/lemuroid-app/src/main/java/com/swordfish/lemuroid/app/shared/input/InputDeviceManager.kt
@@ -1,14 +1,14 @@
-package com.swordfish.lemuroid.app.shared.settings
+package com.swordfish.lemuroid.app.shared.input
import android.content.Context
import android.content.SharedPreferences
import android.hardware.input.InputManager
import android.view.InputDevice
import android.view.KeyEvent
-import android.view.MotionEvent
import com.f2prateek.rx.preferences2.RxSharedPreferences
import com.gojuno.koptional.Optional
import com.gojuno.koptional.toOptional
+import com.swordfish.lemuroid.app.shared.settings.GameMenuShortcut
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single
@@ -17,8 +17,8 @@ import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.BehaviorSubject
import dagger.Lazy
-class GamePadManager(
- context: Context,
+class InputDeviceManager(
+ private val context: Context,
sharedPreferencesFactory: Lazy
) {
@@ -30,8 +30,8 @@ class GamePadManager(
RxSharedPreferences.create(sharedPreferencesFactory.get())
}
- fun getGamePadsBindingsObservable(): Observable<(InputDevice?)->Map> {
- return getEnabledGamePadsObservable()
+ fun getInputBindingsObservable(): Observable<(InputDevice?)->Map> {
+ return getEnabledInputsObservable()
.observeOn(Schedulers.io())
.flatMapSingle { inputDevices ->
Observable.fromIterable(inputDevices).flatMapSingle { inputDevice ->
@@ -42,24 +42,25 @@ class GamePadManager(
.map { bindings -> { bindings[it] ?: mapOf() } }
}
- fun getGamePadMenuShortCutObservable(): Observable> {
- return getEnabledGamePadsObservable()
+ fun getInputMenuShortCutObservable(): Observable> {
+ return getEnabledInputsObservable()
.observeOn(Schedulers.io())
.map { devices ->
- devices.firstOrNull()
+ val device = devices.firstOrNull()
+ device
?.let {
sharedPreferences.getString(
computeGameMenuShortcutPreference(it),
GameMenuShortcut.getDefault(it)?.name
)
}
- ?.let { GameMenuShortcut.findByName(it) }
+ ?.let { GameMenuShortcut.findByName(device, it) }
.toOptional()
}
}
fun getGamePadsPortMapperObservable(): Observable<(InputDevice?)->Int?> {
- return getEnabledGamePadsObservable().map { gamePads ->
+ return getEnabledInputsObservable().map { gamePads ->
val portMappings = gamePads
.mapIndexed { index, inputDevice -> inputDevice.id to index }
.toMap()
@@ -68,7 +69,7 @@ class GamePadManager(
}
private fun getBindings(inputDevice: InputDevice): Single