From 13928b2ff9c8fd6e217e02b6d83de8ea8dc4b197 Mon Sep 17 00:00:00 2001 From: brahmkshatriya <69040506+brahmkshatriya@users.noreply.github.com> Date: Sun, 15 Dec 2024 13:51:26 +0530 Subject: [PATCH 1/2] Update & Documentation --- .github/workflow/build.yml | 66 ++++++++++++++++ README.md | 74 ++++++++++++++++++ app/build.gradle.kts | 2 + ext/build.gradle.kts | 13 ++++ .../echo/extension/ExtensionUnitTest.kt | 75 +++++++++---------- gradle.properties | 4 +- jitpack.yml | 5 ++ 7 files changed, 199 insertions(+), 40 deletions(-) create mode 100644 .github/workflow/build.yml create mode 100644 README.md create mode 100644 jitpack.yml diff --git a/.github/workflow/build.yml b/.github/workflow/build.yml new file mode 100644 index 0000000..dd87f64 --- /dev/null +++ b/.github/workflow/build.yml @@ -0,0 +1,66 @@ +name: nightly + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: 17 + cache: 'gradle' + + - name: Cook Env + run: | + name=$(grep '^extName=' gradle.properties | cut -d'=' -f2) + echo "NAME=$name Extension" >> $GITHUB_ENV + id=$(grep '^extId=' gradle.properties | cut -d'=' -f2) + echo "TAG=$id" >> $GITHUB_ENV + echo -e "## ${{ env.NAME }}\n${{ github.event.head_commit.message }}" > commit.txt + version=$( echo ${{ github.event.head_commit.id }} | cut -c1-7 ) + echo "VERSION=$version" >> $GITHUB_ENV + echo "APP_PATH=app/build/${{ env.TAG }}-$version.eapk" >> $GITHUB_ENV + echo "${{ secrets.KEYSTORE_B64 }}" | base64 -d > $GITHUB_WORKSPACE/signing-key.jks + chmod +x ./gradlew + + - name: Build with Gradle + run: | + ./gradlew assembleDebug \ + -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/signing-key.jks \ + -Pandroid.injected.signing.store.password=${{ secrets.PASSWORD }} \ + -Pandroid.injected.signing.key.alias=key0 \ + -Pandroid.injected.signing.key.password=${{ secrets.PASSWORD }} + + cp app/build/outputs/apk/debug/app-debug.apk ${{ env.APP_PATH }} + + - name: Upload APK + uses: actions/upload-artifact@v4 + with: + path: ${{ env.APP_PATH }} + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + make_latest: true + tag_name: v${{ env.VERSION }} + body_path: commit.txt + name: ${{ env.VERSION }} + files: ${{ env.APP_PATH }} + + - name: Delete Old Releases + uses: sgpublic/delete-release-action@master + with: + release-drop: true + release-keep-count: 3 + release-drop-tag: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..89ce9a1 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# Echo Extension Template + +This is a template for creating an Echo extension. It includes a basic structure for the extension, +so you do not have to start from scratch. + +## Getting Started + +### 1. you can clone this repository. +Clone this repository and name it as you want. + +### 2. Configure the [gradle.properties](gradle.properties) +The file will have the following properties: +- `libVersion` - The version of the Echo library, defaults to `SNAPSHOT`. +- `extType` - The type of the extension you want to create. It can be `music`, `tracker` + or `lyrics`. More information can be found + in [`Extension<*>`](https://github.com/brahmkshatriya/echo/blob/main/common/src/main/java/dev/brahmkshatriya/echo/common/Extension.kt#L33-L43) + java doc. +- `extId` - The id of the extension. (Do not use spaces or special characters) +- `extClass` - The class of the extension. This should be the class that you inherit client + interfaces to. For example in this template, it + is [`TestExtension`](ext/src/main/java/dev/brahmkshatriya/echo/extension/TestExtension.kt). +- `extIcon` - (Optional) The icon of the extension. Will be cropped into a circle. +- `extName` - The name of the extension. +- `extDescription` - The description of the extension. +- `extAuthor` - The author of the extension. +- `extAuthorUrl` - (Optional) The author's website. +- `extRepoUrl` - (Optional) The repository URL of the extension. +- `extUpdateUrl` - (Optional) The update URL of the extension. The following urls are supported: + - Github : https://api.github.com/repos/your_username/your_extension_repo/releases + +### 3. Implement the extension +Here's where the fun begins. Echo checks for `Client` interfaces that your extension implemented to know if your extension supports the feature or not. + +- What are `Client` interfaces? + - These are interfaces that include functions your extension need to implement (`override fun`). + - For example, if you want to create a lyrics extension, you need to implement the `LyricsClient` interface. +- What interfaces are available? + - By default, the [`TestExtension`](ext/src/main/java/dev/brahmkshatriya/echo/extension/TestExtension.kt) implements the `ExtensionClient` interface. + - Pro tip: Hover over the interface to see the documentation, Click on every one things that is clickable to dive deep into the rabbit hole. + - You can find all the available interfaces, for: + - Music Extension - [here](https://github.com/brahmkshatriya/echo/blob/main/common/src/main/java/dev/brahmkshatriya/echo/common/Extension.kt#L65-L117) + - Tracker Extension - [here](https://github.com/brahmkshatriya/echo/blob/main/common/src/main/java/dev/brahmkshatriya/echo/common/Extension.kt#L123-L137) + - Lyrics Extension - [here](https://github.com/brahmkshatriya/echo/blob/main/common/src/main/java/dev/brahmkshatriya/echo/common/Extension.kt#L143-L156) + +The best example of how to implement an extension should be the [Spotify Extension](https://github.com/brahmkshatriya/echo-spotify-extension/blob/main/ext/src/main/java/dev/brahmkshatriya/echo/extension/SpotifyExtension.kt). + +### 4. Making network requests +If your extension needs to make network requests, you can use `OkHttpClient` class provided directly by Echo. For example: +```kotlin +class TestExtension : ExtensionClient { + private val client = OkHttpClient() + + override suspend fun someNiceFunction() { + val request = Request.Builder().url("https://example.com").build() + val response = client.newCall(request).await() + println(response.body?.string()) + } +} +``` +If you are using `OkHttpClient`, use the custom `await()` function for suspending the network call. + +### 5. Testing the extension +There are two ways to test the extension: +- **Local testing**: You can test the extension locally by running the tests in the [`ExtensionUnitTest`](ext/src/test/java/dev/brahmkshatriya/echo/extension/ExtensionUnitTest.kt) class. +- **App testing**: You can test the extension in the Echo app by building & installing the `app` & then opening Echo app. + +### 6. Publishing the extension +This template includes a GitHub Actions workflow that will automatically build and publish the extension to GitHub releases when you make a new commit. You can find the workflow file [here](.github/workflow/build.yml). +You need to do the following steps to publish the extension: +- Enable `Read & write permissions` for workflows in the repository settings (Settings -> Actions -> General -> Workflow Permissions). +- Generate a keystore file : https://developer.android.com/studio/publish/app-signing#generate-key +- Add action secrets in the repository settings (Settings -> Secrets and variables -> Actions -> New repository secret): + - `KEYSTORE_B64` - The base64 encoded keystore file. [How to](https://stackoverflow.com/a/70396534) + - `KEYSTORE_PASSWORD` - The password of the keystore file. \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e92af99..a2ce780 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,6 +38,8 @@ tasks.register("uninstall") { } } +tasks.withType().configureEach { enabled = false } + android { namespace = "dev.brahmkshatriya.echo.extension" compileSdk = 35 diff --git a/ext/build.gradle.kts b/ext/build.gradle.kts index 3d6b4ea..31db3bd 100644 --- a/ext/build.gradle.kts +++ b/ext/build.gradle.kts @@ -5,6 +5,7 @@ plugins { id("java-library") id("org.jetbrains.kotlin.jvm") id("com.gradleup.shadow") version "8.3.0" + id("maven-publish") } java { @@ -46,6 +47,18 @@ val gitCount = execute("git", "rev-list", "--count", "HEAD").toInt() val verCode = gitCount val verName = gitHash +publishing { + publications { + create("mavenJava") { + groupId = "dev.brahmkshatriya.echo.extension" + artifactId = extId + version = verName + + from(components["java"]) + } + } +} + tasks { val shadowJar by getting(ShadowJar::class) { archiveBaseName.set(extId) diff --git a/ext/src/test/java/dev/brahmkshatriya/echo/extension/ExtensionUnitTest.kt b/ext/src/test/java/dev/brahmkshatriya/echo/extension/ExtensionUnitTest.kt index ca7c99f..82e5b1d 100644 --- a/ext/src/test/java/dev/brahmkshatriya/echo/extension/ExtensionUnitTest.kt +++ b/ext/src/test/java/dev/brahmkshatriya/echo/extension/ExtensionUnitTest.kt @@ -3,12 +3,14 @@ package dev.brahmkshatriya.echo.extension import dev.brahmkshatriya.echo.common.clients.AlbumClient import dev.brahmkshatriya.echo.common.clients.ExtensionClient import dev.brahmkshatriya.echo.common.clients.HomeFeedClient +import dev.brahmkshatriya.echo.common.clients.LoginClient import dev.brahmkshatriya.echo.common.clients.RadioClient -import dev.brahmkshatriya.echo.common.clients.SearchClient +import dev.brahmkshatriya.echo.common.clients.SearchFeedClient import dev.brahmkshatriya.echo.common.clients.TrackClient import dev.brahmkshatriya.echo.common.models.EchoMediaItem -import dev.brahmkshatriya.echo.common.models.MediaItemsContainer +import dev.brahmkshatriya.echo.common.models.Shelf import dev.brahmkshatriya.echo.common.models.Track +import dev.brahmkshatriya.echo.common.models.User import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -27,14 +29,19 @@ import kotlin.system.measureTimeMillis class ExtensionUnitTest { private val extension: ExtensionClient = TestExtension() private val searchQuery = "Skrillex" + private val user = User("","Test User") + // Test Setup private val mainThreadSurrogate = newSingleThreadContext("UI thread") - @Before fun setUp() { Dispatchers.setMain(mainThreadSurrogate) extension.setSettings(MockedSettings()) - runBlocking { extension.onExtensionSelected() } + runBlocking { + extension.onExtensionSelected() + if (extension is LoginClient) + extension.onSetLoginUser(user) + } } @After @@ -49,6 +56,7 @@ class ExtensionUnitTest { println("\n") } + // Actual Tests @Test fun testHomeFeed() = testIn("Testing Home Feed") { if (extension !is HomeFeedClient) error("HomeFeedClient is not implemented") @@ -69,9 +77,9 @@ class ExtensionUnitTest { } @Test - fun testNullQuickSearch() = testIn("Testing Null Quick Search") { - if (extension !is SearchClient) error("SearchClient is not implemented") - val search = extension.quickSearch(null) + fun testEmptyQuickSearch() = testIn("Testing Empty Quick Search") { + if (extension !is SearchFeedClient) error("SearchClient is not implemented") + val search = extension.quickSearch("") search.forEach { println(it) } @@ -79,7 +87,7 @@ class ExtensionUnitTest { @Test fun testQuickSearch() = testIn("Testing Quick Search") { - if (extension !is SearchClient) error("SearchClient is not implemented") + if (extension !is SearchFeedClient) error("SearchClient is not implemented") val search = extension.quickSearch(searchQuery) search.forEach { println(it) @@ -87,9 +95,10 @@ class ExtensionUnitTest { } @Test - fun testNullSearch() = testIn("Testing Null Search") { - if (extension !is SearchClient) error("SearchClient is not implemented") - val search = extension.searchFeed(null, null).loadFirst() + fun testEmptySearch() = testIn("Testing Empty Search") { + if (extension !is SearchFeedClient) error("SearchFeedClient is not implemented") + val tab = extension.searchTabs("").firstOrNull() + val search = extension.searchFeed("", tab).loadFirst() search.forEach { println(it) } @@ -97,10 +106,10 @@ class ExtensionUnitTest { @Test fun testSearch() = testIn("Testing Search") { - if (extension !is SearchClient) error("SearchClient is not implemented") + if (extension !is SearchFeedClient) error("SearchFeedClient is not implemented") println("Tabs") extension.searchTabs(searchQuery).forEach { - println(it.name) + println(it.title) } println("Search Results") val search = extension.searchFeed(searchQuery, null).loadFirst() @@ -109,29 +118,19 @@ class ExtensionUnitTest { } } - @Test - fun testSearchWithTab() = testIn("Testing Search with Tab") { - if (extension !is SearchClient) error("SearchClient is not implemented") - val tab = extension.searchTabs(searchQuery).firstOrNull() - val search = extension.searchFeed(searchQuery, tab).loadFirst() - search.forEach { - println(it) - } - } - - private suspend fun searchTrack(q: String? = null): Track { - if (extension !is SearchClient) error("SearchClient is not implemented") + if (extension !is SearchFeedClient) error("SearchFeedClient is not implemented") val query = q ?: searchQuery println("Searching : $query") - val items = extension.searchFeed(query, null).loadFirst() + val tab = extension.searchTabs(query).firstOrNull() + val items = extension.searchFeed(query, tab).loadFirst() val track = items.firstNotNullOfOrNull { - val item = when (it) { - is MediaItemsContainer.Item -> it.media - is MediaItemsContainer.Category -> it.list.firstOrNull() + when (it) { + is Shelf.Item -> (it.media as? EchoMediaItem.TrackItem)?.track + is Shelf.Lists.Tracks -> it.list.firstOrNull() + is Shelf.Lists.Items -> (it.list.firstOrNull() as? EchoMediaItem.TrackItem)?.track else -> null } - (item as? EchoMediaItem.TrackItem)?.track } return track ?: error("Track not found, try a different search query") } @@ -142,7 +141,7 @@ class ExtensionUnitTest { val search = searchTrack() measureTimeMillis { val track = extension.loadTrack(search) - println(track.liked) + println(track) }.also { println("time : $it") } } @@ -152,9 +151,9 @@ class ExtensionUnitTest { val search = searchTrack() measureTimeMillis { val track = extension.loadTrack(search) - val streamable = track.audioStreamables.firstOrNull() + val streamable = track.servers.firstOrNull() ?: error("Track is not streamable") - val stream = extension.getStreamableAudio(streamable) + val stream = extension.loadStreamableMedia(streamable, false) println(stream) }.also { println("time : $it") } } @@ -164,7 +163,7 @@ class ExtensionUnitTest { if (extension !is TrackClient) error("TrackClient is not implemented") if (extension !is RadioClient) error("RadioClient is not implemented") val track = extension.loadTrack(searchTrack()) - val radio = extension.radio(track) + val radio = extension.radio(track, null) val radioTracks = extension.loadTracks(radio).loadFirst() radioTracks.forEach { println(it) @@ -172,10 +171,10 @@ class ExtensionUnitTest { } @Test - fun testTrackMediaItems() = testIn("Testing Track Media Items") { + fun testTrackShelves() = testIn("Testing Track Shelves") { if (extension !is TrackClient) error("TrackClient is not implemented") - val track = extension.loadTrack(Track("iDkSRTBDxJY", "")) - val mediaItems = extension.getMediaItems(track).loadFirst() + val track = extension.loadTrack(searchTrack()) + val mediaItems = extension.getShelves(track).loadFirst() mediaItems.forEach { println(it) } @@ -188,7 +187,7 @@ class ExtensionUnitTest { if (extension !is AlbumClient) error("AlbumClient is not implemented") val album = extension.loadAlbum(small) println(album) - val mediaItems = extension.getMediaItems(album).loadFirst() + val mediaItems = extension.getShelves(album).loadFirst() mediaItems.forEach { println(it) } diff --git a/gradle.properties b/gradle.properties index bda5a62..22d4612 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ android.useAndroidX=true kotlin.code.style=official android.nonTransitiveRClass=true -libVersion=7727467b74 +libVersion=333c7fb3e6 # Can be music, tracker or lyrics extType=music @@ -12,7 +12,7 @@ extClass=TestExtension extIconUrl= extName=Test -extDescription=This is the test extension you creatd. +extDescription=This is the test extension you created. extAuthor=Test extAuthorUrl= diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..f29a667 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,5 @@ +jdk: + - openjdk17 +before_install: + - sdk install java 17.0.1-open + - sdk use java 17.0.1-open \ No newline at end of file From ef3e4f8dee1c45d86883903ee598350444293ebe Mon Sep 17 00:00:00 2001 From: brahmkshatriya <69040506+brahmkshatriya@users.noreply.github.com> Date: Sun, 15 Dec 2024 15:07:50 +0530 Subject: [PATCH 2/2] Fix jitpack things --- README.md | 2 +- app/build.gradle.kts | 2 -- gradle.properties | 2 +- jitpack.yml | 4 +++- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 89ce9a1..8eec293 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Clone this repository and name it as you want. ### 2. Configure the [gradle.properties](gradle.properties) The file will have the following properties: -- `libVersion` - The version of the Echo library, defaults to `SNAPSHOT`. +- `libVersion` - The version of the Echo library, defaults to `main-SNAPSHOT`. - `extType` - The type of the extension you want to create. It can be `music`, `tracker` or `lyrics`. More information can be found in [`Extension<*>`](https://github.com/brahmkshatriya/echo/blob/main/common/src/main/java/dev/brahmkshatriya/echo/common/Extension.kt#L33-L43) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a2ce780..e92af99 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,8 +38,6 @@ tasks.register("uninstall") { } } -tasks.withType().configureEach { enabled = false } - android { namespace = "dev.brahmkshatriya.echo.extension" compileSdk = 35 diff --git a/gradle.properties b/gradle.properties index 22d4612..7d6ab93 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ android.useAndroidX=true kotlin.code.style=official android.nonTransitiveRClass=true -libVersion=333c7fb3e6 +libVersion=main-SNAPSHOT # Can be music, tracker or lyrics extType=music diff --git a/jitpack.yml b/jitpack.yml index f29a667..5292af1 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -2,4 +2,6 @@ jdk: - openjdk17 before_install: - sdk install java 17.0.1-open - - sdk use java 17.0.1-open \ No newline at end of file + - sdk use java 17.0.1-open +install: + - ./gradlew clean ext:assemble publishToMavenLocal \ No newline at end of file