feat: enhance Android build configuration with stable/nightly flavors and update launcher icons
38
.github/workflows/spotube-release-binary.yml
vendored
@ -141,11 +141,16 @@ jobs:
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
- name: Assemble Release
|
- name: Assemble Release
|
||||||
run: ./gradlew :composeApp:assembleRelease
|
run: |
|
||||||
|
if [ "${{ inputs.release_type }}" = "nightly" ]; then
|
||||||
|
./gradlew :composeApp:assembleNightlyRelease -Pnightly=true -PbuildNumber=${{ github.run_number }}
|
||||||
|
else
|
||||||
|
./gradlew :composeApp:assembleStableRelease
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Rename APK
|
- name: Rename APK
|
||||||
run: |
|
run: |
|
||||||
APK_FILE=$(find composeApp/build/outputs/apk/release -name "*.apk" -type f | head -1)
|
APK_FILE=$(find composeApp/build/outputs/apk -path "*elease*" -name "*.apk" -type f | head -1)
|
||||||
cp "$APK_FILE" Spotube-android-all-arch.apk
|
cp "$APK_FILE" Spotube-android-all-arch.apk
|
||||||
|
|
||||||
- name: Upload APK
|
- name: Upload APK
|
||||||
@ -194,7 +199,7 @@ jobs:
|
|||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Package Linux
|
- name: Package Linux
|
||||||
run: ./gradlew :composeApp:packageReleaseDeb :composeApp:packageReleaseRpm
|
run: ./gradlew :composeApp:packageReleaseDeb :composeApp:packageReleaseRpm -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }}
|
||||||
|
|
||||||
- name: Rename Linux artifacts
|
- name: Rename Linux artifacts
|
||||||
run: |
|
run: |
|
||||||
@ -255,7 +260,7 @@ jobs:
|
|||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Package Linux
|
- name: Package Linux
|
||||||
run: ./gradlew :composeApp:packageReleaseDeb :composeApp:packageReleaseRpm
|
run: ./gradlew :composeApp:packageReleaseDeb :composeApp:packageReleaseRpm -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }}
|
||||||
|
|
||||||
- name: Rename Linux artifacts
|
- name: Rename Linux artifacts
|
||||||
run: |
|
run: |
|
||||||
@ -308,7 +313,7 @@ jobs:
|
|||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
- name: Package EXE
|
- name: Package EXE
|
||||||
run: ./gradlew :composeApp:packageReleaseExe
|
run: ./gradlew :composeApp:packageReleaseExe -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }}
|
||||||
|
|
||||||
- name: Rename Windows artifacts
|
- name: Rename Windows artifacts
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@ -357,7 +362,7 @@ jobs:
|
|||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Package DMG
|
- name: Package DMG
|
||||||
run: ./gradlew :composeApp:packageReleaseDmg
|
run: ./gradlew :composeApp:packageReleaseDmg -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }}
|
||||||
|
|
||||||
- name: Rename macOS artifact
|
- name: Rename macOS artifact
|
||||||
run: |
|
run: |
|
||||||
@ -389,18 +394,35 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# The build resolves its plugins from mavenLocal, so Gradle needs the same setup as the build jobs
|
||||||
|
- name: Download maven local
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: maven-local
|
||||||
|
path: ~/.m2/repository/
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: 21
|
||||||
|
|
||||||
|
- name: Set up Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
- name: Set release variables
|
- name: Set release variables
|
||||||
id: vars
|
id: vars
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ inputs.release_type }}" = "stable" ]; then
|
if [ "${{ inputs.release_type }}" = "stable" ]; then
|
||||||
VERSION=$(grep 'versionName' composeApp/build.gradle.kts | head -1 | sed 's/.*"\(.*\)".*/\1/')
|
chmod +x gradlew
|
||||||
|
VERSION=$(./gradlew -q :composeApp:printVersion)
|
||||||
TAG="v${VERSION}"
|
TAG="v${VERSION}"
|
||||||
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
|
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
|
||||||
echo "is_draft=true" >> "$GITHUB_OUTPUT"
|
echo "is_draft=true" >> "$GITHUB_OUTPUT"
|
||||||
echo "release_name=Spotube ${TAG}" >> "$GITHUB_OUTPUT"
|
echo "release_name=Spotube ${TAG}" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
TAG="nightly"
|
TAG="nightly"
|
||||||
VERSION="$(date +%Y-%m-%d)"
|
VERSION="$(date +%Y-%m-%d)-${{ github.run_number }}"
|
||||||
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
|
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
|
||||||
echo "is_draft=false" >> "$GITHUB_OUTPUT"
|
echo "is_draft=false" >> "$GITHUB_OUTPUT"
|
||||||
echo "release_name=Nightly ${VERSION}" >> "$GITHUB_OUTPUT"
|
echo "release_name=Nightly ${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
## High-value commands
|
## High-value commands
|
||||||
- Use Gradle wrapper (`./gradlew` or `./gradlew.bat`) only.
|
- Use Gradle wrapper (`./gradlew` or `./gradlew.bat`) only.
|
||||||
- Run desktop: `:composeApp:run`
|
- Run desktop: `:composeApp:run`
|
||||||
- Build Android debug: `:composeApp:assembleDebug`
|
- Build Android debug: `:composeApp:assembleStableDebug` (or `assembleNightlyDebug`). Android has `stable`/`nightly` product flavors (nightly = beta channel: `.nightly` applicationId suffix, own icons/name in `composeApp/src/nightly/res`). Desktop icons come from `composeApp/icons/{stable,nightly}`, chosen with `-Pnightly=true`.
|
||||||
- Module checks: `:composeApp:check`, `:plugin_interfaces:check`, `:js_plugin_example:check`
|
- Module checks: `:composeApp:check`, `:plugin_interfaces:check`, `:js_plugin_example:check`
|
||||||
- Focused tests: `:composeApp:jvmTest`, `:composeApp:iosSimulatorArm64Test`, `:plugin_interfaces:jvmTest`, `:plugin_interfaces:jsTest`, `:js_plugin_example:jsTest`
|
- Focused tests: `:composeApp:jvmTest`, `:composeApp:iosSimulatorArm64Test`, `:plugin_interfaces:jvmTest`, `:plugin_interfaces:jsTest`, `:js_plugin_example:jsTest`
|
||||||
- No lint/typecheck/formatter tasks are configured; `:composeApp:check` is the only aggregated check.
|
- No lint/typecheck/formatter tasks are configured; `:composeApp:check` is the only aggregated check.
|
||||||
@ -70,6 +70,6 @@
|
|||||||
- **Adaptive Components:** Use `AdaptiveDropdownBottomSheet` or `AdaptiveDialogBottomSheet` inside `commonMain` for adaptive layouts[cite: 2]. Do NOT create platform-specific `expect/actual` variants unless platform-native APIs (like windowing or native scrollbars) are explicitly required[cite: 2].
|
- **Adaptive Components:** Use `AdaptiveDropdownBottomSheet` or `AdaptiveDialogBottomSheet` inside `commonMain` for adaptive layouts[cite: 2]. Do NOT create platform-specific `expect/actual` variants unless platform-native APIs (like windowing or native scrollbars) are explicitly required[cite: 2].
|
||||||
|
|
||||||
### 3. Verification & Diagnostic Loops
|
### 3. Verification & Diagnostic Loops
|
||||||
- **Post-Edit Verification:** After editing Kotlin code, validate compilation by running `./gradlew :composeApp:check` or `./gradlew :composeApp:assembleDebug` via MCP[cite: 2].
|
- **Post-Edit Verification:** After editing Kotlin code, validate compilation by running `./gradlew :composeApp:check` or `./gradlew :composeApp:assembleStableDebug` via MCP[cite: 2].
|
||||||
- **Dispatcher Integrity:** Never remove or bypass `SwingMainDispatcherFactory` in `composeApp/src/jvmMain/.../core/coroutines/`[cite: 2]. It is required to prevent `compose-webview` from hijacking `Dispatchers.Main` away from the Swing EDT[cite: 2].
|
- **Dispatcher Integrity:** Never remove or bypass `SwingMainDispatcherFactory` in `composeApp/src/jvmMain/.../core/coroutines/`[cite: 2]. It is required to prevent `compose-webview` from hijacking `Dispatchers.Main` away from the Swing EDT[cite: 2].
|
||||||
- **Live Preview Iteration:** Use `compose-hot-reload` to evaluate UI layout changes headlessly without forcing full Gradle rebuilds.
|
- **Live Preview Iteration:** Use `compose-hot-reload` to evaluate UI layout changes headlessly without forcing full Gradle rebuilds.
|
||||||
|
|||||||
@ -43,12 +43,24 @@ plugins {
|
|||||||
val bundleId = "org.spotube.spotube"
|
val bundleId = "org.spotube.spotube"
|
||||||
val version = "6.0.0"
|
val version = "6.0.0"
|
||||||
val buildNumber = 46
|
val buildNumber = 46
|
||||||
|
// Desktop icon channel; Android picks its icons via the stable/nightly product flavor. Use -Pnightly=true.
|
||||||
|
val isNightly = providers.gradleProperty("nightly").map { it.toBoolean() }.getOrElse(false)
|
||||||
|
val desktopIconDir = if (isNightly) "icons/nightly" else "icons/stable"
|
||||||
|
// Nightlies are numbered by the CI run (-PbuildNumber=<run>, or GITHUB_RUN_NUMBER) so every build is traceable to its run.
|
||||||
|
val nightlyBuildNumber = providers.gradleProperty("buildNumber").orNull?.toIntOrNull()
|
||||||
|
?: System.getenv("GITHUB_RUN_NUMBER")?.toIntOrNull()
|
||||||
|
?: buildNumber
|
||||||
val targetName = "Spotube"
|
val targetName = "Spotube"
|
||||||
val homePageUrl = "https://spotube.org"
|
val homePageUrl = "https://spotube.org"
|
||||||
val repositoryUrl = "https://github.com/team-spotube/spotube"
|
val repositoryUrl = "https://github.com/team-spotube/spotube"
|
||||||
val appVersionName = version
|
val appVersionName = version
|
||||||
val appBuildNumber = buildNumber
|
val appBuildNumber = if (isNightly) nightlyBuildNumber else buildNumber
|
||||||
val appRepositoryUrl = repositoryUrl
|
val appRepositoryUrl = repositoryUrl
|
||||||
|
// CI reads the release version from here instead of scraping this file.
|
||||||
|
tasks.register("printVersion") {
|
||||||
|
val appVersion = appVersionName
|
||||||
|
doLast { println(appVersion) }
|
||||||
|
}
|
||||||
val githubRepositoryPath = repositoryUrl.removePrefix("https://github.com/")
|
val githubRepositoryPath = repositoryUrl.removePrefix("https://github.com/")
|
||||||
val latestReleaseApiUrl = "https://api.github.com/repos/$githubRepositoryPath/releases/latest"
|
val latestReleaseApiUrl = "https://api.github.com/repos/$githubRepositoryPath/releases/latest"
|
||||||
val generatedAppInfoDir = layout.buildDirectory.dir("generated/appBuildInfo/commonMain/kotlin")
|
val generatedAppInfoDir = layout.buildDirectory.dir("generated/appBuildInfo/commonMain/kotlin")
|
||||||
@ -297,6 +309,19 @@ android {
|
|||||||
versionCode = buildNumber
|
versionCode = buildNumber
|
||||||
versionName = version
|
versionName = version
|
||||||
}
|
}
|
||||||
|
// stable = production channel, nightly = beta channel (own icon, name and installable side by side)
|
||||||
|
flavorDimensions += "channel"
|
||||||
|
productFlavors {
|
||||||
|
create("stable") {
|
||||||
|
dimension = "channel"
|
||||||
|
}
|
||||||
|
create("nightly") {
|
||||||
|
dimension = "channel"
|
||||||
|
applicationIdSuffix = ".nightly"
|
||||||
|
versionCode = nightlyBuildNumber
|
||||||
|
versionNameSuffix = "-nightly.$nightlyBuildNumber"
|
||||||
|
}
|
||||||
|
}
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
resources {
|
||||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||||
@ -382,11 +407,13 @@ nucleus.application {
|
|||||||
homepage = homePageUrl
|
homepage = homePageUrl
|
||||||
|
|
||||||
linux {
|
linux {
|
||||||
|
iconFile.set(project.file("$desktopIconDir/icon.png"))
|
||||||
modules("jdk.security.auth")
|
modules("jdk.security.auth")
|
||||||
debMaintainer = "Team Spotube <team.spotube@proton.me>"
|
debMaintainer = "Team Spotube <team.spotube@proton.me>"
|
||||||
rpmLicenseType = "AGPL-3.0-or-later"
|
rpmLicenseType = "AGPL-3.0-or-later"
|
||||||
}
|
}
|
||||||
windows {
|
windows {
|
||||||
|
iconFile.set(project.file("$desktopIconDir/icon.ico"))
|
||||||
shortcut = true
|
shortcut = true
|
||||||
menu = true
|
menu = true
|
||||||
dirChooser = true
|
dirChooser = true
|
||||||
@ -394,6 +421,7 @@ nucleus.application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macOS {
|
macOS {
|
||||||
|
iconFile.set(project.file("$desktopIconDir/icon.icns"))
|
||||||
notarization {
|
notarization {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
composeApp/icons/nightly/icon.icns
Normal file
BIN
composeApp/icons/nightly/icon.ico
Normal file
|
After Width: | Height: | Size: 364 KiB |
BIN
composeApp/icons/nightly/icon.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
composeApp/icons/stable/icon.icns
Normal file
BIN
composeApp/icons/stable/icon.ico
Normal file
|
After Width: | Height: | Size: 364 KiB |
BIN
composeApp/icons/stable/icon.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
composeApp/src/androidMain/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
@ -29,6 +29,7 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.media3.common.MediaItem as Media3MediaItem
|
import androidx.media3.common.MediaItem as Media3MediaItem
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.session.DefaultMediaNotificationProvider
|
||||||
import androidx.media3.session.LibraryResult
|
import androidx.media3.session.LibraryResult
|
||||||
import androidx.media3.session.MediaLibraryService
|
import androidx.media3.session.MediaLibraryService
|
||||||
import androidx.media3.session.MediaLibraryService.LibraryParams
|
import androidx.media3.session.MediaLibraryService.LibraryParams
|
||||||
@ -41,6 +42,7 @@ import com.google.common.util.concurrent.Futures
|
|||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import org.spotube.spotube.MainActivity
|
import org.spotube.spotube.MainActivity
|
||||||
|
import org.spotube.spotube.R
|
||||||
import org.spotube.spotube.core.audioplayer.AudioPlayer
|
import org.spotube.spotube.core.audioplayer.AudioPlayer
|
||||||
import org.spotube.spotube.core.audioplayer.AudioPlayerInterface
|
import org.spotube.spotube.core.audioplayer.AudioPlayerInterface
|
||||||
import org.spotube.spotube.core.audioplayer.AudioPlayerQueue
|
import org.spotube.spotube.core.audioplayer.AudioPlayerQueue
|
||||||
@ -75,6 +77,16 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
|
|||||||
|
|
||||||
createNotificationChannel()
|
createNotificationChannel()
|
||||||
|
|
||||||
|
// Media3 posts the real playback notification; point it at our channel,
|
||||||
|
// ID and monochrome icon so it replaces the placeholder below in place.
|
||||||
|
setMediaNotificationProvider(
|
||||||
|
DefaultMediaNotificationProvider.Builder(this)
|
||||||
|
.setChannelId(CHANNEL_ID)
|
||||||
|
.setNotificationId(NOTIFICATION_ID)
|
||||||
|
.build()
|
||||||
|
.apply { setSmallIcon(R.drawable.ic_stat_spotube) }
|
||||||
|
)
|
||||||
|
|
||||||
val sessionActivity = PendingIntent.getActivity(
|
val sessionActivity = PendingIntent.getActivity(
|
||||||
this,
|
this,
|
||||||
0,
|
0,
|
||||||
@ -87,7 +99,7 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
|
|||||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
.setContentTitle("Spotube")
|
.setContentTitle("Spotube")
|
||||||
.setContentText(runBlocking { getString(Res.string.notification_playback_service_text) })
|
.setContentText(runBlocking { getString(Res.string.notification_playback_service_text) })
|
||||||
.setSmallIcon(android.R.drawable.ic_media_play)
|
.setSmallIcon(R.drawable.ic_stat_spotube)
|
||||||
.setContentIntent(sessionActivity)
|
.setContentIntent(sessionActivity)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
BIN
composeApp/src/androidMain/res/drawable-hdpi/ic_stat_spotube.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
composeApp/src/androidMain/res/drawable-mdpi/ic_stat_spotube.png
Normal file
|
After Width: | Height: | Size: 737 B |
@ -1,47 +0,0 @@
|
|||||||
<!--
|
|
||||||
~ Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
|
|
||||||
~
|
|
||||||
~ This program is free software: you can redistribute it and/or modify
|
|
||||||
~ it under the terms of the GNU Affero General Public License as published by
|
|
||||||
~ the Free Software Foundation, either version 3 of the License, or
|
|
||||||
~ (at your option) any later version.
|
|
||||||
~
|
|
||||||
~ This program is distributed in the hope that it will be useful,
|
|
||||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
~ GNU Affero General Public License for more details.
|
|
||||||
~
|
|
||||||
~ You should have received a copy of the GNU Affero General Public License
|
|
||||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:endX="85.84757"
|
|
||||||
android:endY="92.4963"
|
|
||||||
android:startX="42.9492"
|
|
||||||
android:startY="49.59793"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0" />
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0" />
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:fillType="nonZero"
|
|
||||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
|
||||||
android:strokeWidth="1"
|
|
||||||
android:strokeColor="#00000000" />
|
|
||||||
</vector>
|
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
@ -1,187 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
|
|
||||||
~
|
|
||||||
~ This program is free software: you can redistribute it and/or modify
|
|
||||||
~ it under the terms of the GNU Affero General Public License as published by
|
|
||||||
~ the Free Software Foundation, either version 3 of the License, or
|
|
||||||
~ (at your option) any later version.
|
|
||||||
~
|
|
||||||
~ This program is distributed in the hope that it will be useful,
|
|
||||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
~ GNU Affero General Public License for more details.
|
|
||||||
~
|
|
||||||
~ You should have received a copy of the GNU Affero General Public License
|
|
||||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#3DDC84"
|
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M9,0L9,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,0L19,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,0L29,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,0L39,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,0L49,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,0L59,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,0L69,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,0L79,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M89,0L89,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M99,0L99,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,9L108,9"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,19L108,19"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,29L108,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,39L108,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,49L108,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,59L108,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,69L108,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,79L108,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,89L108,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,99L108,99"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,29L89,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,39L89,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,49L89,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,59L89,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,69L89,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,79L89,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,19L29,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,19L39,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,19L49,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,19L59,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,19L69,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,19L79,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
</vector>
|
|
||||||
@ -17,6 +17,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@color/ic_launcher_background" />
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||||
</adaptive-icon>
|
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
|
||||||
|
</adaptive-icon>
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@color/ic_launcher_background" />
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||||
</adaptive-icon>
|
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
|
||||||
|
</adaptive-icon>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 23 KiB |
21
composeApp/src/androidMain/res/values/colors.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
|
||||||
|
~
|
||||||
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU Affero General Public License as published by
|
||||||
|
~ the Free Software Foundation, either version 3 of the License, or
|
||||||
|
~ (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU Affero General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU Affero General Public License
|
||||||
|
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#000000</color>
|
||||||
|
</resources>
|
||||||
@ -907,6 +907,7 @@
|
|||||||
<string name="plugin_discover_badge_verified">Verified</string>
|
<string name="plugin_discover_badge_verified">Verified</string>
|
||||||
<string name="plugin_discover_stars_content_description">GitHub stars</string>
|
<string name="plugin_discover_stars_content_description">GitHub stars</string>
|
||||||
<string name="plugin_discover_repository_content_description">GitHub repository URL</string>
|
<string name="plugin_discover_repository_content_description">GitHub repository URL</string>
|
||||||
|
<string name="plugin_discover_repository_host">github.com</string>
|
||||||
|
|
||||||
<!-- Plugin release install dialog -->
|
<!-- Plugin release install dialog -->
|
||||||
<string name="plugin_install_no_releases">No releases found</string>
|
<string name="plugin_install_no_releases">No releases found</string>
|
||||||
|
|||||||
@ -40,6 +40,8 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@ -134,6 +136,7 @@ import spotube.composeapp.generated.resources.Res
|
|||||||
import spotube.composeapp.generated.resources.plugin_discover_badge_official
|
import spotube.composeapp.generated.resources.plugin_discover_badge_official
|
||||||
import spotube.composeapp.generated.resources.plugin_discover_badge_verified
|
import spotube.composeapp.generated.resources.plugin_discover_badge_verified
|
||||||
import spotube.composeapp.generated.resources.plugin_discover_repository_content_description
|
import spotube.composeapp.generated.resources.plugin_discover_repository_content_description
|
||||||
|
import spotube.composeapp.generated.resources.plugin_discover_repository_host
|
||||||
import spotube.composeapp.generated.resources.plugin_discover_stars_content_description
|
import spotube.composeapp.generated.resources.plugin_discover_stars_content_description
|
||||||
import spotube.composeapp.generated.resources.plugin_info_abilities
|
import spotube.composeapp.generated.resources.plugin_info_abilities
|
||||||
import spotube.composeapp.generated.resources.plugin_info_api_version
|
import spotube.composeapp.generated.resources.plugin_info_api_version
|
||||||
@ -848,6 +851,7 @@ private fun SupportDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun DiscoverRepoCard(
|
private fun DiscoverRepoCard(
|
||||||
repo: GitHubRepo,
|
repo: GitHubRepo,
|
||||||
@ -952,9 +956,10 @@ private fun DiscoverRepoCard(
|
|||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(
|
FlowRow(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
itemVerticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
repo.owner.login,
|
repo.owner.login,
|
||||||
@ -997,9 +1002,10 @@ private fun DiscoverRepoCard(
|
|||||||
tint = AppTheme.colors.onSurfaceVariant
|
tint = AppTheme.colors.onSurfaceVariant
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"github.com",
|
stringResource(Res.string.plugin_discover_repository_host),
|
||||||
style = AppTheme.typography.labelSmall,
|
style = AppTheme.typography.labelSmall,
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
color = AppTheme.colors.onSurfaceVariant,
|
||||||
|
singleLine = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,9 @@ import androidx.compose.ui.unit.isSpecified
|
|||||||
import androidx.compose.ui.window.WindowPlacement
|
import androidx.compose.ui.window.WindowPlacement
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.rememberWindowState
|
import androidx.compose.ui.window.rememberWindowState
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import spotube.composeapp.generated.resources.Res
|
||||||
|
import spotube.composeapp.generated.resources.spotube_logo
|
||||||
import dev.nucleusframework.application.DecoratedWindow
|
import dev.nucleusframework.application.DecoratedWindow
|
||||||
import dev.nucleusframework.application.NucleusBackend
|
import dev.nucleusframework.application.NucleusBackend
|
||||||
import dev.nucleusframework.application.nucleusApplication
|
import dev.nucleusframework.application.nucleusApplication
|
||||||
@ -200,6 +203,7 @@ fun main() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = "Spotube",
|
title = "Spotube",
|
||||||
|
icon = painterResource(Res.drawable.spotube_logo),
|
||||||
visible = isWindowVisible
|
visible = isWindowVisible
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
|
|||||||
BIN
composeApp/src/nightly/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
BIN
composeApp/src/nightly/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
composeApp/src/nightly/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
BIN
composeApp/src/nightly/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
composeApp/src/nightly/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
BIN
composeApp/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
composeApp/src/nightly/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 13 KiB |
BIN
composeApp/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
composeApp/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 20 KiB |
BIN
composeApp/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
21
composeApp/src/nightly/res/values/strings.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
|
||||||
|
~
|
||||||
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU Affero General Public License as published by
|
||||||
|
~ the Free Software Foundation, either version 3 of the License, or
|
||||||
|
~ (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU Affero General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU Affero General Public License
|
||||||
|
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Spotube Nightly</string>
|
||||||
|
</resources>
|
||||||
@ -15,7 +15,8 @@
|
|||||||
],
|
],
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"platform" : "ios",
|
"platform" : "ios",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024",
|
||||||
|
"filename" : "app-icon-1024-dark.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"appearances" : [
|
"appearances" : [
|
||||||
@ -26,7 +27,8 @@
|
|||||||
],
|
],
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"platform" : "ios",
|
"platform" : "ios",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024",
|
||||||
|
"filename" : "app-icon-1024-tinted.png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 179 KiB |