Обновление 0.5.4.1
Добавлен прокси, Перевод оставшихся слов и исправление орфографических ошибок. Удаление трека из очереди свайпом
This commit is contained in:
@@ -12,7 +12,7 @@ android {
|
|||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 33
|
targetSdk = 33
|
||||||
versionCode = 20
|
versionCode = 20
|
||||||
versionName = "0.5.4ru"
|
versionName = "0.5.4.1rus"
|
||||||
}
|
}
|
||||||
|
|
||||||
splits {
|
splits {
|
||||||
@@ -74,6 +74,7 @@ dependencies {
|
|||||||
implementation(projects.composeRouting)
|
implementation(projects.composeRouting)
|
||||||
implementation(projects.composeReordering)
|
implementation(projects.composeReordering)
|
||||||
|
|
||||||
|
|
||||||
implementation(libs.compose.activity)
|
implementation(libs.compose.activity)
|
||||||
implementation(libs.compose.foundation)
|
implementation(libs.compose.foundation)
|
||||||
implementation(libs.compose.ui)
|
implementation(libs.compose.ui)
|
||||||
@@ -85,8 +86,10 @@ dependencies {
|
|||||||
implementation(libs.palette)
|
implementation(libs.palette)
|
||||||
|
|
||||||
implementation(libs.exoplayer)
|
implementation(libs.exoplayer)
|
||||||
|
implementation(libs.exoplayer.okhttp)
|
||||||
|
|
||||||
implementation(libs.room)
|
implementation(libs.room)
|
||||||
|
implementation("androidx.media3:media3-datasource-okhttp:1.0.0-alpha03")
|
||||||
kapt(libs.room.compiler)
|
kapt(libs.room.compiler)
|
||||||
|
|
||||||
implementation(projects.innertube)
|
implementation(projects.innertube)
|
||||||
|
|||||||
@@ -60,6 +60,16 @@ import com.valentinilk.shimmer.LocalShimmerTheme
|
|||||||
import com.valentinilk.shimmer.defaultShimmerTheme
|
import com.valentinilk.shimmer.defaultShimmerTheme
|
||||||
import it.hamy.compose.persist.PersistMap
|
import it.hamy.compose.persist.PersistMap
|
||||||
import it.hamy.compose.persist.PersistMapOwner
|
import it.hamy.compose.persist.PersistMapOwner
|
||||||
|
|
||||||
|
import it.hamy.innertube.utils.ProxyPreferenceItem
|
||||||
|
import it.hamy.innertube.utils.ProxyPreferences
|
||||||
|
import it.hamy.muza.utils.isProxyEnabledKey
|
||||||
|
import it.hamy.muza.utils.proxyHostNameKey
|
||||||
|
import it.hamy.muza.utils.proxyModeKey
|
||||||
|
import it.hamy.muza.utils.proxyPortKey
|
||||||
|
import java.net.Proxy
|
||||||
|
|
||||||
|
|
||||||
import it.hamy.innertube.Innertube
|
import it.hamy.innertube.Innertube
|
||||||
import it.hamy.innertube.models.bodies.BrowseBody
|
import it.hamy.innertube.models.bodies.BrowseBody
|
||||||
import it.hamy.innertube.requests.playlistPage
|
import it.hamy.innertube.requests.playlistPage
|
||||||
@@ -127,6 +137,7 @@ class MainActivity : ComponentActivity(), PersistMapOwner {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION", "UNCHECKED_CAST")
|
@Suppress("DEPRECATION", "UNCHECKED_CAST")
|
||||||
persistMap = lastCustomNonConfigurationInstance as? PersistMap ?: PersistMap()
|
persistMap = lastCustomNonConfigurationInstance as? PersistMap ?: PersistMap()
|
||||||
|
|
||||||
@@ -134,6 +145,17 @@ class MainActivity : ComponentActivity(), PersistMapOwner {
|
|||||||
|
|
||||||
val launchedFromNotification = intent?.extras?.getBoolean("expandPlayerBottomSheet") == true
|
val launchedFromNotification = intent?.extras?.getBoolean("expandPlayerBottomSheet") == true
|
||||||
|
|
||||||
|
with(preferences){
|
||||||
|
if(getBoolean(isProxyEnabledKey,false)) {
|
||||||
|
val hostName = getString(proxyHostNameKey,null)
|
||||||
|
val proxyPort = getInt(proxyPortKey, 8080)
|
||||||
|
val proxyMode = getEnum(proxyModeKey, Proxy.Type.HTTP)
|
||||||
|
hostName?.let { hName->
|
||||||
|
ProxyPreferences.preference = ProxyPreferenceItem(hName,proxyPort,proxyMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val isSystemInDarkTheme = isSystemInDarkTheme()
|
val isSystemInDarkTheme = isSystemInDarkTheme()
|
||||||
|
|||||||
@@ -43,7 +43,14 @@ import androidx.media3.common.Player
|
|||||||
import androidx.media3.common.Timeline
|
import androidx.media3.common.Timeline
|
||||||
import androidx.media3.database.StandaloneDatabaseProvider
|
import androidx.media3.database.StandaloneDatabaseProvider
|
||||||
import androidx.media3.datasource.DataSource
|
import androidx.media3.datasource.DataSource
|
||||||
import androidx.media3.datasource.DefaultHttpDataSource
|
|
||||||
|
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||||
|
import it.hamy.innertube.utils.ProxyPreferences
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Proxy
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
import androidx.media3.datasource.ResolvingDataSource
|
import androidx.media3.datasource.ResolvingDataSource
|
||||||
import androidx.media3.datasource.cache.Cache
|
import androidx.media3.datasource.cache.Cache
|
||||||
import androidx.media3.datasource.cache.CacheDataSource
|
import androidx.media3.datasource.cache.CacheDataSource
|
||||||
@@ -737,12 +744,24 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun okHttpClient() : OkHttpClient{
|
||||||
|
ProxyPreferences.preference?.let{
|
||||||
|
return OkHttpClient.Builder()
|
||||||
|
.proxy(Proxy(it.proxyMode,InetSocketAddress(it.proxyHost,it.proxyPort)))
|
||||||
|
.connectTimeout(Duration.ofSeconds(16))
|
||||||
|
.readTimeout(Duration.ofSeconds(8))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
return OkHttpClient.Builder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(16))
|
||||||
|
.readTimeout(Duration.ofSeconds(8))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
private fun createCacheDataSource(): DataSource.Factory {
|
private fun createCacheDataSource(): DataSource.Factory {
|
||||||
return CacheDataSource.Factory().setCache(cache).apply {
|
return CacheDataSource.Factory().setCache(cache).apply {
|
||||||
setUpstreamDataSourceFactory(
|
setUpstreamDataSourceFactory(
|
||||||
DefaultHttpDataSource.Factory()
|
OkHttpDataSource.Factory(okHttpClient())
|
||||||
.setConnectTimeoutMs(16000)
|
|
||||||
.setReadTimeoutMs(8000)
|
|
||||||
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0")
|
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ fun InHistoryMediaItemMenu(
|
|||||||
|
|
||||||
if (isHiding) {
|
if (isHiding) {
|
||||||
ConfirmationDialog(
|
ConfirmationDialog(
|
||||||
text = "Do you really want to hide this song? Its playback time and cache will be wiped.\nThis action is irreversible.",
|
text = "Вы действительно хотите скрыть эту песню? Время воспроизведения и кэш будут удалены.\n" + "Это действие необратимо",
|
||||||
onDismiss = { isHiding = false },
|
onDismiss = { isHiding = false },
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
@@ -330,7 +330,7 @@ fun MediaItemMenu(
|
|||||||
|
|
||||||
if (isCreatingNewPlaylist && onAddToPlaylist != null) {
|
if (isCreatingNewPlaylist && onAddToPlaylist != null) {
|
||||||
TextFieldDialog(
|
TextFieldDialog(
|
||||||
hintText = "Enter the playlist name",
|
hintText = "Введите название плейлиста",
|
||||||
onDismiss = { isCreatingNewPlaylist = false },
|
onDismiss = { isCreatingNewPlaylist = false },
|
||||||
onDone = { text ->
|
onDone = { text ->
|
||||||
onDismiss()
|
onDismiss()
|
||||||
@@ -365,7 +365,7 @@ fun MediaItemMenu(
|
|||||||
|
|
||||||
if (onAddToPlaylist != null) {
|
if (onAddToPlaylist != null) {
|
||||||
SecondaryTextButton(
|
SecondaryTextButton(
|
||||||
text = "New playlist",
|
text = "Новый плейлист",
|
||||||
onClick = { isCreatingNewPlaylist = true },
|
onClick = { isCreatingNewPlaylist = true },
|
||||||
alternative = true
|
alternative = true
|
||||||
)
|
)
|
||||||
@@ -377,7 +377,7 @@ fun MediaItemMenu(
|
|||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.playlist,
|
icon = R.drawable.playlist,
|
||||||
text = playlistPreview.playlist.name,
|
text = playlistPreview.playlist.name,
|
||||||
secondaryText = "${playlistPreview.songCount} songs",
|
secondaryText = "${playlistPreview.songCount} песен",
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
onAddToPlaylist(playlistPreview.playlist, playlistPreview.songCount)
|
onAddToPlaylist(playlistPreview.playlist, playlistPreview.songCount)
|
||||||
@@ -463,7 +463,7 @@ fun MediaItemMenu(
|
|||||||
onStartRadio?.let { onStartRadio ->
|
onStartRadio?.let { onStartRadio ->
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.radio,
|
icon = R.drawable.radio,
|
||||||
text = "Start radio",
|
text = "Включить радио",
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
onStartRadio()
|
onStartRadio()
|
||||||
@@ -474,7 +474,7 @@ fun MediaItemMenu(
|
|||||||
onPlayNext?.let { onPlayNext ->
|
onPlayNext?.let { onPlayNext ->
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.play_skip_forward,
|
icon = R.drawable.play_skip_forward,
|
||||||
text = "Play next",
|
text = "Следующая",
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
onPlayNext()
|
onPlayNext()
|
||||||
@@ -485,7 +485,7 @@ fun MediaItemMenu(
|
|||||||
onEnqueue?.let { onEnqueue ->
|
onEnqueue?.let { onEnqueue ->
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.enqueue,
|
icon = R.drawable.enqueue,
|
||||||
text = "Enqueue",
|
text = "В очередь",
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
onEnqueue()
|
onEnqueue()
|
||||||
@@ -496,7 +496,7 @@ fun MediaItemMenu(
|
|||||||
onGoToEqualizer?.let { onGoToEqualizer ->
|
onGoToEqualizer?.let { onGoToEqualizer ->
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.equalizer,
|
icon = R.drawable.equalizer,
|
||||||
text = "Equalizer",
|
text = "Эквалайзер",
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
onGoToEqualizer()
|
onGoToEqualizer()
|
||||||
@@ -520,9 +520,9 @@ fun MediaItemMenu(
|
|||||||
if (isShowingSleepTimerDialog) {
|
if (isShowingSleepTimerDialog) {
|
||||||
if (sleepTimerMillisLeft != null) {
|
if (sleepTimerMillisLeft != null) {
|
||||||
ConfirmationDialog(
|
ConfirmationDialog(
|
||||||
text = "Do you want to stop the sleep timer?",
|
text = "Вы хотите отключить таймер сна?",
|
||||||
cancelText = "No",
|
cancelText = "нет",
|
||||||
confirmText = "Stop",
|
confirmText = "отключить",
|
||||||
onDismiss = { isShowingSleepTimerDialog = false },
|
onDismiss = { isShowingSleepTimerDialog = false },
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
binder?.cancelSleepTimer()
|
binder?.cancelSleepTimer()
|
||||||
@@ -538,7 +538,7 @@ fun MediaItemMenu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
BasicText(
|
BasicText(
|
||||||
text = "Set sleep timer",
|
text = "Установить таймер сна",
|
||||||
style = typography.s.semiBold,
|
style = typography.s.semiBold,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = 8.dp, horizontal = 24.dp)
|
.padding(vertical = 8.dp, horizontal = 24.dp)
|
||||||
@@ -570,13 +570,13 @@ fun MediaItemMenu(
|
|||||||
|
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
BasicText(
|
BasicText(
|
||||||
text = "88h 88m",
|
text = "88ч 88м",
|
||||||
style = typography.s.semiBold,
|
style = typography.s.semiBold,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.alpha(0f)
|
.alpha(0f)
|
||||||
)
|
)
|
||||||
BasicText(
|
BasicText(
|
||||||
text = "${amount / 6}h ${(amount % 6) * 10}m",
|
text = "${amount / 6}ч ${(amount % 6) * 10}м",
|
||||||
style = typography.s.semiBold
|
style = typography.s.semiBold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -603,12 +603,12 @@ fun MediaItemMenu(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
DialogTextButton(
|
DialogTextButton(
|
||||||
text = "Cancel",
|
text = "Отмена",
|
||||||
onClick = { isShowingSleepTimerDialog = false }
|
onClick = { isShowingSleepTimerDialog = false }
|
||||||
)
|
)
|
||||||
|
|
||||||
DialogTextButton(
|
DialogTextButton(
|
||||||
text = "Set",
|
text = "Установить",
|
||||||
enabled = amount > 0,
|
enabled = amount > 0,
|
||||||
primary = true,
|
primary = true,
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -623,12 +623,12 @@ fun MediaItemMenu(
|
|||||||
|
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.alarm,
|
icon = R.drawable.alarm,
|
||||||
text = "Sleep timer",
|
text = "Таймер сна",
|
||||||
onClick = { isShowingSleepTimerDialog = true },
|
onClick = { isShowingSleepTimerDialog = true },
|
||||||
trailingContent = sleepTimerMillisLeft?.let {
|
trailingContent = sleepTimerMillisLeft?.let {
|
||||||
{
|
{
|
||||||
BasicText(
|
BasicText(
|
||||||
text = "${formatAsDuration(it)} left",
|
text = "Осталось ${formatAsDuration(it)}",
|
||||||
style = typography.xxs.medium,
|
style = typography.xxs.medium,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(
|
.background(
|
||||||
@@ -646,7 +646,7 @@ fun MediaItemMenu(
|
|||||||
if (onAddToPlaylist != null) {
|
if (onAddToPlaylist != null) {
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.playlist,
|
icon = R.drawable.playlist,
|
||||||
text = "Add to playlist",
|
text = "Добавить в плейлист",
|
||||||
onClick = { isViewingPlaylists = true },
|
onClick = { isViewingPlaylists = true },
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Image(
|
Image(
|
||||||
@@ -666,7 +666,7 @@ fun MediaItemMenu(
|
|||||||
albumInfo?.let { (albumId) ->
|
albumInfo?.let { (albumId) ->
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.disc,
|
icon = R.drawable.disc,
|
||||||
text = "Go to album",
|
text = "Перейти в альбом",
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
onGoToAlbum(albumId)
|
onGoToAlbum(albumId)
|
||||||
@@ -679,7 +679,7 @@ fun MediaItemMenu(
|
|||||||
artistsInfo?.forEach { (authorId, authorName) ->
|
artistsInfo?.forEach { (authorId, authorName) ->
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.person,
|
icon = R.drawable.person,
|
||||||
text = "More of $authorName",
|
text = "Больше от $authorName",
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
onGoToArtist(authorId)
|
onGoToArtist(authorId)
|
||||||
@@ -691,7 +691,7 @@ fun MediaItemMenu(
|
|||||||
onRemoveFromQueue?.let { onRemoveFromQueue ->
|
onRemoveFromQueue?.let { onRemoveFromQueue ->
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.trash,
|
icon = R.drawable.trash,
|
||||||
text = "Remove from queue",
|
text = "Убрать из очереди",
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
onRemoveFromQueue()
|
onRemoveFromQueue()
|
||||||
@@ -702,7 +702,7 @@ fun MediaItemMenu(
|
|||||||
onRemoveFromPlaylist?.let { onRemoveFromPlaylist ->
|
onRemoveFromPlaylist?.let { onRemoveFromPlaylist ->
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.trash,
|
icon = R.drawable.trash,
|
||||||
text = "Remove from playlist",
|
text = "Удалить из плейлиста",
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
onRemoveFromPlaylist()
|
onRemoveFromPlaylist()
|
||||||
@@ -713,7 +713,7 @@ fun MediaItemMenu(
|
|||||||
onHideFromDatabase?.let { onHideFromDatabase ->
|
onHideFromDatabase?.let { onHideFromDatabase ->
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.trash,
|
icon = R.drawable.trash,
|
||||||
text = "Hide",
|
text = "Скрыть",
|
||||||
onClick = onHideFromDatabase
|
onClick = onHideFromDatabase
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -721,7 +721,7 @@ fun MediaItemMenu(
|
|||||||
onRemoveFromQuickPicks?.let {
|
onRemoveFromQuickPicks?.let {
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.trash,
|
icon = R.drawable.trash,
|
||||||
text = "Hide from \"Quick picks\"",
|
text = "Скрыть из \"Обзора\"",
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
onRemoveFromQuickPicks()
|
onRemoveFromQuickPicks()
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ fun Lyrics(
|
|||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
) {
|
) {
|
||||||
BasicText(
|
BasicText(
|
||||||
text = "${if (isShowingSynchronizedLyrics) "Синхронизирован текст" else "Т"}екст песни не доступен",
|
text = "${if (isShowingSynchronizedLyrics) "Синхронизированный т" else "Т"}екст не доступен",
|
||||||
style = typography.xs.center.medium.color(PureBlackColorPalette.text),
|
style = typography.xs.center.medium.color(PureBlackColorPalette.text),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(Color.Black.copy(0.4f))
|
.background(Color.Black.copy(0.4f))
|
||||||
|
|||||||
@@ -83,6 +83,13 @@ import it.hamy.muza.utils.smoothScrollToTop
|
|||||||
import it.hamy.muza.utils.windows
|
import it.hamy.muza.utils.windows
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
|
import androidx.compose.foundation.gestures.draggable
|
||||||
|
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
@@ -202,6 +209,7 @@ fun Queue(
|
|||||||
key = { it.uid.hashCode() }
|
key = { it.uid.hashCode() }
|
||||||
) { window ->
|
) { window ->
|
||||||
val isPlayingThisMediaItem = mediaItemIndex == window.firstPeriodIndex
|
val isPlayingThisMediaItem = mediaItemIndex == window.firstPeriodIndex
|
||||||
|
var offsetX by remember { mutableStateOf(0f) }
|
||||||
|
|
||||||
SongItem(
|
SongItem(
|
||||||
song = window.mediaItem,
|
song = window.mediaItem,
|
||||||
@@ -283,6 +291,23 @@ fun Queue(
|
|||||||
reorderingState = reorderingState,
|
reorderingState = reorderingState,
|
||||||
index = window.firstPeriodIndex
|
index = window.firstPeriodIndex
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.draggable(
|
||||||
|
orientation = Orientation.Horizontal,
|
||||||
|
state = rememberDraggableState(onDelta = { delta ->
|
||||||
|
if (isPlayingThisMediaItem) return@rememberDraggableState
|
||||||
|
offsetX += delta
|
||||||
|
}),
|
||||||
|
onDragStopped = { velocity ->
|
||||||
|
if ((offsetX <= -300.0f && velocity <= -3000.0f) || (offsetX >= 300.0f && velocity >= 3000.0f)) {
|
||||||
|
binder.player.removeMediaItem(window.firstPeriodIndex)
|
||||||
|
} else {
|
||||||
|
offsetX = 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.offset{ IntOffset(offsetX.roundToInt(), 0) }
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.provider.Settings
|
|||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
@@ -38,10 +39,17 @@ import it.hamy.muza.utils.isAtLeastAndroid12
|
|||||||
import it.hamy.muza.utils.isAtLeastAndroid6
|
import it.hamy.muza.utils.isAtLeastAndroid6
|
||||||
import it.hamy.muza.utils.isIgnoringBatteryOptimizations
|
import it.hamy.muza.utils.isIgnoringBatteryOptimizations
|
||||||
import it.hamy.muza.utils.isInvincibilityEnabledKey
|
import it.hamy.muza.utils.isInvincibilityEnabledKey
|
||||||
|
import it.hamy.muza.utils.isProxyEnabledKey
|
||||||
import it.hamy.muza.utils.pauseSearchHistoryKey
|
import it.hamy.muza.utils.pauseSearchHistoryKey
|
||||||
|
import it.hamy.muza.utils.proxyHostNameKey
|
||||||
|
import it.hamy.muza.utils.proxyModeKey
|
||||||
|
import it.hamy.muza.utils.proxyPortKey
|
||||||
import it.hamy.muza.utils.rememberPreference
|
import it.hamy.muza.utils.rememberPreference
|
||||||
import it.hamy.muza.utils.toast
|
import it.hamy.muza.utils.toast
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import java.net.Proxy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("BatteryLife")
|
@SuppressLint("BatteryLife")
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@@ -72,6 +80,14 @@ fun OtherSettings() {
|
|||||||
|
|
||||||
var isInvincibilityEnabled by rememberPreference(isInvincibilityEnabledKey, false)
|
var isInvincibilityEnabled by rememberPreference(isInvincibilityEnabledKey, false)
|
||||||
|
|
||||||
|
var isProxyEnabled by rememberPreference(isProxyEnabledKey, false)
|
||||||
|
|
||||||
|
var proxyHost by rememberPreference(proxyHostNameKey, defaultValue = "")
|
||||||
|
|
||||||
|
var proxyPort by rememberPreference(proxyPortKey, defaultValue = 1080)
|
||||||
|
|
||||||
|
var proxyMode by rememberPreference(proxyModeKey, defaultValue = Proxy.Type.HTTP)
|
||||||
|
|
||||||
var isIgnoringBatteryOptimizations by remember {
|
var isIgnoringBatteryOptimizations by remember {
|
||||||
mutableStateOf(context.isIgnoringBatteryOptimizations)
|
mutableStateOf(context.isIgnoringBatteryOptimizations)
|
||||||
}
|
}
|
||||||
@@ -178,5 +194,33 @@ fun OtherSettings() {
|
|||||||
isChecked = isInvincibilityEnabled,
|
isChecked = isInvincibilityEnabled,
|
||||||
onCheckedChange = { isInvincibilityEnabled = it }
|
onCheckedChange = { isInvincibilityEnabled = it }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "PROXY")
|
||||||
|
|
||||||
|
SwitchSettingEntry(
|
||||||
|
title = "HTTP Proxy",
|
||||||
|
text = "Enable HTTP Proxy",
|
||||||
|
isChecked = isProxyEnabled,
|
||||||
|
onCheckedChange = { isProxyEnabled = it }
|
||||||
|
)
|
||||||
|
|
||||||
|
AnimatedVisibility(visible = isProxyEnabled) {
|
||||||
|
Column {
|
||||||
|
EnumValueSelectorSettingsEntry(title = "Proxy",
|
||||||
|
selectedValue = proxyMode, onValueSelected = {proxyMode = it})
|
||||||
|
TextDialogSettingEntry(
|
||||||
|
title = "Хост",
|
||||||
|
text = "Введите http хост",
|
||||||
|
currentText = proxyHost,
|
||||||
|
onTextSave = { proxyHost = it })
|
||||||
|
TextDialogSettingEntry(
|
||||||
|
title = "Порт",
|
||||||
|
text = "Введите порт",
|
||||||
|
currentText = proxyPort.toString(),
|
||||||
|
onTextSave = { proxyPort = it.toIntOrNull() ?: 1080 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,17 +21,20 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import it.hamy.compose.routing.RouteHandler
|
import it.hamy.compose.routing.RouteHandler
|
||||||
import it.hamy.muza.R
|
import it.hamy.muza.R
|
||||||
import it.hamy.muza.ui.components.themed.Scaffold
|
import it.hamy.muza.ui.components.themed.Scaffold
|
||||||
import it.hamy.muza.ui.components.themed.Switch
|
import it.hamy.muza.ui.components.themed.Switch
|
||||||
|
import it.hamy.muza.ui.components.themed.TextFieldDialog
|
||||||
import it.hamy.muza.ui.components.themed.ValueSelectorDialog
|
import it.hamy.muza.ui.components.themed.ValueSelectorDialog
|
||||||
import it.hamy.muza.ui.screens.globalRoutes
|
import it.hamy.muza.ui.screens.globalRoutes
|
||||||
import it.hamy.muza.ui.styling.LocalAppearance
|
import it.hamy.muza.ui.styling.LocalAppearance
|
||||||
import it.hamy.muza.utils.color
|
import it.hamy.muza.utils.color
|
||||||
import it.hamy.muza.utils.secondary
|
import it.hamy.muza.utils.secondary
|
||||||
import it.hamy.muza.utils.semiBold
|
import it.hamy.muza.utils.semiBold
|
||||||
|
import it.hamy.muza.utils.toast
|
||||||
|
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@@ -193,6 +196,37 @@ fun SettingsEntry(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TextDialogSettingEntry(
|
||||||
|
title: String,
|
||||||
|
text: String,
|
||||||
|
currentText: String,
|
||||||
|
onTextSave: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isEnabled: Boolean = true
|
||||||
|
) {
|
||||||
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
if (showDialog) {
|
||||||
|
TextFieldDialog(hintText =title ,
|
||||||
|
onDismiss = { showDialog = false },
|
||||||
|
onDone ={value->
|
||||||
|
onTextSave(value)
|
||||||
|
context.toast("Сохранено!")
|
||||||
|
} , doneText = "Save", initialTextInput = currentText)
|
||||||
|
}
|
||||||
|
SettingsEntry(
|
||||||
|
title = title,
|
||||||
|
text = text,
|
||||||
|
isEnabled = isEnabled,
|
||||||
|
onClick = { showDialog = true },
|
||||||
|
trailingContent = { },
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsDescription(
|
fun SettingsDescription(
|
||||||
text: String,
|
text: String,
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ const val homeScreenTabIndexKey = "homeScreenTabIndex"
|
|||||||
const val searchResultScreenTabIndexKey = "searchResultScreenTabIndex"
|
const val searchResultScreenTabIndexKey = "searchResultScreenTabIndex"
|
||||||
const val artistScreenTabIndexKey = "artistScreenTabIndex"
|
const val artistScreenTabIndexKey = "artistScreenTabIndex"
|
||||||
const val pauseSearchHistoryKey = "pauseSearchHistory"
|
const val pauseSearchHistoryKey = "pauseSearchHistory"
|
||||||
|
const val isProxyEnabledKey = "isProxyEnabled"
|
||||||
|
const val proxyHostNameKey = "proxyHostname"
|
||||||
|
const val proxyPortKey = "proxyPortKey"
|
||||||
|
const val proxyModeKey = "proxyModeKey"
|
||||||
|
|
||||||
inline fun <reified T : Enum<T>> SharedPreferences.getEnum(
|
inline fun <reified T : Enum<T>> SharedPreferences.getEnum(
|
||||||
key: String,
|
key: String,
|
||||||
|
|||||||
@@ -15,8 +15,12 @@ import io.ktor.serialization.kotlinx.json.json
|
|||||||
import it.hamy.innertube.models.NavigationEndpoint
|
import it.hamy.innertube.models.NavigationEndpoint
|
||||||
import it.hamy.innertube.models.Runs
|
import it.hamy.innertube.models.Runs
|
||||||
import it.hamy.innertube.models.Thumbnail
|
import it.hamy.innertube.models.Thumbnail
|
||||||
|
import it.hamy.innertube.utils.ProxyPreferences
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Proxy
|
||||||
|
|
||||||
|
|
||||||
object Innertube {
|
object Innertube {
|
||||||
val client = HttpClient(OkHttp) {
|
val client = HttpClient(OkHttp) {
|
||||||
@@ -44,6 +48,19 @@ object Innertube {
|
|||||||
parameters.append("prettyPrint", "false")
|
parameters.append("prettyPrint", "false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProxyPreferences.preference?.let {
|
||||||
|
engine {
|
||||||
|
proxy = Proxy(
|
||||||
|
it.proxyMode,
|
||||||
|
InetSocketAddress(
|
||||||
|
it.proxyHost,
|
||||||
|
it.proxyPort
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal const val browse = "/youtubei/v1/browse"
|
internal const val browse = "/youtubei/v1/browse"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package it.hamy.innertube.utils
|
||||||
|
|
||||||
|
import java.net.Proxy
|
||||||
|
|
||||||
|
object ProxyPreferences {
|
||||||
|
var preference: ProxyPreferenceItem? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ProxyPreferenceItem(
|
||||||
|
var proxyHost: String,
|
||||||
|
var proxyPort: Int,
|
||||||
|
var proxyMode: Proxy.Type
|
||||||
|
)
|
||||||
@@ -7,6 +7,7 @@ dependencyResolutionManagement {
|
|||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { setUrl("https://jitpack.io") }
|
maven { setUrl("https://jitpack.io") }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
versionCatalogs {
|
versionCatalogs {
|
||||||
@@ -36,6 +37,8 @@ dependencyResolutionManagement {
|
|||||||
|
|
||||||
version("media3", "1.0.0-beta03")
|
version("media3", "1.0.0-beta03")
|
||||||
library("exoplayer", "androidx.media3", "media3-exoplayer").versionRef("media3")
|
library("exoplayer", "androidx.media3", "media3-exoplayer").versionRef("media3")
|
||||||
|
library("exoplayer-okhttp", "androidx.media3", "media3-datasource-okhttp").versionRef("media3")
|
||||||
|
|
||||||
|
|
||||||
version("ktor", "2.1.2")
|
version("ktor", "2.1.2")
|
||||||
library("ktor-client-core", "io.ktor", "ktor-client-core").versionRef("ktor")
|
library("ktor-client-core", "io.ktor", "ktor-client-core").versionRef("ktor")
|
||||||
|
|||||||
Reference in New Issue
Block a user