Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/android-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,52 @@ jobs:
name: desktop-dmg-package
path: composeApp/build/compose/binaries/main-release/dmg/*.dmg

build-desktop-dmg-intel:
name: Build desktop DMG package
runs-on: macos-26-intel
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v6
with:
submodules: 'recursive'

- name: set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 21
distribution: "zulu"
cache: 'gradle'

- name: Update build product flavor
run: |
echo "" >> ./gradle.properties
echo "isFullBuild=true" >> ./gradle.properties

- name: Update Sentry Secrets
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN_JVM }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
echo 'SENTRY_DSN=${{ secrets.SENTRY_DSN_JVM }}' > ./local.properties
echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> ./local.properties

- name: Generate aboutLibraries.json
run: ./gradlew exportLibraryDefinitions

- name: Set up VLC
run: ./gradlew vlcSetup

- name: Build desktop DMG package
run: ./gradlew packageReleaseDmg

- name: Upload DMG package
uses: actions/upload-artifact@v4
with:
name: desktop-dmg-intel-package
path: composeApp/build/compose/binaries/main-release/dmg/*.dmg

build-desktop-msi:
name: Build desktop MSI package
runs-on: windows-latest
Expand Down
47 changes: 46 additions & 1 deletion .github/workflows/desktop-dmg-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,49 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: desktop-dmg-package
path: composeApp/build/compose/binaries/main-release/dmg/*.dmg
path: composeApp/build/compose/binaries/main-release/dmg/*.dmg
build-desktop-dmg-intel:
name: Build desktop DMG package
runs-on: macos-26-intel
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v6
with:
submodules: 'recursive'

- name: set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 21
distribution: "zulu"
cache: 'gradle'

- name: Update build product flavor
run: |
echo "" >> ./gradle.properties
echo "isFullBuild=true" >> ./gradle.properties

- name: Update Sentry Secrets
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN_JVM }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
echo 'SENTRY_DSN=${{ secrets.SENTRY_DSN_JVM }}' > ./local.properties
echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> ./local.properties

- name: Generate aboutLibraries.json
run: ./gradlew exportLibraryDefinitions

- name: Set up VLC
run: ./gradlew vlcSetup

- name: Build desktop DMG package
run: ./gradlew packageReleaseDmg

- name: Upload DMG package
uses: actions/upload-artifact@v4
with:
name: desktop-dmg-intel-package
path: composeApp/build/compose/binaries/main-release/dmg/*.dmg
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,7 @@ The software is provided "AS IS", without warranty of any kind. The developers o

Because we do not host any media files, we cannot process DMCA takedown requests for audio or video content. However, if you represent a copyright holder or have legal concerns regarding the open-source code itself, please contact us via email at: **ndtminh2608@gmail.com**

## Developer/Team
- [maxrave-dev](https://github.com/maxrave-dev/SimpMusic): Founder/Developer/Designer
- [Owen Connor](https://github.com/owencz1998): Discord Server Admin.
- [ilianoKokoro](https://github.com/ilianoKokoro): Discord Server Admin.
- [CrazyWolf13](https://github.com/CrazyWolf13): Issues organizer/planner.

## Contribute
We're looking for more contributors, all contributions are welcome!
See our [CODE OF CONDUCT](https://github.com/maxrave-dev/SimpMusic/blob/main/CODE_OF_CONDUCT.md)

Expand Down
23 changes: 17 additions & 6 deletions androidApp/src/main/java/com/maxrave/simpmusic/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,23 @@ class MainActivity : AppCompatActivity() {

if (!EasyPermissions.hasPermissions(this, Manifest.permission.POST_NOTIFICATIONS)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
EasyPermissions.requestPermissions(
this,
runBlocking { ComposeResUtils.getResString(ComposeResUtils.StringType.NOTIFICATION_REQUEST) },
1,
Manifest.permission.POST_NOTIFICATIONS,
)
val doNotAsk = getString("notification_permission_do_not_ask")
if (doNotAsk != "true") {
val wasAsked = getString("notification_permission_asked")
if (wasAsked != "true") {
// First time: request system permission
EasyPermissions.requestPermissions(
this,
runBlocking { ComposeResUtils.getResString(ComposeResUtils.StringType.NOTIFICATION_REQUEST) },
1,
Manifest.permission.POST_NOTIFICATIONS,
)
putString("notification_permission_asked", "true")
} else {
// Already asked before: show custom dialog with "Don't show again"
viewModel.showNotificationPermissionDialog()
}
}
}
}
viewModel.getLocation()
Expand Down
5 changes: 5 additions & 0 deletions composeApp/proguard-desktop-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -266,5 +266,10 @@
-dontwarn com.google.re2j.Matcher
-dontwarn com.google.re2j.Pattern

# Wire (used by NewPipe extractor) - AndroidMessage references Android classes not available on Desktop
-dontwarn android.os.Parcelable
-dontwarn android.os.Parcelable$Creator
-dontwarn android.os.Parcel

-keep class * extends androidx.room.RoomDatabase { <init>(); }
-keep class androidx.datastore.preferences.** { *; }
4 changes: 4 additions & 0 deletions composeApp/src/commonMain/composeResources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@
<string name="invalid_port">Invalid port</string>
<string name="proxy_host_message">Please enter your Proxy host</string>
<string name="proxy_port_message">Please enter your Proxy port</string>
<string name="proxy_username">Proxy username</string>
<string name="proxy_password">Proxy password</string>
<string name="proxy_username_message">Please enter your Proxy username (optional)</string>
<string name="proxy_password_message">Please enter your Proxy password (optional)</string>
<string name="five_seconds">Five seconds</string>
<string name="lrclib" translatable="false">LRCLIB</string>
<string name="better_lyrics" translatable="false">BetterLyrics</string>
Expand Down
92 changes: 78 additions & 14 deletions composeApp/src/commonMain/kotlin/com/maxrave/simpmusic/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.animation.shrinkHorizontally
import androidx.compose.animation.slideInHorizontally
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand All @@ -25,6 +26,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowForwardIos
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
Expand All @@ -34,6 +36,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
Expand Down Expand Up @@ -99,9 +102,12 @@ import org.jetbrains.compose.resources.stringResource
import org.koin.compose.koinInject
import simpmusic.composeapp.generated.resources.Res
import simpmusic.composeapp.generated.resources.cancel
import simpmusic.composeapp.generated.resources.do_not_show_again
import simpmusic.composeapp.generated.resources.download
import simpmusic.composeapp.generated.resources.good_night
import simpmusic.composeapp.generated.resources.notification
import simpmusic.composeapp.generated.resources.sleep_timer_off
import simpmusic.composeapp.generated.resources.this_app_needs_to_access_your_notification
import simpmusic.composeapp.generated.resources.this_link_is_not_supported
import simpmusic.composeapp.generated.resources.unknown
import simpmusic.composeapp.generated.resources.update_available
Expand All @@ -120,6 +126,7 @@ fun App(viewModel: SharedViewModel = koinInject()) {
val nowPlayingData by viewModel.nowPlayingState.collectAsStateWithLifecycle()
val updateData by viewModel.updateResponse.collectAsStateWithLifecycle()
val intent by viewModel.intent.collectAsStateWithLifecycle()
val showNotificationPermissionDialog by viewModel.showNotificationPermissionDialog.collectAsStateWithLifecycle()

val isTranslucentBottomBar by viewModel.getTranslucentBottomBar().collectAsStateWithLifecycle(DataStoreManager.FALSE)
val isLiquidGlassEnabled by viewModel.getEnableLiquidGlass().collectAsStateWithLifecycle(DataStoreManager.FALSE)
Expand Down Expand Up @@ -175,11 +182,12 @@ fun App(viewModel: SharedViewModel = koinInject()) {
val segments = data.pathSegments
// For simpmusic.org: segments = ["app", "watch"] → appPath = segments[1]
// For simpmusic://: host IS the appPath (e.g. host="watch"), segments = []
val appPath = if (data.scheme == "simpmusic") {
data.host
} else {
segments.getOrNull(1)
}
val appPath =
if (data.scheme == "simpmusic") {
data.host
} else {
segments.getOrNull(1)
}
Logger.d("MainActivity", "simpmusic.org deep link, appPath: $appPath")
viewModel.setIntent(null)
when (appPath) {
Expand All @@ -204,11 +212,12 @@ fun App(viewModel: SharedViewModel = koinInject()) {
"channel", "c" -> {
// simpmusic://channel/UCxxx → segments = ["UCxxx"]
// simpmusic.org/app/channel/UCxxx → segments = ["app", "channel", "UCxxx"]
val artistId = if (data.scheme == "simpmusic") {
segments.firstOrNull()
} else {
segments.getOrNull(2)
}
val artistId =
if (data.scheme == "simpmusic") {
segments.firstOrNull()
} else {
segments.getOrNull(2)
}
artistId?.let {
if (it.startsWith("UC")) {
navController.navigate(ArtistDestination(channelId = it))
Expand All @@ -231,7 +240,7 @@ fun App(viewModel: SharedViewModel = koinInject()) {
} else {
Logger.d("MainActivity", "onCreate: $data")
when (val path = data.pathSegments.firstOrNull()) {
"playlist" ->
"playlist" -> {
data
.getQueryParameter("list")
?.let { playlistId ->
Expand All @@ -256,8 +265,9 @@ fun App(viewModel: SharedViewModel = koinInject()) {
)
}
}
}

"channel", "c" ->
"channel", "c" -> {
data.lastPathSegment?.let { artistId ->
if (artistId.startsWith("UC")) {
viewModel.setIntent(null)
Expand All @@ -274,15 +284,17 @@ fun App(viewModel: SharedViewModel = koinInject()) {
)
}
}
}

else ->
else -> {
when {
path == "watch" -> data.getQueryParameter("v")
data.host == "youtu.be" -> path
else -> null
}?.let { videoId ->
viewModel.loadSharedMediaItem(videoId)
}
}
}
}
}
Expand Down Expand Up @@ -494,7 +506,6 @@ fun App(viewModel: SharedViewModel = koinInject()) {
sharedViewModel = viewModel,
isExpanded = true,
dismissIcon = Icons.AutoMirrored.Rounded.ArrowForwardIos,
onSwipeEnabledChange = {},
) {
isShowNowPlaylistScreen = false
}
Expand Down Expand Up @@ -669,6 +680,59 @@ fun App(viewModel: SharedViewModel = koinInject()) {
},
)
}

if (showNotificationPermissionDialog) {
var doNotShowAgain by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = {
viewModel.dismissNotificationPermissionDialog(doNotShowAgain)
},
confirmButton = {
TextButton(
onClick = {
viewModel.dismissNotificationPermissionDialog(doNotShowAgain)
},
) {
Text(
stringResource(Res.string.yes),
style = typo().bodySmall,
)
}
},
title = {
Text(
stringResource(Res.string.notification),
style = typo().labelSmall,
)
},
text = {
Column {
Text(
stringResource(Res.string.this_app_needs_to_access_your_notification),
style = typo().bodySmall,
)
Spacer(modifier = Modifier.height(8.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
Modifier
.clickable { doNotShowAgain = !doNotShowAgain }
.fillMaxWidth(),
) {
Checkbox(
checked = doNotShowAgain,
onCheckedChange = { doNotShowAgain = it },
)
Spacer(modifier = Modifier.width(5.dp))
Text(
stringResource(Res.string.do_not_show_again),
style = typo().bodySmall,
)
}
}
},
)
}
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,23 +294,11 @@ fun InfoPlayerBottomSheet(
val coroutineScope = rememberCoroutineScope()
val localDensity = LocalDensity.current
val windowInsets = WindowInsets.systemBars
var swipeEnabled by rememberSaveable { mutableStateOf(true) }
val scrollState = rememberScrollState()
val sheetState =
rememberModalBottomSheetState(
skipPartiallyExpanded = true,
confirmValueChange = {
swipeEnabled
},
)
val scrollState = rememberScrollState()

LaunchedEffect(true) {
snapshotFlow { scrollState.value }
.distinctUntilChanged()
.collectLatest {
swipeEnabled = scrollState.value == 0
}
}

val screenDataState by sharedViewModel.nowPlayingScreenData.collectAsStateWithLifecycle()
val songEntity by sharedViewModel.nowPlayingState.map { it?.songEntity }.collectAsState(null)
Expand Down
Loading
Loading