Make nightly builds a separate app

This allows users to have nightly builds alongside the production builds
This commit is contained in:
Rafael Caetano 2023-11-03 15:45:18 +00:00
parent 439094a718
commit c03c02f0af
37 changed files with 313 additions and 58 deletions

View File

@ -51,7 +51,7 @@ jobs:
echo "MELONDS_KEY_ALIAS=melonds" >> local.properties echo "MELONDS_KEY_ALIAS=melonds" >> local.properties
echo "MELONDS_KEY_PASSWORD=$MELONDS_KEYSTORE_PASSWORD" >> local.properties echo "MELONDS_KEY_PASSWORD=$MELONDS_KEYSTORE_PASSWORD" >> local.properties
chmod +x ./gradlew chmod +x ./gradlew
./gradlew :app:assembleGitHubRelease ./gradlew :app:assembleGitHubNightlyRelease
git tag -m "Nightly Release" -f -a nightly-release git tag -m "Nightly Release" -f -a nightly-release
git push -f origin refs/tags/nightly-release git push -f origin refs/tags/nightly-release
@ -64,12 +64,12 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
tag: nightly-release tag: nightly-release
name: 'Nightly Build' name: 'Nightly Build'
body: 'The currently Nightly Build. Whenever new changes are pushed, you can find the latest build here.' body: 'The currently Nightly Build. Whenever new changes are pushed, you can find the latest build here. You can keep this installation alongside your main one.'
artifacts: app/build/outputs/apk/gitHub/release/app-gitHub-release.apk artifacts: app/build/outputs/apk/gitHubNightly/release/app-gitHub-nightly-release.apk
artifactContentType: application/vnd.android.package-archive artifactContentType: application/vnd.android.package-archive
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: melonDS-android name: melonDS-android
path: app/build/outputs/apk/gitHub/release/app-gitHub-release.apk path: app/build/outputs/apk/gitHubNightly/release/app-gitHub-nightly-release.apk

View File

@ -35,7 +35,7 @@ jobs:
echo "MELONDS_KEY_ALIAS=melonds-playstore" >> local.properties echo "MELONDS_KEY_ALIAS=melonds-playstore" >> local.properties
echo "MELONDS_KEY_PASSWORD=$MELONDS_KEY_PASSWORD" >> local.properties echo "MELONDS_KEY_PASSWORD=$MELONDS_KEY_PASSWORD" >> local.properties
chmod +x ./gradlew chmod +x ./gradlew
./gradlew :app:assemblePlayStoreRelease ./gradlew :app:assemblePlayStoreProdRelease
- name: Get Version - name: Get Version
id: release_params id: release_params
@ -47,19 +47,19 @@ jobs:
serviceAccountJsonPlainText: ${{ secrets.MELONDS_PLAYSTORE_ACCOUNT_JSON }} serviceAccountJsonPlainText: ${{ secrets.MELONDS_PLAYSTORE_ACCOUNT_JSON }}
packageName: me.magnum.melonds packageName: me.magnum.melonds
releaseName: ${{ steps.release_params.outputs.VERSION }} releaseName: ${{ steps.release_params.outputs.VERSION }}
releaseFiles: app/build/outputs/apk/playStore/release/app-playStore-release.apk releaseFiles: app/build/outputs/apk/playStoreProd/release/app-playStore-prod-release.apk
track: beta track: beta
inAppUpdatePriority: 2 inAppUpdatePriority: 2
status: draft status: draft
whatsNewDirectory: ./.github/changelog/playStore whatsNewDirectory: ./.github/changelog/playStore
mappingFile: app/build/outputs/mapping/playStoreRelease/mapping.txt mappingFile: app/build/outputs/mapping/playStoreProdRelease/mapping.txt
debugSymbols: app/build/outputs/native-debug-symbols/playStoreRelease/native-debug-symbols.zip debugSymbols: app/build/outputs/native-debug-symbols/playStoreProdRelease/native-debug-symbols.zip
- name: Upload APK, Mapping and Debug Symbols - name: Upload APK, Mapping and Debug Symbols
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: playstore-release name: playstore-release
path: | path: |
app/build/outputs/apk/playStore/release/app-playStore-release.apk app/build/outputs/apk/playStoreProd/release/app-playStore-prod-release.apk
app/build/outputs/mapping/playStoreRelease/mapping.txt app/build/outputs/mapping/playStoreProdRelease/mapping.txt
app/build/outputs/native-debug-symbols/playStoreRelease/native-debug-symbols.zip app/build/outputs/native-debug-symbols/playStoreProdRelease/native-debug-symbols.zip

View File

@ -36,7 +36,7 @@ jobs:
echo "MELONDS_KEY_ALIAS=melonds" >> local.properties echo "MELONDS_KEY_ALIAS=melonds" >> local.properties
echo "MELONDS_KEY_PASSWORD=$MELONDS_KEYSTORE_PASSWORD" >> local.properties echo "MELONDS_KEY_PASSWORD=$MELONDS_KEYSTORE_PASSWORD" >> local.properties
chmod +x ./gradlew chmod +x ./gradlew
./gradlew :app:assembleGitHubRelease ./gradlew :app:assembleGitHubProdRelease
- name: Get Tag and Version - name: Get Tag and Version
id: release_params id: release_params
@ -52,7 +52,7 @@ jobs:
tag: ${{ steps.release_params.outputs.TAG }} tag: ${{ steps.release_params.outputs.TAG }}
name: ${{ steps.release_params.outputs.VERSION }} name: ${{ steps.release_params.outputs.VERSION }}
bodyFile: ./.github/changelog/gitHub.md bodyFile: ./.github/changelog/gitHub.md
artifacts: app/build/outputs/apk/gitHub/release/app-gitHub-release.apk artifacts: app/build/outputs/apk/gitHubProd/release/app-gitHub-prod-release.apk
artifactContentType: application/vnd.android.package-archive artifactContentType: application/vnd.android.package-archive
- name: Upload APK, Mapping and Debug Symbols - name: Upload APK, Mapping and Debug Symbols
@ -60,9 +60,9 @@ jobs:
with: with:
name: github-release name: github-release
path: | path: |
app/build/outputs/apk/gitHub/release/app-gitHub-release.apk app/build/outputs/apk/gitHubProd/release/app-gitHub-prod-release.apk
app/build/outputs/mapping/gitHubRelease/mapping.txt app/build/outputs/mapping/gitHubProdRelease/mapping.txt
app/build/outputs/native-debug-symbols/gitHubRelease/native-debug-symbols.zip app/build/outputs/native-debug-symbols/gitHubProdRelease/native-debug-symbols.zip
playstore-release: playstore-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -93,7 +93,7 @@ jobs:
echo "MELONDS_KEY_ALIAS=melonds-playstore" >> local.properties echo "MELONDS_KEY_ALIAS=melonds-playstore" >> local.properties
echo "MELONDS_KEY_PASSWORD=$MELONDS_KEY_PASSWORD" >> local.properties echo "MELONDS_KEY_PASSWORD=$MELONDS_KEY_PASSWORD" >> local.properties
chmod +x ./gradlew chmod +x ./gradlew
./gradlew :app:assemblePlayStoreRelease ./gradlew :app:assemblePlayStoreProdRelease
- name: Get Version - name: Get Version
id: release_params id: release_params
@ -105,19 +105,19 @@ jobs:
serviceAccountJsonPlainText: ${{ secrets.MELONDS_PLAYSTORE_ACCOUNT_JSON }} serviceAccountJsonPlainText: ${{ secrets.MELONDS_PLAYSTORE_ACCOUNT_JSON }}
packageName: me.magnum.melonds packageName: me.magnum.melonds
releaseName: ${{ steps.release_params.outputs.VERSION }} releaseName: ${{ steps.release_params.outputs.VERSION }}
releaseFiles: app/build/outputs/apk/playStore/release/app-playStore-release.apk releaseFiles: app/build/outputs/apk/playStoreProd/release/app-playStore-prod-release.apk
track: beta track: beta
inAppUpdatePriority: 2 inAppUpdatePriority: 2
status: draft status: draft
whatsNewDirectory: ./.github/changelog/playStore whatsNewDirectory: ./.github/changelog/playStore
mappingFile: app/build/outputs/mapping/playStoreRelease/mapping.txt mappingFile: app/build/outputs/mapping/playStoreProdRelease/mapping.txt
debugSymbols: app/build/outputs/native-debug-symbols/playStoreRelease/native-debug-symbols.zip debugSymbols: app/build/outputs/native-debug-symbols/playStoreProdRelease/native-debug-symbols.zip
- name: Upload APK, Mapping and Debug Symbols - name: Upload APK, Mapping and Debug Symbols
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: playstore-release name: playstore-release
path: | path: |
app/build/outputs/apk/playStore/release/app-playStore-release.apk app/build/outputs/apk/playStoreProd/release/app-playStore-prod-release.apk
app/build/outputs/mapping/playStoreRelease/mapping.txt app/build/outputs/mapping/playStoreProdRelease/mapping.txt
app/build/outputs/native-debug-symbols/playStoreRelease/native-debug-symbols.zip app/build/outputs/native-debug-symbols/playStoreProdRelease/native-debug-symbols.zip

View File

@ -27,6 +27,12 @@ third-party frontend with the following configuration:
* `uri` (preferred) - a string with the [SAF](https://developer.android.com/guide/topics/providers/create-document-provider) URI of the NDS ROM (ZIP files are supported) * `uri` (preferred) - a string with the [SAF](https://developer.android.com/guide/topics/providers/create-document-provider) URI of the NDS ROM (ZIP files are supported)
* `PATH` - a string with the absolute path to the NDS ROM (ZIP files are supported) * `PATH` - a string with the absolute path to the NDS ROM (ZIP files are supported)
# Nightly Builds
To have access to the latest changes, you can install nightly builds that you can find [here](https://github.com/rafaelvcaetano/melonDS-android/releases/tag/nightly-release).
Be aware that these builds can contain more bugs than usual and you may need to clear your app data to get it to work properly.
# Building # Building
To build the project you will need Android SDK, NDK and CMake. To build the project you will need Android SDK, NDK and CMake.
@ -36,9 +42,9 @@ To build the project you will need Android SDK, NDK and CMake.
`git clone --recurse-submodules https://github.com/rafaelvcaetano/melonDS-android.git` `git clone --recurse-submodules https://github.com/rafaelvcaetano/melonDS-android.git`
2. Install the Android SDK, NDK and CMake 2. Install the Android SDK, NDK and CMake
3. Build with: 3. Build with:
1. Unix: `./gradlew :app:assembleGitHubDebug` 1. Unix: `./gradlew :app:assembleGitHubProdDebug`
2. Windows: `gradlew.bat :app:assembleGitHubDebug` 2. Windows: `gradlew.bat :app:assembleGitHubProdDebug`
4. The generated APK can be found at `app/gitHub/debug` 4. The generated APK can be found at `app/gitHubProd/debug`
If you want to create a release build, you will need to modify your `local.properties` file to include the following fields: If you want to create a release build, you will need to modify your `local.properties` file to include the following fields:
* `MELONDS_KEYSTORE=<path_to_your_keystore>` * `MELONDS_KEYSTORE=<path_to_your_keystore>`

View File

@ -63,6 +63,7 @@ android {
} }
flavorDimensions.add("version") flavorDimensions.add("version")
flavorDimensions.add("build")
productFlavors { productFlavors {
create("playStore") { create("playStore") {
dimension = "version" dimension = "version"
@ -70,8 +71,19 @@ android {
} }
create("gitHub") { create("gitHub") {
dimension = "version" dimension = "version"
isDefault = true
versionNameSuffix = " GH" versionNameSuffix = " GH"
} }
create("prod") {
dimension = "build"
isDefault = true
}
create("nightly") {
dimension = "build"
applicationIdSuffix = ".nightly"
versionNameSuffix = " (NIGHTLY)"
}
} }
externalNativeBuild { externalNativeBuild {
cmake { cmake {

View File

@ -7,10 +7,8 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.domain.services.UpdateInstallManager import me.magnum.melonds.domain.services.UpdateInstallManager
import me.magnum.melonds.github.GitHubApi import me.magnum.melonds.github.GitHubApi
import me.magnum.melonds.github.repositories.GitHubUpdatesRepository
import me.magnum.melonds.github.services.GitHubUpdateInstallManager import me.magnum.melonds.github.services.GitHubUpdateInstallManager
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
@ -32,13 +30,6 @@ object GitHubModule {
return retrofit.create(GitHubApi::class.java) return retrofit.create(GitHubApi::class.java)
} }
@Provides
@Singleton
fun provideUpdatesRepository(@ApplicationContext context: Context, gitHubApi: GitHubApi): UpdatesRepository {
val gitHubPreferences = context.getSharedPreferences("preferences-github", Context.MODE_PRIVATE)
return GitHubUpdatesRepository(context, gitHubApi, gitHubPreferences)
}
@Provides @Provides
@Singleton @Singleton
fun provideUpdateInstallManager(@ApplicationContext context: Context): UpdateInstallManager { fun provideUpdateInstallManager(@ApplicationContext context: Context): UpdateInstallManager {

View File

@ -7,4 +7,7 @@ import retrofit2.http.GET
interface GitHubApi { interface GitHubApi {
@GET("/repos/rafaelvcaetano/melonDS-android/releases/latest") @GET("/repos/rafaelvcaetano/melonDS-android/releases/latest")
fun getLatestRelease(): Single<ReleaseDto> fun getLatestRelease(): Single<ReleaseDto>
@GET("/repos/rafaelvcaetano/melonDS-android/releases/tags/nightly-release")
fun getLatestNightlyRelease(): Single<ReleaseDto>
} }

View File

@ -0,0 +1,5 @@
package me.magnum.melonds.github
const val APK_CONTENT_TYPE = "application/vnd.android.package-archive"
const val PREF_KEY_GITHUB_CHECK_FOR_UPDATES = "github_check_for_updates"

View File

@ -6,5 +6,6 @@ data class ReleaseDto(
@SerializedName("tag_name") val tagName: String, @SerializedName("tag_name") val tagName: String,
@SerializedName("name") val name: String, @SerializedName("name") val name: String,
@SerializedName("body") val body: String, @SerializedName("body") val body: String,
@SerializedName("created_at") val createdAt: String,
@SerializedName("assets") val assets: List<AssetDto> @SerializedName("assets") val assets: List<AssetDto>
) )

View File

@ -10,7 +10,7 @@ import android.net.Uri
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import io.reactivex.Observable import io.reactivex.Observable
import me.magnum.melonds.common.providers.UpdateContentProvider import me.magnum.melonds.common.providers.UpdateContentProvider
import me.magnum.melonds.domain.model.AppUpdate import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.model.DownloadProgress import me.magnum.melonds.domain.model.DownloadProgress
import me.magnum.melonds.domain.services.UpdateInstallManager import me.magnum.melonds.domain.services.UpdateInstallManager
import java.io.File import java.io.File

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreference
android:key="github_check_for_updates"
android:title="@string/check_for_updates"
android:summary="@string/check_for_updates_summary"
app:iconSpaceReserved="false"
android:defaultValue="true" />
</PreferenceScreen>

View File

@ -0,0 +1,24 @@
package me.magnum.melonds.di
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.github.GitHubApi
import me.magnum.melonds.github.repositories.GitHubNightlyUpdatesRepository
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object GitHubNightlyModule {
@Provides
@Singleton
fun provideUpdatesRepository(@ApplicationContext context: Context, gitHubApi: GitHubApi): UpdatesRepository {
val gitHubPreferences = context.getSharedPreferences("preferences-github", Context.MODE_PRIVATE)
return GitHubNightlyUpdatesRepository(gitHubApi, gitHubPreferences)
}
}

View File

@ -0,0 +1,115 @@
package me.magnum.melonds.github.repositories
import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.core.net.toUri
import io.reactivex.Maybe
import io.reactivex.Single
import me.magnum.melonds.domain.model.Version
import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.github.APK_CONTENT_TYPE
import me.magnum.melonds.github.GitHubApi
import me.magnum.melonds.github.PREF_KEY_GITHUB_CHECK_FOR_UPDATES
import me.magnum.melonds.github.dtos.ReleaseDto
import java.time.Duration
import java.time.Instant
class GitHubNightlyUpdatesRepository(private val api: GitHubApi, private val preferences: SharedPreferences) : UpdatesRepository {
companion object {
private const val KEY_NEXT_CHECK_DATE = "github_updates_nightly_next_check_date"
private const val KEY_LAST_RELEASE_DATE = "github_updates_nightly_last_release_date"
}
override fun checkNewUpdate(): Maybe<AppUpdate> {
return shouldCheckUpdates()
.flatMapMaybe { checkUpdates ->
if (checkUpdates) {
api.getLatestNightlyRelease().flatMapMaybe { release ->
if (shouldUpdate(release)) {
val apkBinary = release.assets.firstOrNull { it.contentType == APK_CONTENT_TYPE }
if (apkBinary != null) {
val update = AppUpdate(
AppUpdate.Type.NIGHTLY,
apkBinary.id,
apkBinary.url.toUri(),
Version.fromString(release.tagName),
release.body,
apkBinary.size,
Instant.parse(release.createdAt),
)
Maybe.just(update)
} else {
Maybe.empty()
}
} else {
Maybe.empty()
}
}
} else {
Maybe.empty()
}
}
}
override fun skipUpdate(update: AppUpdate) {
scheduleNextUpdate()
}
override fun notifyUpdateDownloaded(update: AppUpdate) {
// This doesn't mean that the user has actually installed the update, but we have no way to determine that. As such, just assume that the update will be installed and
// store the date of the update
preferences.edit {
putLong(KEY_LAST_RELEASE_DATE, update.updateDate.toEpochMilli())
}
}
private fun shouldCheckUpdates(): Single<Boolean> {
return Single.create { emitter ->
val updateCheckEnabled = preferences.getBoolean(PREF_KEY_GITHUB_CHECK_FOR_UPDATES, true)
if (!updateCheckEnabled) {
emitter.onSuccess(false)
return@create
}
val nextUpdateCheckTime = preferences.getLong(KEY_NEXT_CHECK_DATE, -1)
if (nextUpdateCheckTime == (-1).toLong()) {
emitter.onSuccess(true)
return@create
}
val now = Instant.now()
val shouldCheckUpdates = now.toEpochMilli() > nextUpdateCheckTime
emitter.onSuccess(shouldCheckUpdates)
}
}
private fun scheduleNextUpdate() {
val now = Instant.now()
val nextUpdateDate = now + Duration.ofDays(1)
preferences.edit {
putLong(KEY_NEXT_CHECK_DATE, nextUpdateDate.toEpochMilli())
}
}
private fun shouldUpdate(releaseDto: ReleaseDto): Boolean {
val lastReleaseDate = preferences.getLong(KEY_LAST_RELEASE_DATE, -1)
if (lastReleaseDate == -1L) {
// If there is no last release date, then it's the first time the user is running the app and checking for updates. Ignore this release since we can't check if
// it's actually different from the one the user has installed, and save the release date in the preferences so that we can have a future reference
val releaseDate = Instant.parse(releaseDto.createdAt)
preferences.edit {
putLong(KEY_LAST_RELEASE_DATE, releaseDate.toEpochMilli())
}
scheduleNextUpdate()
return false
}
val thisReleaseDate = Instant.parse(releaseDto.createdAt)
return thisReleaseDate.toEpochMilli() > lastReleaseDate
}
}

View File

@ -0,0 +1,24 @@
package me.magnum.melonds.di
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.github.GitHubApi
import me.magnum.melonds.github.repositories.GitHubProdUpdatesRepository
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object GitHubProdModule {
@Provides
@Singleton
fun provideUpdatesRepository(@ApplicationContext context: Context, gitHubApi: GitHubApi): UpdatesRepository {
val gitHubPreferences = context.getSharedPreferences("preferences-github", Context.MODE_PRIVATE)
return GitHubProdUpdatesRepository(context, gitHubApi, gitHubPreferences)
}
}

View File

@ -6,17 +6,19 @@ import androidx.core.content.edit
import androidx.core.net.toUri import androidx.core.net.toUri
import io.reactivex.Maybe import io.reactivex.Maybe
import io.reactivex.Single import io.reactivex.Single
import me.magnum.melonds.domain.model.AppUpdate import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.model.Version import me.magnum.melonds.domain.model.Version
import me.magnum.melonds.domain.repositories.UpdatesRepository import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.github.GitHubApi import me.magnum.melonds.github.GitHubApi
import me.magnum.melonds.github.PREF_KEY_GITHUB_CHECK_FOR_UPDATES
import me.magnum.melonds.github.dtos.ReleaseDto import me.magnum.melonds.github.dtos.ReleaseDto
import me.magnum.melonds.utils.PackageManagerCompat import me.magnum.melonds.utils.PackageManagerCompat
import me.magnum.melonds.utils.enumValueOfIgnoreCase import me.magnum.melonds.utils.enumValueOfIgnoreCase
import java.time.Instant
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class GitHubUpdatesRepository(private val context: Context, private val api: GitHubApi, private val preferences: SharedPreferences) : UpdatesRepository { class GitHubProdUpdatesRepository(private val context: Context, private val api: GitHubApi, private val preferences: SharedPreferences) : UpdatesRepository {
companion object { companion object {
private const val APK_CONTENT_TYPE = "application/vnd.android.package-archive" private const val APK_CONTENT_TYPE = "application/vnd.android.package-archive"
private const val KEY_SKIP_VERSION = "github_updates_skip_version" private const val KEY_SKIP_VERSION = "github_updates_skip_version"
@ -35,11 +37,13 @@ class GitHubUpdatesRepository(private val context: Context, private val api: Git
val apkBinary = release.assets.firstOrNull { it.contentType == APK_CONTENT_TYPE } val apkBinary = release.assets.firstOrNull { it.contentType == APK_CONTENT_TYPE }
if (apkBinary != null) { if (apkBinary != null) {
val update = AppUpdate( val update = AppUpdate(
AppUpdate.Type.PRODUCTION,
apkBinary.id, apkBinary.id,
apkBinary.url.toUri(), apkBinary.url.toUri(),
Version.fromString(release.tagName), Version.fromString(release.tagName),
release.body, release.body,
apkBinary.size apkBinary.size,
Instant.parse(release.createdAt),
) )
Maybe.just(update) Maybe.just(update)
} else { } else {
@ -61,8 +65,18 @@ class GitHubUpdatesRepository(private val context: Context, private val api: Git
} }
} }
override fun notifyUpdateDownloaded(update: AppUpdate) {
// Do nothing
}
private fun shouldCheckUpdates(): Single<Boolean> { private fun shouldCheckUpdates(): Single<Boolean> {
return Single.create { emitter -> return Single.create { emitter ->
val updateCheckEnabled = preferences.getBoolean(PREF_KEY_GITHUB_CHECK_FOR_UPDATES, true)
if (!updateCheckEnabled) {
emitter.onSuccess(false)
return@create
}
val lastCheckUpdateTimestamp = preferences.getLong(KEY_LAST_UPDATE_CHECK, -1) val lastCheckUpdateTimestamp = preferences.getLong(KEY_LAST_UPDATE_CHECK, -1)
if (lastCheckUpdateTimestamp == (-1).toLong()) { if (lastCheckUpdateTimestamp == (-1).toLong()) {
emitter.onSuccess(true) emitter.onSuccess(true)

View File

@ -1,11 +0,0 @@
package me.magnum.melonds.domain.model
import android.net.Uri
data class AppUpdate(
val id: Long,
val downloadUri: Uri,
val newVersion: Version,
val description: String,
val binarySize: Long
)

View File

@ -0,0 +1,21 @@
package me.magnum.melonds.domain.model.appupdate
import android.net.Uri
import me.magnum.melonds.domain.model.Version
import java.time.Instant
data class AppUpdate(
val type: Type,
val id: Long,
val downloadUri: Uri,
val newVersion: Version,
val description: String,
val binarySize: Long,
val updateDate: Instant,
) {
enum class Type {
PRODUCTION,
NIGHTLY,
}
}

View File

@ -1,10 +1,10 @@
package me.magnum.melonds.domain.repositories package me.magnum.melonds.domain.repositories
import io.reactivex.Maybe import io.reactivex.Maybe
import io.reactivex.Observable import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.model.AppUpdate
interface UpdatesRepository { interface UpdatesRepository {
fun checkNewUpdate(): Maybe<AppUpdate> fun checkNewUpdate(): Maybe<AppUpdate>
fun skipUpdate(update: AppUpdate) fun skipUpdate(update: AppUpdate)
fun notifyUpdateDownloaded(update: AppUpdate)
} }

View File

@ -1,7 +1,7 @@
package me.magnum.melonds.domain.services package me.magnum.melonds.domain.services
import io.reactivex.Observable import io.reactivex.Observable
import me.magnum.melonds.domain.model.AppUpdate import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.model.DownloadProgress import me.magnum.melonds.domain.model.DownloadProgress
interface UpdateInstallManager { interface UpdateInstallManager {

View File

@ -27,6 +27,7 @@ import me.magnum.melonds.common.Permission
import me.magnum.melonds.common.contracts.DirectoryPickerContract import me.magnum.melonds.common.contracts.DirectoryPickerContract
import me.magnum.melonds.databinding.ActivityRomListBinding import me.magnum.melonds.databinding.ActivityRomListBinding
import me.magnum.melonds.domain.model.* import me.magnum.melonds.domain.model.*
import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.ui.dsiwaremanager.DSiWareManagerActivity import me.magnum.melonds.ui.dsiwaremanager.DSiWareManagerActivity
import me.magnum.melonds.ui.emulator.EmulatorActivity import me.magnum.melonds.ui.emulator.EmulatorActivity
import me.magnum.melonds.ui.settings.SettingsActivity import me.magnum.melonds.ui.settings.SettingsActivity
@ -98,7 +99,10 @@ class RomListActivity : AppCompatActivity() {
} }
updatesViewModel.getAppUpdate().observe(this) { updatesViewModel.getAppUpdate().observe(this) {
showUpdateAvailableDialog(it) when (it.type) {
AppUpdate.Type.PRODUCTION -> showProdUpdateAvailableDialog(it)
AppUpdate.Type.NIGHTLY -> showNightlyUpdateAvailableDialog(it)
}
} }
updatesViewModel.getDownloadProgress().observe(this) { updatesViewModel.getDownloadProgress().observe(this) {
onDownloadProgressUpdated(it) onDownloadProgressUpdated(it)
@ -199,7 +203,7 @@ class RomListActivity : AppCompatActivity() {
romListFragment.setRomSelectedListener { rom -> loadRom(rom) } romListFragment.setRomSelectedListener { rom -> loadRom(rom) }
} }
private fun showUpdateAvailableDialog(update: AppUpdate) { private fun showProdUpdateAvailableDialog(update: AppUpdate) {
val message = markwon.toMarkdown(update.description) val message = markwon.toMarkdown(update.description)
AlertDialog.Builder(this) AlertDialog.Builder(this)
@ -215,6 +219,19 @@ class RomListActivity : AppCompatActivity() {
.show() .show()
} }
private fun showNightlyUpdateAvailableDialog(update: AppUpdate) {
AlertDialog.Builder(this)
.setTitle(getString(R.string.nightly_update_available))
.setMessage(getString(R.string.nightly_update_available_message))
.setPositiveButton(R.string.update) { _, _ ->
startUpdateDownload(update)
}
.setNegativeButton(R.string.remind_later_update) { _, _ ->
updatesViewModel.skipUpdate(update)
}
.show()
}
private fun startUpdateDownload(update: AppUpdate) { private fun startUpdateDownload(update: AppUpdate) {
downloadProgressDialog?.dismiss() downloadProgressDialog?.dismiss()
downloadProgressDialog = AlertDialog.Builder(this) downloadProgressDialog = AlertDialog.Builder(this)

View File

@ -5,7 +5,7 @@ import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import me.magnum.melonds.common.Schedulers import me.magnum.melonds.common.Schedulers
import me.magnum.melonds.domain.model.AppUpdate import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.model.DownloadProgress import me.magnum.melonds.domain.model.DownloadProgress
import me.magnum.melonds.domain.repositories.UpdatesRepository import me.magnum.melonds.domain.repositories.UpdatesRepository
import me.magnum.melonds.domain.services.UpdateInstallManager import me.magnum.melonds.domain.services.UpdateInstallManager
@ -47,6 +47,9 @@ class UpdatesViewModel @Inject constructor(
.subscribeOn(schedulers.backgroundThreadScheduler) .subscribeOn(schedulers.backgroundThreadScheduler)
.subscribe { .subscribe {
updateDownloadProgressLiveData.postValue(it) updateDownloadProgressLiveData.postValue(it)
if (it is DownloadProgress.DownloadComplete) {
updatesRepository.notifyUpdateDownloaded(update)
}
} }
.addTo(disposables) .addTo(disposables)
} }

View File

@ -24,6 +24,7 @@ class GeneralPreferencesFragment : PreferenceFragmentCompat(), PreferenceFragmen
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_general, rootKey) setPreferencesFromResource(R.xml.pref_general, rootKey)
addPreferencesFromResource(R.xml.pref_general_updates)
rewindPreference = findPreference("enable_rewind")!! rewindPreference = findPreference("enable_rewind")!!
val sustainedPerformancePreference = findPreference<SwitchPreference>("enable_sustained_performance")!! val sustainedPerformancePreference = findPreference<SwitchPreference>("enable_sustained_performance")!!

View File

@ -49,7 +49,10 @@
<string name="action_refresh_rom_list">Refresh ROM list</string> <string name="action_refresh_rom_list">Refresh ROM list</string>
<string name="hint_search_roms">Search ROMs…</string> <string name="hint_search_roms">Search ROMs…</string>
<string name="update_available">Update available: %1$s</string> <string name="update_available">Update available: %1$s</string>
<string name="nightly_update_available">Nightly update available</string>
<string name="nightly_update_available_message">Do you want to download it now?</string>
<string name="skip_update">Skip</string> <string name="skip_update">Skip</string>
<string name="remind_later_update">Later</string>
<string name="downloading_update">Downloading update…</string> <string name="downloading_update">Downloading update…</string>
<string name="starting_download">Starting download…</string> <string name="starting_download">Starting download…</string>
<string name="download_progress_sizes">%1$.1fMB/%2$.1fMB</string> <string name="download_progress_sizes">%1$.1fMB/%2$.1fMB</string>
@ -148,6 +151,8 @@
<string name="rewind_memory_usage_above_recommended_limit">Memory usage above recommended limit</string> <string name="rewind_memory_usage_above_recommended_limit">Memory usage above recommended limit</string>
<string name="sustained_performance_mode">Sustained performance mode</string> <string name="sustained_performance_mode">Sustained performance mode</string>
<string name="sustained_performance_mode_summary">When enabled, peak performance will be lower but thermal throttling will be less pronounced.</string> <string name="sustained_performance_mode_summary">When enabled, peak performance will be lower but thermal throttling will be less pronounced.</string>
<string name="check_for_updates">Check for updates</string>
<string name="check_for_updates_summary">When enabled, the app will periodically check for available updates</string>
<string name="rom_icon_filtering">Icon filtering</string> <string name="rom_icon_filtering">Icon filtering</string>
<string name="max_rom_cache_size">Max. ROM cache size</string> <string name="max_rom_cache_size">Max. ROM cache size</string>
<string name="clear_extracted_rom_cache">Clear extracted ROM cache</string> <string name="clear_extracted_rom_cache">Clear extracted ROM cache</string>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen>
<!-- Empty on purpose since not all builds have update options -->
</PreferenceScreen>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">melonDS Nightly</string>
</resources>

View File

@ -1,7 +1,7 @@
package me.magnum.melonds.playstore package me.magnum.melonds.playstore
import io.reactivex.Maybe import io.reactivex.Maybe
import me.magnum.melonds.domain.model.AppUpdate import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.repositories.UpdatesRepository import me.magnum.melonds.domain.repositories.UpdatesRepository
class PlayStoreUpdatesRepository : UpdatesRepository { class PlayStoreUpdatesRepository : UpdatesRepository {
@ -12,4 +12,8 @@ class PlayStoreUpdatesRepository : UpdatesRepository {
override fun skipUpdate(update: AppUpdate) { override fun skipUpdate(update: AppUpdate) {
// Do nothing. Update checking not supported in the Play Store version // Do nothing. Update checking not supported in the Play Store version
} }
override fun notifyUpdateDownloaded(update: AppUpdate) {
// Do nothing
}
} }

View File

@ -1,8 +1,8 @@
package me.magnum.melonds.services package me.magnum.melonds.services
import io.reactivex.Observable import io.reactivex.Observable
import me.magnum.melonds.domain.model.AppUpdate
import me.magnum.melonds.domain.model.DownloadProgress import me.magnum.melonds.domain.model.DownloadProgress
import me.magnum.melonds.domain.model.appupdate.AppUpdate
import me.magnum.melonds.domain.services.UpdateInstallManager import me.magnum.melonds.domain.services.UpdateInstallManager
class PlayStoreUpdateInstallManager : UpdateInstallManager { class PlayStoreUpdateInstallManager : UpdateInstallManager {