From 8e073284d2a47e040c44e5ff41497f90ffd00952 Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Thu, 12 Dec 2024 21:44:17 -0300 Subject: [PATCH 1/3] First implementation of multiple beatmap mirror support --- .../osu/beatmaplisting/BeatmapListing.kt | 32 +++--- ...eatmapModel.kt => BeatmapListingModels.kt} | 23 ---- .../osu/beatmaplisting/BeatmapMirror.kt | 104 +++++++----------- .../osu/beatmaplisting/BeatmapMirrorAction.kt | 24 ++++ .../osu/beatmaplisting/BeatmapMirrorModels.kt | 38 +++++++ .../osu/beatmaplisting/mirrors/OsuDirect.kt | 80 ++++++++++++++ 6 files changed, 197 insertions(+), 104 deletions(-) rename src/com/reco1l/osu/beatmaplisting/{BeatmapModel.kt => BeatmapListingModels.kt} (97%) create mode 100644 src/com/reco1l/osu/beatmaplisting/BeatmapMirrorAction.kt create mode 100644 src/com/reco1l/osu/beatmaplisting/BeatmapMirrorModels.kt create mode 100644 src/com/reco1l/osu/beatmaplisting/mirrors/OsuDirect.kt diff --git a/src/com/reco1l/osu/beatmaplisting/BeatmapListing.kt b/src/com/reco1l/osu/beatmaplisting/BeatmapListing.kt index 6dc6ff6d3..21cb42aaa 100644 --- a/src/com/reco1l/osu/beatmaplisting/BeatmapListing.kt +++ b/src/com/reco1l/osu/beatmaplisting/BeatmapListing.kt @@ -195,21 +195,17 @@ class BeatmapListing : BaseFragment(), ensureActive() - JsonArrayRequest(mirror.search.endpoint).use { request -> - - request.buildUrl { - - addQueryParameter("mode", "0") - addQueryParameter("query", searchBox.text.toString()) - addQueryParameter("offset", offset.toString()) - } + JsonArrayRequest( + mirror.search.request( + query = searchBox.text.toString(), + offset = offset + ) + ).use { request -> request.buildRequest { header("User-Agent", "Chrome/Android") } - ensureActive() - val beatmapSets = mirror.search.mapResponse(request.execute().json) - + val beatmapSets = mirror.search.response(request.execute().json) ensureActive() adapter.data.addAll(beatmapSets) @@ -393,8 +389,10 @@ class BeatmapSetDetails(val beatmapSet: BeatmapSetModel, val holder: BeatmapSetV } downloadButton.setOnClickListener { - val url = BeatmapListing.mirror.downloadEndpoint(beatmapSet.id) - BeatmapDownloader.download(url, "${beatmapSet.id} ${beatmapSet.artist} - ${beatmapSet.title}") + BeatmapDownloader.download( + url = BeatmapListing.mirror.download.request(beatmapSet.id).toString(), + suggestedFilename = "${beatmapSet.id} ${beatmapSet.artist} - ${beatmapSet.title}" + ) } cover.setImageDrawable(holder.cover.drawable) @@ -598,8 +596,10 @@ class BeatmapSetViewHolder(itemView: View, private val mediaScope: CoroutineScop } downloadButton.setOnClickListener { - val url = BeatmapListing.mirror.downloadEndpoint(beatmapSet.id) - BeatmapDownloader.download(url, "${beatmapSet.id} ${beatmapSet.artist} - ${beatmapSet.title}") + BeatmapDownloader.download( + url = BeatmapListing.mirror.download.request(beatmapSet.id).toString(), + suggestedFilename = "${beatmapSet.id} ${beatmapSet.artist} - ${beatmapSet.title}" + ) } @@ -621,7 +621,7 @@ class BeatmapSetViewHolder(itemView: View, private val mediaScope: CoroutineScop previewJob = mediaScope.launch { try { - previewStream = URLBassStream(BeatmapListing.mirror.previewEndpoint(beatmapSet.beatmaps[0].id)) { + previewStream = URLBassStream(BeatmapListing.mirror.preview.request(beatmapSet.beatmaps[0].id).toString()) { stopPreview(true) if (BeatmapListing.isPlayingMusic) { diff --git a/src/com/reco1l/osu/beatmaplisting/BeatmapModel.kt b/src/com/reco1l/osu/beatmaplisting/BeatmapListingModels.kt similarity index 97% rename from src/com/reco1l/osu/beatmaplisting/BeatmapModel.kt rename to src/com/reco1l/osu/beatmaplisting/BeatmapListingModels.kt index 67a4c9ac8..0ebe77a03 100644 --- a/src/com/reco1l/osu/beatmaplisting/BeatmapModel.kt +++ b/src/com/reco1l/osu/beatmaplisting/BeatmapListingModels.kt @@ -6,54 +6,31 @@ import ru.nsu.ccfit.zuev.osu.RankedStatus * Beatmap set response model for beatmaps mirrors. */ data class BeatmapSetModel( - val id: Long, - val title: String, - val titleUnicode: String, - val artist: String, - val artistUnicode: String, - val status: RankedStatus, - val creator: String, - val thumbnail: String?, - val beatmaps: List - ) /** * Beatmap response model for beatmaps mirrors. */ data class BeatmapModel( - val id: Long, - val version: String, - val starRating: Double, - val ar: Double, - val cs: Double, - val hp: Double, - val od: Double, - val bpm: Double, - val lengthSec: Long, - val circleCount: Int, - val sliderCount:Int, - val spinnerCount: Int - ) \ No newline at end of file diff --git a/src/com/reco1l/osu/beatmaplisting/BeatmapMirror.kt b/src/com/reco1l/osu/beatmaplisting/BeatmapMirror.kt index ee199e221..bee618b46 100644 --- a/src/com/reco1l/osu/beatmaplisting/BeatmapMirror.kt +++ b/src/com/reco1l/osu/beatmaplisting/BeatmapMirror.kt @@ -1,42 +1,53 @@ package com.reco1l.osu.beatmaplisting -import org.json.JSONArray -import ru.nsu.ccfit.zuev.osu.RankedStatus - +import androidx.annotation.DrawableRes +import com.reco1l.osu.beatmaplisting.mirrors.OsuDirectDownloadRequestModel +import com.reco1l.osu.beatmaplisting.mirrors.OsuDirectPreviewRequestModel +import com.reco1l.osu.beatmaplisting.mirrors.OsuDirectSearchRequestModel +import com.reco1l.osu.beatmaplisting.mirrors.OsuDirectSearchResponseModel +import ru.nsu.ccfit.zuev.osuplus.R /** - * Defines an action to be performed on a mirror API. + * Defines a beatmap mirror API and its actions. */ -data class MirrorAction( +enum class BeatmapMirror( /** - * The action API endpoint. + * The home URL of the beatmap mirror where the user will be redirected to when + * clicking on the logo. */ - // TODO replace with a request creation function, some APIs have different query arguments. - val endpoint: String, + val homeUrl: String, /** - * A function to map the response into a model. + * The description of the beatmap mirror. */ - val mapResponse: (R) -> M + val description: String, -) + /** + * The resource ID of the logo image to be displayed in the UI. + */ + @DrawableRes + val logoResource: Int, -/** - * Defines a beatmap mirror API and its actions. - */ -enum class BeatmapMirror( + + // Actions / Endpoints /** * The search query action. */ - val search: MirrorAction>, + val search: BeatmapMirrorActionWithResponse, - val downloadEndpoint: (Long) -> String, + /** + * The download action. + */ + val download: BeatmapMirrorAction, - val previewEndpoint: (Long) -> String, + /** + * The music preview action. + */ + val preview: BeatmapMirrorAction, - ) { +) { /** * osu.direct beatmap mirror. @@ -44,54 +55,17 @@ enum class BeatmapMirror( * [See documentation](https://osu.direct/api/docs) */ OSU_DIRECT( - search = MirrorAction( - endpoint = "https://osu.direct/api/v2/search", - mapResponse = { array -> - - MutableList(array.length()) { index -> - - val json = array.getJSONObject(index) - - BeatmapSetModel( - id = json.getLong("id"), - title = json.getString("title"), - titleUnicode = json.getString("title_unicode"), - artist = json.getString("artist"), - artistUnicode = json.getString("artist_unicode"), - status = RankedStatus.valueOf(json.getInt("ranked")), - creator = json.getString("creator"), - thumbnail = json.optJSONObject("covers")?.optString("card"), - beatmaps = json.getJSONArray("beatmaps").let { - - MutableList(it.length()) { i -> - - val obj = it.getJSONObject(i) - - BeatmapModel( - id = obj.getLong("id"), - version = obj.getString("version"), - starRating = obj.getDouble("difficulty_rating"), - ar = obj.getDouble("ar"), - cs = obj.getDouble("cs"), - hp = obj.getDouble("drain"), - od = obj.getDouble("accuracy"), - bpm = obj.getDouble("bpm"), - lengthSec = obj.getLong("hit_length"), - circleCount = obj.getInt("count_circles"), - sliderCount = obj.getInt("count_sliders"), - spinnerCount = obj.getInt("count_spinners") - ) - - }.sortedBy(BeatmapModel::starRating) - } - ) - } + homeUrl = "https://osu.direct", + description = "osu.direct", + logoResource = R.drawable.powered_by_osudirect, - } + search = BeatmapMirrorActionWithResponse( + request = OsuDirectSearchRequestModel(), + response = OsuDirectSearchResponseModel(), ), - downloadEndpoint = { "https://osu.direct/api/d/$it" }, - previewEndpoint = { "https://osu.direct/api/media/preview/$it" }, - ); + download = BeatmapMirrorAction(OsuDirectDownloadRequestModel()), + preview = BeatmapMirrorAction(OsuDirectPreviewRequestModel()), + ) } diff --git a/src/com/reco1l/osu/beatmaplisting/BeatmapMirrorAction.kt b/src/com/reco1l/osu/beatmaplisting/BeatmapMirrorAction.kt new file mode 100644 index 000000000..f97a90b33 --- /dev/null +++ b/src/com/reco1l/osu/beatmaplisting/BeatmapMirrorAction.kt @@ -0,0 +1,24 @@ +package com.reco1l.osu.beatmaplisting + +/** + * Defines an action to be performed on a mirror API. + */ +open class BeatmapMirrorAction( + + /** + * The action API endpoint. + */ + val request: RequestModel + +) + +class BeatmapMirrorActionWithResponse( + + request: RequestModel, + + /** + * The action response mapping. + */ + val response: ResponseModel + +) : BeatmapMirrorAction(request) diff --git a/src/com/reco1l/osu/beatmaplisting/BeatmapMirrorModels.kt b/src/com/reco1l/osu/beatmaplisting/BeatmapMirrorModels.kt new file mode 100644 index 000000000..9101f66ea --- /dev/null +++ b/src/com/reco1l/osu/beatmaplisting/BeatmapMirrorModels.kt @@ -0,0 +1,38 @@ +package com.reco1l.osu.beatmaplisting + +import okhttp3.HttpUrl + + +// Request + +fun interface BeatmapMirrorSearchRequestModel { + /** + * @param query The search query. + * @param offset The search result offset. + */ + operator fun invoke(query: String, offset: Int): HttpUrl +} + +fun interface BeatmapMirrorDownloadRequestModel { + /** + * @param beatmapSetId The beatmap set ID. + */ + operator fun invoke(beatmapSetId: Long): HttpUrl +} + +fun interface BeatmapMirrorPreviewRequestModel { + /** + * @param beatmapSetId The beatmap set ID. + */ + operator fun invoke(beatmapSetId: Long): HttpUrl +} + + +// Response + +fun interface BeatmapMirrorSearchResponseModel { + /** + * @return The list of beatmap sets. + */ + operator fun invoke(response: Any): MutableList +} \ No newline at end of file diff --git a/src/com/reco1l/osu/beatmaplisting/mirrors/OsuDirect.kt b/src/com/reco1l/osu/beatmaplisting/mirrors/OsuDirect.kt new file mode 100644 index 000000000..ab5a55b25 --- /dev/null +++ b/src/com/reco1l/osu/beatmaplisting/mirrors/OsuDirect.kt @@ -0,0 +1,80 @@ +package com.reco1l.osu.beatmaplisting.mirrors + +import com.reco1l.osu.beatmaplisting.BeatmapMirrorDownloadRequestModel +import com.reco1l.osu.beatmaplisting.BeatmapMirrorPreviewRequestModel +import com.reco1l.osu.beatmaplisting.BeatmapMirrorSearchRequestModel +import com.reco1l.osu.beatmaplisting.BeatmapMirrorSearchResponseModel +import com.reco1l.osu.beatmaplisting.BeatmapModel +import com.reco1l.osu.beatmaplisting.BeatmapSetModel +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import org.json.JSONArray +import ru.nsu.ccfit.zuev.osu.RankedStatus + + +class OsuDirectSearchRequestModel : BeatmapMirrorSearchRequestModel { + override fun invoke(query: String, offset: Int): HttpUrl { + return "https://osu.direct/api/v2/search".toHttpUrl() + .newBuilder() + .addQueryParameter("mode", "0") + .addQueryParameter("query", query) + .addQueryParameter("offset", offset.toString()) + .build() + } +} + +class OsuDirectSearchResponseModel : BeatmapMirrorSearchResponseModel { + override fun invoke(response: Any): MutableList { + response as JSONArray + + return MutableList(response.length()) { index -> + val json = response.getJSONObject(index) + + BeatmapSetModel( + id = json.getLong("id"), + title = json.getString("title"), + titleUnicode = json.getString("title_unicode"), + artist = json.getString("artist"), + artistUnicode = json.getString("artist_unicode"), + status = RankedStatus.valueOf(json.getInt("ranked")), + creator = json.getString("creator"), + thumbnail = json.optJSONObject("covers")?.optString("card"), + beatmaps = json.getJSONArray("beatmaps").let { + + MutableList(it.length()) { i -> + + val obj = it.getJSONObject(i) + + BeatmapModel( + id = obj.getLong("id"), + version = obj.getString("version"), + starRating = obj.getDouble("difficulty_rating"), + ar = obj.getDouble("ar"), + cs = obj.getDouble("cs"), + hp = obj.getDouble("drain"), + od = obj.getDouble("accuracy"), + bpm = obj.getDouble("bpm"), + lengthSec = obj.getLong("hit_length"), + circleCount = obj.getInt("count_circles"), + sliderCount = obj.getInt("count_sliders"), + spinnerCount = obj.getInt("count_spinners") + ) + + }.sortedBy(BeatmapModel::starRating) + } + ) + } + } +} + +class OsuDirectDownloadRequestModel : BeatmapMirrorDownloadRequestModel { + override fun invoke(beatmapSetId: Long): HttpUrl { + return "https://osu.direct/api/d/$beatmapSetId".toHttpUrl() + } +} + +class OsuDirectPreviewRequestModel : BeatmapMirrorPreviewRequestModel { + override fun invoke(beatmapSetId: Long): HttpUrl { + return "https://osu.direct/api/media/preview/$beatmapSetId".toHttpUrl() + } +} \ No newline at end of file From 465b36171ee6e0d92fe696bacc3b2cd7c94f891e Mon Sep 17 00:00:00 2001 From: Geronimo Ferruccio Date: Thu, 12 Dec 2024 23:00:14 -0300 Subject: [PATCH 2/3] Implement UI features --- res/drawable/arrow_drop_down_24px.xml | 10 ++++++ res/drawable/osudirect.png | Bin 0 -> 502 bytes res/drawable/powered_by_osudirect.png | Bin 7249 -> 0 bytes res/layout/beatmap_downloader_fragment.xml | 30 ++++++++++------ .../osu/beatmaplisting/BeatmapListing.kt | 33 +++++++++++++----- .../osu/beatmaplisting/BeatmapMirror.kt | 2 +- src/com/reco1l/osu/ui/ListDialog.kt | 19 +++++++--- src/com/reco1l/osu/ui/entity/BeatmapButton.kt | 2 +- 8 files changed, 71 insertions(+), 25 deletions(-) create mode 100644 res/drawable/arrow_drop_down_24px.xml create mode 100644 res/drawable/osudirect.png delete mode 100644 res/drawable/powered_by_osudirect.png diff --git a/res/drawable/arrow_drop_down_24px.xml b/res/drawable/arrow_drop_down_24px.xml new file mode 100644 index 000000000..ea75299b3 --- /dev/null +++ b/res/drawable/arrow_drop_down_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/res/drawable/osudirect.png b/res/drawable/osudirect.png new file mode 100644 index 0000000000000000000000000000000000000000..3f6a60b42923659fbfbf227c2b105f46d7f4493f GIT binary patch literal 502 zcmVPx$mPtfGR7gv;Re^28KnR3MnoQDqk~WhhnWXL{btg$Sp}rS581sHxO)W)H@(%~x z9cKFHhimyoR<(E zZj1n6xgIjDb)o)%iq@WKO}pynMsvh~a5%TPaPN}|rnc-#dwT$sa7aF1cA*RW#7;_g zPzG#kB`RuRF6TllJap0oF|%d{eC8`NT^+5L{)@h)T6BEHZEFFm`=^z4%lR)RIS_X3 sg#Up6000hUSV?A0O#mtY000O800000007cclK=n!07*qoM6N<$f>|KaOaK4? literal 0 HcmV?d00001 diff --git a/res/drawable/powered_by_osudirect.png b/res/drawable/powered_by_osudirect.png deleted file mode 100644 index e3d8430015c5c84210fc6390dd8b9454e9efafa7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7249 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 - + android:layout_toLeftOf="@id/logo" + android:drawableLeft="@drawable/osudirect" + android:drawablePadding="8dp" + android:paddingVertical="16dp" + android:paddingLeft="16dp" + android:paddingRight="2dp" + android:text="osu.direct" + android:textColor="#FFF" /> + Option( + text = buildSpannedString { + append(mirror.description) + appendLine() + color(0xBFFFFFFF.toInt()) { append(mirror.homeUrl) } + }, + value = mirror.ordinal, + icon = requireContext().getDrawable(mirror.logoResource) + ) + }) + .setSelected(mirror.ordinal) + .setOnSelectListener { + Config.setInt("beatmapMirror", it as Int) + mirror = BeatmapMirror.entries[Config.getInt("beatmapMirror", 0)] + search(false) + } + .setTitle("Select a beatmap mirror") + .show() } findViewById(R.id.close)!!.setOnClickListener { @@ -282,7 +297,7 @@ class BeatmapListing : BaseFragment(), /** * The current selected beatmap mirror. */ - var mirror = BeatmapMirror.OSU_DIRECT + var mirror = BeatmapMirror.entries[Config.getInt("beatmapMirror", 0)] /** * Whether is a beatmap preview music playing or not. diff --git a/src/com/reco1l/osu/beatmaplisting/BeatmapMirror.kt b/src/com/reco1l/osu/beatmaplisting/BeatmapMirror.kt index bee618b46..3485e3b65 100644 --- a/src/com/reco1l/osu/beatmaplisting/BeatmapMirror.kt +++ b/src/com/reco1l/osu/beatmaplisting/BeatmapMirror.kt @@ -57,7 +57,7 @@ enum class BeatmapMirror( OSU_DIRECT( homeUrl = "https://osu.direct", description = "osu.direct", - logoResource = R.drawable.powered_by_osudirect, + logoResource = R.drawable.osudirect, search = BeatmapMirrorActionWithResponse( request = OsuDirectSearchRequestModel(), diff --git a/src/com/reco1l/osu/ui/ListDialog.kt b/src/com/reco1l/osu/ui/ListDialog.kt index 40547753e..75ffe9cd2 100644 --- a/src/com/reco1l/osu/ui/ListDialog.kt +++ b/src/com/reco1l/osu/ui/ListDialog.kt @@ -1,14 +1,17 @@ package com.reco1l.osu.ui import android.graphics.Color +import android.graphics.drawable.Drawable import android.view.LayoutInflater import android.view.ViewGroup import android.widget.TextView +import androidx.annotation.DrawableRes import androidx.core.view.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.reco1l.toolkt.android.cornerRadius import com.reco1l.toolkt.android.dp +import com.reco1l.toolkt.android.drawableLeft import com.reco1l.toolkt.android.drawableRight import ru.nsu.ccfit.zuev.osuplus.R @@ -21,12 +24,17 @@ data class Option( /** * The text to be displayed in the option. */ - val text: String, + val text: CharSequence, /** * The value to be returned when the option is selected. */ - val value: Any + val value: Any, + + /** + * The icon to be displayed in the option. + */ + val icon: Drawable? = null ) @@ -71,8 +79,8 @@ open class SelectDialog : MessageDialog() { } - fun setOptions(options: MutableList