Skip to content
Open
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
95 changes: 86 additions & 9 deletions app/desktop/src/main/kotlin/AniDesktop.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,22 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.LocalSystemTheme
import androidx.compose.ui.Modifier
import androidx.compose.ui.SystemTheme
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.FrameWindowScope
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPlacement
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.application
Expand Down Expand Up @@ -118,15 +123,23 @@ import org.koin.core.context.startKoin
import org.openani.mediamp.ffmpeg.FFmpegKit
import org.openani.mediamp.vlc.VlcMediampPlayer
import java.awt.Frame
import java.awt.KeyEventDispatcher
import java.awt.KeyboardFocusManager
import java.awt.event.KeyEvent as AwtKeyEvent
import java.awt.event.MouseWheelListener
import java.io.File
import java.nio.file.Paths
import java.util.Locale
import kotlin.io.path.absolutePathString
import kotlin.math.roundToInt
import kotlin.system.exitProcess


private val logger by lazy { logger("Ani") }
private inline val toplevelLogger get() = logger
private const val APP_ZOOM_MIN = 0.5f
private const val APP_ZOOM_MAX = 2.0f
private const val APP_ZOOM_STEP = 0.1f

object AniDesktop {
// init {
Expand Down Expand Up @@ -440,21 +453,32 @@ object AniDesktop {
val uiSettings by settingsRepository.uiSettings.flow.collectAsState(UISettings.Default)
val trayState = rememberAniTrayState()
val appIcon = painterResource(Res.drawable.a_round)
val requestExit: () -> Unit = remember {
{
kotlin.runCatching { exitApplication() }
.onFailure { logger.error(it) { "Failed to exit application" } }
Unit
}
}

AniSystemTray(
state = trayState,
icon = appIcon,
tooltip = "Ani",
onExit = ::exitApplication,
onExit = requestExit,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这块好像是无关修改

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我尝试拆一下 不过以前的相关issue非常混乱 我可能不能包含的很清楚

)

Window(
visible = !trayState.isWindowHiddenToTray,
onCloseRequest = {
trayState.handleCloseRequest(
closeBehavior = uiSettings.desktopCloseBehavior,
onExit = ::exitApplication,
)
kotlin.runCatching {
trayState.handleCloseRequest(
closeBehavior = uiSettings.desktopCloseBehavior,
onExit = requestExit,
)
}.onFailure {
logger.error(it) { "Failed to handle close request from main window" }
}
},
state = windowState,
title = "Ani",
Expand Down Expand Up @@ -516,10 +540,14 @@ object AniDesktop {
WindowFrame(
windowState = windowState,
onCloseRequest = {
trayState.handleCloseRequest(
closeBehavior = uiSettings.desktopCloseBehavior,
onExit = ::exitApplication,
)
kotlin.runCatching {
trayState.handleCloseRequest(
closeBehavior = uiSettings.desktopCloseBehavior,
onExit = requestExit,
)
}.onFailure {
logger.error(it) { "Failed to handle close request from custom window frame" }
}
},
) {
MainWindowContent(navigator)
Expand All @@ -545,6 +573,15 @@ object AniDesktop {
@Composable
private fun FrameWindowScope.MainWindowContent(aniNavigator: AniNavigator) {
AniApp {
var zoomScale by remember { mutableStateOf(1f) }
val baseDensity = LocalDensity.current
val windowState = LocalWindowState.current
val zoomedDensity = remember(baseDensity, zoomScale) {
Density(
density = baseDensity.density * zoomScale,
fontScale = baseDensity.fontScale * zoomScale,
)
}
val themeSettings = LocalThemeSettings.current
val titleBarThemeController = LocalTitleBarThemeController.current
val systemTheme = LocalSystemTheme.current
Expand All @@ -562,6 +599,38 @@ private fun FrameWindowScope.MainWindowContent(aniNavigator: AniNavigator) {
onDispose {}
}

DisposableEffect(window) {
val mouseWheelListener = MouseWheelListener { event ->
if (!event.isControlDown) return@MouseWheelListener
zoomScale = zoomScale.adjustAppZoom(
direction = if (event.wheelRotation < 0) 1 else -1,
isFullscreen = windowState.placement == WindowPlacement.Fullscreen,
)
event.consume()
}
val keyEventDispatcher = KeyEventDispatcher { event ->
if (event.id != AwtKeyEvent.KEY_PRESSED || !event.isControlDown) {
return@KeyEventDispatcher false
}
val direction = when (event.keyCode) {
AwtKeyEvent.VK_EQUALS, AwtKeyEvent.VK_ADD -> 1
AwtKeyEvent.VK_MINUS, AwtKeyEvent.VK_SUBTRACT -> -1
else -> return@KeyEventDispatcher false
}
zoomScale = zoomScale.adjustAppZoom(
direction = direction,
isFullscreen = windowState.placement == WindowPlacement.Fullscreen,
)
true
}
window.addMouseWheelListener(mouseWheelListener)
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(keyEventDispatcher)
onDispose {
window.removeMouseWheelListener(mouseWheelListener)
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(keyEventDispatcher)
}
}

OverrideCaptionButtonAppearance(isDark = isTitleBarDark)

Box(
Expand All @@ -580,6 +649,7 @@ private fun FrameWindowScope.MainWindowContent(aniNavigator: AniNavigator) {
val content by vm.content.collectAsStateWithLifecycle()

CompositionLocalProvider(
LocalDensity provides zoomedDensity,
LocalNavigator provides aniNavigator,
LocalToaster provides remember(vm) {
object : Toaster {
Expand All @@ -600,6 +670,13 @@ private fun FrameWindowScope.MainWindowContent(aniNavigator: AniNavigator) {
}
}

private fun Float.adjustAppZoom(direction: Int, isFullscreen: Boolean): Float {
val minScale = if (isFullscreen) APP_ZOOM_MIN else 1f
val scaled = (this + direction * APP_ZOOM_STEP)
.coerceIn(minScale, APP_ZOOM_MAX)
return (scaled * 10).roundToInt() / 10f
}

@Composable
private fun WindowStateRecorder(
windowState: WindowState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ constructor(
* @since 4.1
*/
val fastSelectWebKind: Boolean = true,
/**
* Hide currently failed web sources in the simple source selector.
* @since 4.2
*/
val hideDeadWebSources: Boolean = false,
/**
* 给 low tier 源加载的宽容时间, 在这个时间内只接受 low tier 加载完成,
* 就算 high tier 比 low tier 率先加载完成也不选择.
Expand Down Expand Up @@ -81,4 +86,4 @@ constructor(
hideSingleEpisodeForCompleted = false,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
import me.him188.ani.app.data.models.schedule.AnimeSeasonId
import me.him188.ani.app.data.models.schedule.yearMonths
import me.him188.ani.app.data.network.AniSubjectSearchService
import me.him188.ani.app.data.network.BangumiSearchFilters
import me.him188.ani.app.data.network.BangumiSubjectSearchService
Expand Down Expand Up @@ -131,17 +130,23 @@ class SubjectSearchRepository(
private fun SubjectSearchQuery.toBangumiSearchFilters(): BangumiSearchFilters {
return BangumiSearchFilters(
tags,
airDates = season?.toBangumiAirDates(),
airDates = season?.toBangumiAirDates() ?: year?.toBangumiAirDates(),
ratings = rating?.toBangumiRatings(),
nsfw = nsfw,
)
}

private fun AnimeSeasonId.toBangumiAirDates(): List<String> {
val (begin, _, end) = this.yearMonths
return listOf(
">=${begin.first}-${begin.second}-01",
"<${end.first}-${end.second}-31",
">=${year}-${season.searchStartMonth()}-01",
"<${season.nextSeasonStartYear(year)}-${season.nextSeasonStartMonth()}-01",
)
}

private fun Int.toBangumiAirDates(): List<String> {
return listOf(
">=$this-01-01",
"<${this + 1}-01-01",
)
}

Expand All @@ -153,6 +158,31 @@ class SubjectSearchRepository(
)
}

private fun me.him188.ani.app.data.models.schedule.AnimeSeason.searchStartMonth(): String {
return when (this) {
me.him188.ani.app.data.models.schedule.AnimeSeason.WINTER -> "01"
me.him188.ani.app.data.models.schedule.AnimeSeason.SPRING -> "04"
me.him188.ani.app.data.models.schedule.AnimeSeason.SUMMER -> "07"
me.him188.ani.app.data.models.schedule.AnimeSeason.AUTUMN -> "10"
}
}

private fun me.him188.ani.app.data.models.schedule.AnimeSeason.nextSeasonStartMonth(): String {
return when (this) {
me.him188.ani.app.data.models.schedule.AnimeSeason.WINTER -> "04"
me.him188.ani.app.data.models.schedule.AnimeSeason.SPRING -> "07"
me.him188.ani.app.data.models.schedule.AnimeSeason.SUMMER -> "10"
me.him188.ani.app.data.models.schedule.AnimeSeason.AUTUMN -> "01"
}
}

private fun me.him188.ani.app.data.models.schedule.AnimeSeason.nextSeasonStartYear(year: Int): Int {
return when (this) {
me.him188.ani.app.data.models.schedule.AnimeSeason.AUTUMN -> year + 1
else -> year
}
}

/**
* 将数据过滤从View提升到分页层,不然会导致 #2380
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ data class SubjectSearchQuery(
val type: SubjectType = SubjectType.ANIME,
// val useOldSearchApi: Boolean = true,
val tags: List<String>? = null,
val year: Int? = null,
val season: AnimeSeasonId? = null,
val rating: RatingRange? = null,
// val rank: Pair<String?, String?> = Pair(null, null),
Expand All @@ -27,7 +28,7 @@ data class SubjectSearchQuery(
}

fun hasFilters(): Boolean {
return tags != null || season != null || rating != null || nsfw != null || sort != SearchSort.MATCH
return tags != null || year != null || season != null || rating != null || nsfw != null || sort != SearchSort.MATCH
}

fun hasSearchRequest(): Boolean {
Expand Down
3 changes: 3 additions & 0 deletions app/shared/src/commonMain/kotlin/ui/main/SearchViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ class SearchViewModel(
put("has_query", updatedQuery.keywords.isNotEmpty())
put("tags", updatedQuery.tags.orEmpty().joinToString(","))
put("tag_count", updatedQuery.tags.orEmpty().size)
put("year", updatedQuery.year)
put("season", updatedQuery.season?.id)
}
}
refreshSearch(updatedQuery)
Expand Down Expand Up @@ -318,6 +320,7 @@ class SearchViewModel(
private fun SubjectSearchQuery.shouldTriggerSearch(): Boolean {
return keywords.isNotEmpty() ||
!tags.isNullOrEmpty() ||
year != null ||
season != null ||
rating != null ||
nsfw != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ import me.him188.ani.app.videoplayer.ui.progress.AudioSwitcher
import me.him188.ani.app.videoplayer.ui.progress.MediaProgressIndicatorText
import me.him188.ani.app.videoplayer.ui.progress.PlayerControllerBar
import me.him188.ani.app.videoplayer.ui.progress.PlayerControllerDefaults
import me.him188.ani.app.videoplayer.ui.progress.PlayerControllerDefaults.SpeedSwitcher
import me.him188.ani.app.videoplayer.ui.progress.PlayerControllerDefaults.VideoAspectRatioSelector
import me.him188.ani.app.videoplayer.ui.progress.PlaybackSpeedSwitcher
import me.him188.ani.app.videoplayer.ui.progress.PlayerProgressSliderState
import me.him188.ani.app.videoplayer.ui.progress.SubtitleSwitcher
import me.him188.ani.app.videoplayer.ui.progress.rememberMediaProgressSliderState
Expand Down Expand Up @@ -446,7 +446,7 @@ internal fun EpisodeVideoImpl(
val playbackSpeedAlwaysOnRequester =
rememberAlwaysOnRequester(playerControllerState, "speedSwitcher")
playbackSpeedControllerState?.also { controller ->
SpeedSwitcher(controller) {
PlaybackSpeedSwitcher(controller) {
if (it) {
playbackSpeedAlwaysOnRequester.request()
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ class EpisodeViewModel(
getPreferredWebMediaSource(subjectId),
backgroundScope,
webCaptchaCoordinator,
settingsRepository.mediaSelectorSettings,
)
} else {
// TODO: 2025/1/22 We should not use createTestMediaSelectorState
Expand Down
Loading
Loading