From b81b6844324d1cfa728cd3005222971fbbe4759f Mon Sep 17 00:00:00 2001 From: Tommy-Geenexus Date: Wed, 23 Oct 2024 16:18:50 +0200 Subject: [PATCH] feat: support FiiO KA13 --- app/config/detekt/baseline.xml | 3 + .../control/business/ControlViewModel.kt | 2 +- .../control/domain/GetCurrentStateUseCase.kt | 6 + .../control/domain/GetVolumeLevelUseCase.kt | 6 + .../control/domain/SetDacFilterUseCase.kt | 16 +- .../domain/SetIndicatorStateUseCase.kt | 14 +- .../control/domain/SetProfileUseCase.kt | 32 +- .../domain/SetSpdifOutEnabledUseCase.kt | 9 + .../control/domain/SetVolumeLevelUseCase.kt | 17 +- .../control/ui/ControlScreen.kt | 22 ++ .../core/data/UsbRepository.kt | 16 +- .../core/dongle/SupportedDongles.kt | 2 + .../core/dongle/fiio/ka13/FiioKa13.kt | 121 +++++++ .../dongle/fiio/ka13/FiioKa13UsbCommand.kt | 29 ++ .../core/dongle/fiio/ka13/feature/Filter.kt | 58 ++++ .../fiio/ka13/feature/FirmwareVersion.kt | 37 +++ .../fiio/ka13/feature/IndicatorState.kt | 54 +++ .../dongle/fiio/ka13/feature/SampleRate.kt | 71 ++++ .../dongle/fiio/ka13/feature/VolumeLevel.kt | 48 +++ .../core/dongle/fiio/ka5/FiioKa5.kt | 60 ++-- .../core/dongle/moondrop/dawn/MoondropDawn.kt | 21 +- .../dongle/moondrop/dawn/MoondropDawn35.kt | 4 +- .../dongle/moondrop/dawn/MoondropDawn44.kt | 4 +- .../dongle/moondrop/dawn/MoondropDawnPro.kt | 4 +- .../UsbDeviceConnectionExtensions.kt | 32 ++ .../core/volume/HardwareVolumeControl.kt | 10 +- .../fiio/ka13/data/FiioKa13UsbRepository.kt | 313 ++++++++++++++++++ .../dongle/fiio/ka13/ui/FiioKa13Items.kt | 105 ++++++ .../dongle/fiio/ka13/ui/FiioKa13Previews.kt | 64 ++++ .../dongle/fiio/ka13/ui/ItemInfo.kt | 86 +++++ .../dongle/fiio/ka13/ui/ItemMisc.kt | 69 ++++ .../fiio/ka5/data/FiioKa5UsbRepository.kt | 30 +- .../dawn/data/MoondropDawnUsbRepository.kt | 12 +- .../dongle/moondrop/dawn/ui/ItemAudio.kt | 7 +- app/src/main/res/xml/device_filter.xml | 2 + 35 files changed, 1303 insertions(+), 83 deletions(-) create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/FiioKa13.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/FiioKa13UsbCommand.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/Filter.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/FirmwareVersion.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/IndicatorState.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/SampleRate.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/VolumeLevel.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/extension/UsbDeviceConnectionExtensions.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/data/FiioKa13UsbRepository.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/FiioKa13Items.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/FiioKa13Previews.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/ItemInfo.kt create mode 100644 app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/ItemMisc.kt diff --git a/app/config/detekt/baseline.xml b/app/config/detekt/baseline.xml index 11e4674..1f21eee 100644 --- a/app/config/detekt/baseline.xml +++ b/app/config/detekt/baseline.xml @@ -6,9 +6,12 @@ CyclomaticComplexMethod:ControlScreen.kt$@Composable fun ControlScreen( windowSizeClass: WindowSizeClass, viewModel: ControlViewModel, onNavigateUp: () -> Unit ) EmptyFunctionBlock:ItemAudio.kt$<no name provided>${ } EmptyFunctionBlock:ItemDisplay.kt$<no name provided>${ } + ForbiddenComment:FiioKa13UsbRepository.kt$FiioKa13UsbRepository$// FIXME: Reading current state is not working properly (firmware issue?) + ForbiddenComment:FiioKa13UsbRepository.kt$FiioKa13UsbRepository$// TODO: Is it actually working? LargeClass:FiioKa5UsbRepository.kt$FiioKa5UsbRepository : UsbRepository LongMethod:ControlScreen.kt$@Composable fun ControlScreen( modifier: Modifier = Modifier, windowSizeClass: WindowSizeClass = WindowSizeClass.calculateFromSize(DpSize.Zero), scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(), profileListState: LazyStaggeredGridState = rememberLazyStaggeredGridState(), snackBarHostState: SnackbarHostState = remember { SnackbarHostState() }, profiles: LazyPagingItems<Profile> = UnsupportedUsbDongle.profileFlow().collectAsLazyPagingItems(), usbDongle: UsbDongle = UnsupportedUsbDongle, isLoading: Boolean = false, onRefresh: () -> Unit = {}, onReset: () -> Unit = {}, onProfileShortcutAdd: (Profile) -> Unit = {}, onProfileShortcutRemove: (Profile) -> Unit = {}, onProfileDelete: (Profile) -> Unit = { _ -> }, onProfileApply: (Profile) -> Unit = { _ -> }, onProfileExport: (String) -> Unit = { _ -> }, onChannelBalanceSelected: (Int) -> Unit = { _ -> }, onDacModeSelected: (Byte) -> Unit = { _ -> }, onDisplayBrightnessSelected: (Int) -> Unit = { _ -> }, onDisplayTimeoutSelected: (Int) -> Unit = { _ -> }, onDisplayInvertChange: (Boolean) -> Unit = { _ -> }, onFilterSelected: (Byte) -> Unit = { _ -> }, onGainSelected: (Byte) -> Unit = { _ -> }, onHardwareMuteEnabledSelected: (Boolean) -> Unit = { _ -> }, onHidModeSelected: (Byte) -> Unit = { _ -> }, onIndicatorStateSelected: (Byte) -> Unit = { _ -> }, onSpdifOutEnabledSelected: (Boolean) -> Unit = { _ -> }, onVolumeLevelSelected: (Int) -> Unit = { _ -> }, onVolumeModeSelected: (Byte) -> Unit = { _ -> } ) LongMethod:ControlScreen.kt$@Composable fun ControlScreen( windowSizeClass: WindowSizeClass, viewModel: ControlViewModel, onNavigateUp: () -> Unit ) + LongMethod:FiioKa13UsbRepository.kt$FiioKa13UsbRepository$suspend fun setVolumeLevel(fiioKa13: FiioKa13, volumeLevel: VolumeLevel): Result<FiioKa13> LongMethod:FiioKa5Items.kt$@Composable fun FiioKa5Items( modifier: Modifier = Modifier, fiioKa5: FiioKa5 = FiioKa5(), onChannelBalanceSelected: (Int) -> Unit = {}, onVolumeLevelSelected: (Int) -> Unit = {}, onVolumeModeSelected: (Byte) -> Unit = {}, onDisplayBrightnessSelected: (Int) -> Unit = {}, onDisplayTimeoutSelected: (Int) -> Unit = {}, onDisplayInvertSelected: (Boolean) -> Unit = {}, onGainSelected: (Byte) -> Unit = {}, onFilterSelected: (Byte) -> Unit = {}, onSpdifOutSelected: (Boolean) -> Unit = {}, onHardwareMuteSelected: (Boolean) -> Unit = {}, onDacModeSelected: (Byte) -> Unit = {}, onHidModeSelected: (Byte) -> Unit = {} ) LongMethod:FiioKa5UsbRepository.kt$FiioKa5UsbRepository$suspend fun getCurrentState(usbDongle: FiioKa5): Result<FiioKa5> LongMethod:FiioKa5UsbRepository.kt$FiioKa5UsbRepository$suspend fun setAll( fiioKa5: FiioKa5, channelBalance: ChannelBalance, dacMode: DacMode, displayBrightness: DisplayBrightness, displayInvert: DisplayInvert, displayTimeout: DisplayTimeout, filter: Filter, gain: Gain, hardwareMute: HardwareMute, hidMode: HidMode, spdifOut: SpdifOut, volumeLevel: VolumeLevel, volumeMode: VolumeMode ): Result<FiioKa5> diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/business/ControlViewModel.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/business/ControlViewModel.kt index 21cc103..c684348 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/business/ControlViewModel.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/business/ControlViewModel.kt @@ -25,7 +25,6 @@ import androidx.lifecycle.ViewModel import androidx.paging.Pager import androidx.paging.PagingConfig import dagger.hilt.android.lifecycle.HiltViewModel -import io.github.tommygeenexus.usbdonglecontrol.core.db.Profile import io.github.tommygeenexus.usbdonglecontrol.control.data.ProfileRepository import io.github.tommygeenexus.usbdonglecontrol.control.domain.GetCurrentStateUseCase import io.github.tommygeenexus.usbdonglecontrol.control.domain.GetVolumeLevelUseCase @@ -44,6 +43,7 @@ import io.github.tommygeenexus.usbdonglecontrol.control.domain.SetSpdifOutEnable import io.github.tommygeenexus.usbdonglecontrol.control.domain.SetVolumeLevelUseCase import io.github.tommygeenexus.usbdonglecontrol.control.domain.SetVolumeModeUseCase import io.github.tommygeenexus.usbdonglecontrol.core.data.UsbRepository +import io.github.tommygeenexus.usbdonglecontrol.core.db.Profile import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UnsupportedUsbDongle import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UsbDongle import io.github.tommygeenexus.usbdonglecontrol.core.volume.HardwareVolumeControl diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/GetCurrentStateUseCase.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/GetCurrentStateUseCase.kt index b22ae6d..ac399b0 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/GetCurrentStateUseCase.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/GetCurrentStateUseCase.kt @@ -22,8 +22,10 @@ package io.github.tommygeenexus.usbdonglecontrol.control.domain import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UnsupportedUsbDongleException import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UsbDongle +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.FiioKa5 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.MoondropDawn +import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.data.FiioKa13UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka5.data.FiioKa5UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.moondrop.dawn.data.MoondropDawnUsbRepository import javax.inject.Inject @@ -31,11 +33,15 @@ import javax.inject.Singleton @Singleton class GetCurrentStateUseCase @Inject constructor( + private val fiioKa13UsbRepository: FiioKa13UsbRepository, private val fiioKa5UsbRepository: FiioKa5UsbRepository, private val moondropDawnUsbRepository: MoondropDawnUsbRepository ) { suspend operator fun invoke(usbDongle: UsbDongle): Result = when (usbDongle) { + is FiioKa13 -> { + fiioKa13UsbRepository.getCurrentState(usbDongle) + } is FiioKa5 -> { fiioKa5UsbRepository.getCurrentState(usbDongle) } diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/GetVolumeLevelUseCase.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/GetVolumeLevelUseCase.kt index dc497ec..877ca94 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/GetVolumeLevelUseCase.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/GetVolumeLevelUseCase.kt @@ -22,8 +22,10 @@ package io.github.tommygeenexus.usbdonglecontrol.control.domain import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UnsupportedUsbDongleException import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UsbDongle +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.FiioKa5 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.MoondropDawn +import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.data.FiioKa13UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka5.data.FiioKa5UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.moondrop.dawn.data.MoondropDawnUsbRepository import javax.inject.Inject @@ -31,11 +33,15 @@ import javax.inject.Singleton @Singleton class GetVolumeLevelUseCase @Inject constructor( + private val fiioKa13UsbRepository: FiioKa13UsbRepository, private val fiioKa5UsbRepository: FiioKa5UsbRepository, private val moondropDawnUsbRepository: MoondropDawnUsbRepository ) { suspend operator fun invoke(usbDongle: UsbDongle): Result = when (usbDongle) { + is FiioKa13 -> { + fiioKa13UsbRepository.getCurrentState(usbDongle) + } is FiioKa5 -> { fiioKa5UsbRepository.getVolumeLevelAndMode(usbDongle) } diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetDacFilterUseCase.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetDacFilterUseCase.kt index 82697ce..cc1d878 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetDacFilterUseCase.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetDacFilterUseCase.kt @@ -22,9 +22,12 @@ package io.github.tommygeenexus.usbdonglecontrol.control.domain import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UnsupportedUsbDongleException import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UsbDongle +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.Filter as FilterFiioKa13 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.FiioKa5 -import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.Filter +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.Filter as FilterFiioKa5 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.MoondropDawn +import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.data.FiioKa13UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka5.data.FiioKa5UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.moondrop.dawn.data.MoondropDawnUsbRepository import javax.inject.Inject @@ -32,22 +35,29 @@ import javax.inject.Singleton @Singleton class SetDacFilterUseCase @Inject constructor( + private val fiioKa13UsbRepository: FiioKa13UsbRepository, private val fiioKa5UsbRepository: FiioKa5UsbRepository, private val moondropDawnUsbRepository: MoondropDawnUsbRepository ) { suspend operator fun invoke(usbDongle: UsbDongle, id: Byte): Result = when (usbDongle) { + is FiioKa13 -> { + fiioKa13UsbRepository.setFilter( + fiioKa13 = usbDongle, + filter = FilterFiioKa13.findByIdOrDefault(id) + ) + } is FiioKa5 -> { fiioKa5UsbRepository.setFilter( fiioKa5 = usbDongle, - filter = Filter.findByIdOrDefault(id) + filter = FilterFiioKa5.findByIdOrDefault(id) ) } is MoondropDawn -> { moondropDawnUsbRepository.setFilter( moondropDawn = usbDongle, - filter = Filter.findByIdOrDefault(id) + filter = FilterFiioKa5.findByIdOrDefault(id) ) } else -> Result.failure(UnsupportedUsbDongleException()) diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetIndicatorStateUseCase.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetIndicatorStateUseCase.kt index a2b3909..5ba1bac 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetIndicatorStateUseCase.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetIndicatorStateUseCase.kt @@ -22,23 +22,33 @@ package io.github.tommygeenexus.usbdonglecontrol.control.domain import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UnsupportedUsbDongleException import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UsbDongle +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.IndicatorState as IndicatorStateFiioKa13 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.MoondropDawn -import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.feature.IndicatorState +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.feature.IndicatorState as IndicatorStateMoondropDawn +import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.data.FiioKa13UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.moondrop.dawn.data.MoondropDawnUsbRepository import javax.inject.Inject import javax.inject.Singleton @Singleton class SetIndicatorStateUseCase @Inject constructor( + private val fiioKa13UsbRepository: FiioKa13UsbRepository, private val moondropDawnUsbRepository: MoondropDawnUsbRepository ) { suspend operator fun invoke(usbDongle: UsbDongle, id: Byte): Result = when (usbDongle) { + is FiioKa13 -> { + fiioKa13UsbRepository.setIndicatorState( + fiioKa13 = usbDongle, + indicatorState = IndicatorStateFiioKa13.findByIdOrDefault(id) + ) + } is MoondropDawn -> { moondropDawnUsbRepository.setIndicatorState( moondropDawn = usbDongle, - indicatorState = IndicatorState.findByIdOrDefault(id) + indicatorState = IndicatorStateMoondropDawn.findByIdOrDefault(id) ) } else -> Result.failure(UnsupportedUsbDongleException()) diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetProfileUseCase.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetProfileUseCase.kt index 142f0b8..599ec65 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetProfileUseCase.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetProfileUseCase.kt @@ -23,13 +23,18 @@ package io.github.tommygeenexus.usbdonglecontrol.control.domain import io.github.tommygeenexus.usbdonglecontrol.core.db.Profile import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UnsupportedUsbDongleException import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UsbDongle +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.Filter as FilterFiioKa13 +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.IndicatorState as IndicatorStateFiioKa13 +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.VolumeLevel as VolumeLevelFiioKa13 +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.createFromDisplayValue import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.FiioKa5 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.ChannelBalance import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.DacMode import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.DisplayBrightness import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.DisplayInvert import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.DisplayTimeout -import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.Filter +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.Filter as FilterFiioKa5 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.Gain import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.HardwareMute import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.HidMode @@ -38,9 +43,10 @@ import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.Vol import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.VolumeMode import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.createFromDisplayValue import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.MoondropDawn -import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.feature.IndicatorState -import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.feature.VolumeLevel +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.feature.IndicatorState as IndicatorStateMoondropDawn +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.feature.VolumeLevel as VolumeLevelMoondropDawn import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.feature.createFromDisplayValue +import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.data.FiioKa13UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka5.data.FiioKa5UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.moondrop.dawn.data.MoondropDawnUsbRepository import javax.inject.Inject @@ -48,12 +54,24 @@ import javax.inject.Singleton @Singleton class SetProfileUseCase @Inject constructor( + private val fiioKa13UsbRepository: FiioKa13UsbRepository, private val fiioKa5UsbRepository: FiioKa5UsbRepository, private val moondropDawnUsbRepository: MoondropDawnUsbRepository ) { suspend operator fun invoke(usbDongle: UsbDongle, profile: Profile): Result = when (usbDongle) { + is FiioKa13 -> { + fiioKa13UsbRepository.setAll( + fiioKa13 = usbDongle, + filter = FilterFiioKa13.findByIdOrDefault(profile.filterId), + indicatorState = IndicatorStateFiioKa13.findByIdOrDefault( + profile.indicatorStateId + ), + spdifOut = SpdifOut(isEnabled = profile.isSpdifOutEnabled), + volumeLevel = VolumeLevelFiioKa13.createFromDisplayValue(profile.volumeLevel) + ) + } is FiioKa5 -> { fiioKa5UsbRepository.setAll( fiioKa5 = usbDongle, @@ -68,7 +86,7 @@ class SetProfileUseCase @Inject constructor( displayTimeout = DisplayTimeout.createFromDisplayValue( profile.displayTimeout ), - filter = Filter.findByIdOrDefault(id = profile.filterId), + filter = FilterFiioKa5.findByIdOrDefault(id = profile.filterId), gain = Gain.findByIdOrDefault(id = profile.gainId), hardwareMute = HardwareMute(isEnabled = profile.isHardwareMuteEnabled), hidMode = HidMode.findByIdOrDefault(id = profile.hidModeId), @@ -85,12 +103,12 @@ class SetProfileUseCase @Inject constructor( is MoondropDawn -> { moondropDawnUsbRepository.setAll( moondropDawn = usbDongle, - filter = Filter.findByIdOrDefault(id = profile.filterId), + filter = FilterFiioKa5.findByIdOrDefault(id = profile.filterId), gain = Gain.findByIdOrDefault(id = profile.gainId), - indicatorState = IndicatorState.findByIdOrDefault( + indicatorState = IndicatorStateMoondropDawn.findByIdOrDefault( id = profile.indicatorStateId ), - volumeLevel = VolumeLevel.createFromDisplayValue( + volumeLevel = VolumeLevelMoondropDawn.createFromDisplayValue( displayValue = profile.volumeLevel ) ) diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetSpdifOutEnabledUseCase.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetSpdifOutEnabledUseCase.kt index 0e9f5f3..dce90bb 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetSpdifOutEnabledUseCase.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetSpdifOutEnabledUseCase.kt @@ -22,14 +22,17 @@ package io.github.tommygeenexus.usbdonglecontrol.control.domain import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UnsupportedUsbDongleException import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UsbDongle +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.FiioKa5 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.SpdifOut +import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.data.FiioKa13UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka5.data.FiioKa5UsbRepository import javax.inject.Inject import javax.inject.Singleton @Singleton class SetSpdifOutEnabledUseCase @Inject constructor( + private val fiioKa13UsbRepository: FiioKa13UsbRepository, private val fiioKa5UsbRepository: FiioKa5UsbRepository ) { @@ -37,6 +40,12 @@ class SetSpdifOutEnabledUseCase @Inject constructor( usbDongle: UsbDongle, isSpdifOutEnabled: Boolean ): Result = when (usbDongle) { + is FiioKa13 -> { + fiioKa13UsbRepository.setSpdifOut( + fiioKa13 = usbDongle, + spdifOut = SpdifOut(isEnabled = isSpdifOutEnabled) + ) + } is FiioKa5 -> { fiioKa5UsbRepository.setSpdifOut( fiioKa5 = usbDongle, diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetVolumeLevelUseCase.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetVolumeLevelUseCase.kt index 1632ac7..040e68b 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetVolumeLevelUseCase.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/domain/SetVolumeLevelUseCase.kt @@ -22,12 +22,16 @@ package io.github.tommygeenexus.usbdonglecontrol.control.domain import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UnsupportedUsbDongleException import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UsbDongle +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.VolumeLevel as VolumeLevelFiioKa13 +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.createFromDisplayValue import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.FiioKa5 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.VolumeLevel as VolumeLevelFiioKa5 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.createFromDisplayValue import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.MoondropDawn -import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.feature.VolumeLevel +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.feature.VolumeLevel as VolumeLevelMoondropDawn import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.feature.createFromDisplayValue +import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.data.FiioKa13UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka5.data.FiioKa5UsbRepository import io.github.tommygeenexus.usbdonglecontrol.dongle.moondrop.dawn.data.MoondropDawnUsbRepository import javax.inject.Inject @@ -35,12 +39,21 @@ import javax.inject.Singleton @Singleton class SetVolumeLevelUseCase @Inject constructor( + private val fiioKa13UsbRepository: FiioKa13UsbRepository, private val fiioKa5UsbRepository: FiioKa5UsbRepository, private val moondropDawnUsbRepository: MoondropDawnUsbRepository ) { suspend operator fun invoke(usbDongle: UsbDongle, volumeLevel: Int): Result = when (usbDongle) { + is FiioKa13 -> { + fiioKa13UsbRepository.setVolumeLevel( + fiioKa13 = usbDongle, + volumeLevel = VolumeLevelFiioKa13.createFromDisplayValue( + displayValue = volumeLevel + ) + ) + } is FiioKa5 -> { fiioKa5UsbRepository.setVolumeLevel( fiioKa5 = usbDongle, @@ -53,7 +66,7 @@ class SetVolumeLevelUseCase @Inject constructor( is MoondropDawn -> { moondropDawnUsbRepository.setVolumeLevel( moondropDawn = usbDongle, - volumeLevel = VolumeLevel.createFromDisplayValue( + volumeLevel = VolumeLevelMoondropDawn.createFromDisplayValue( displayValue = volumeLevel ) ) diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/ui/ControlScreen.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/ui/ControlScreen.kt index 5047b9e..f5b70fd 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/ui/ControlScreen.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/control/ui/ControlScreen.kt @@ -83,6 +83,7 @@ import io.github.tommygeenexus.usbdonglecontrol.core.control.ControlTabs import io.github.tommygeenexus.usbdonglecontrol.core.db.Profile import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UnsupportedUsbDongle import io.github.tommygeenexus.usbdonglecontrol.core.dongle.UsbDongle +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.FiioKa5 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.MoondropDawn import io.github.tommygeenexus.usbdonglecontrol.core.dongle.productName @@ -90,6 +91,7 @@ import io.github.tommygeenexus.usbdonglecontrol.core.dongle.profileFlow import io.github.tommygeenexus.usbdonglecontrol.core.extension.consumeProfileShortcut import io.github.tommygeenexus.usbdonglecontrol.core.receiver.UsbDeviceAttachDetachPermissionReceiver import io.github.tommygeenexus.usbdonglecontrol.core.receiver.UsbServiceVolumeLevelChangedReceiver +import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.ui.FiioKa13Items import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka5.ui.FiioKa5Items import io.github.tommygeenexus.usbdonglecontrol.dongle.moondrop.dawn.ui.MoondropDawnItems import io.github.tommygeenexus.usbdonglecontrol.theme.getHorizontalPadding @@ -477,6 +479,26 @@ fun ControlScreen( ) if (selectedTabIndex == ControlTabs.State.index) { when (usbDongle) { + is FiioKa13 -> { + FiioKa13Items( + modifier = Modifier.padding( + horizontal = windowSizeClass.getHorizontalPadding() + ), + fiioKa13 = usbDongle, + onFilterSelected = { filterId -> + onFilterSelected(filterId) + }, + onIndicatorStateSelected = { indicatorStateId -> + onIndicatorStateSelected(indicatorStateId) + }, + onSpdifOutSelected = { isSpdifOutEnabled -> + onSpdifOutEnabledSelected(isSpdifOutEnabled) + }, + onVolumeLevelSelected = { volumeLevel -> + onVolumeLevelSelected(volumeLevel) + } + ) + } is FiioKa5 -> { FiioKa5Items( modifier = Modifier.padding( diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/data/UsbRepository.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/data/UsbRepository.kt index 5916fc9..2204198 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/data/UsbRepository.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/data/UsbRepository.kt @@ -96,11 +96,11 @@ open class UsbRepository @Inject constructor( } @Throws(UnsupportedUsbDongleException::class, IllegalStateException::class) - suspend fun openFirstAttachedUsbDongleOrThrow(): UsbDeviceConnection = + suspend fun openFirstAttachedUsbDongleOrThrow(): Pair = withContext(dispatcherIo) { val (usbDevice, _, _) = getFirstAttachedUsbDongle().getOrThrow() val manager = context.getSystemService(Context.USB_SERVICE) as UsbManager - manager.openDevice(usbDevice).also { connection -> + usbDevice to manager.openDevice(usbDevice).also { connection -> if (connection == null) { error("manager.openDevice() failed") } @@ -170,16 +170,20 @@ open class UsbRepository @Inject constructor( @Throws(IllegalStateException::class) suspend fun UsbDeviceConnection.controlWrite( + requestType: Int = REQUEST_TYPE_WRITE, + requestId: Int = REQUEST_ID_WRITE, + requestValue: Int = REQUEST_VALUE, + requestIndex: Int = REQUEST_INDEX, payload: ByteArray, payloadSize: Int, transferTimeout: Int, delayInMillisecondsAfterTransfer: Long ) = withContext(dispatcherIo) { val result = controlTransfer( - REQUEST_TYPE_WRITE, - REQUEST_ID_WRITE, - REQUEST_VALUE, - REQUEST_INDEX, + requestType, + requestId, + requestValue, + requestIndex, payload, payloadSize, transferTimeout diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/SupportedDongles.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/SupportedDongles.kt index 937f13a..65fbdfa 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/SupportedDongles.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/SupportedDongles.kt @@ -21,12 +21,14 @@ package io.github.tommygeenexus.usbdonglecontrol.core.dongle import android.hardware.usb.UsbDevice +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.FiioKa5 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.MoondropDawn35 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.MoondropDawn44 import io.github.tommygeenexus.usbdonglecontrol.core.dongle.moondrop.dawn.MoondropDawnPro val supportedDongles = listOf( + FiioKa13(), FiioKa5(), MoondropDawn35(), MoondropDawn44(), diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/FiioKa13.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/FiioKa13.kt new file mode 100644 index 0000000..88f7e06 --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/FiioKa13.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13 + +import io.github.tommygeenexus.usbdonglecontrol.core.db.Profile +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.FiioUsbDongle +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.Filter +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.FirmwareVersion +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.IndicatorState +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.SampleRate +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.VolumeLevel +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.default +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.displayValueToPercent +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.SpdifOut +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.default +import io.github.tommygeenexus.usbdonglecontrol.core.volume.HardwareVolumeControl +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize + +@Parcelize +data class FiioKa13( + val filter: Filter = Filter.default(), + val firmwareVersion: FirmwareVersion = FirmwareVersion.default(), + val indicatorState: IndicatorState = IndicatorState.default(), + val sampleRate: SampleRate = SampleRate.default(), + val spdifOut: SpdifOut = SpdifOut.default(), + val volumeLevel: VolumeLevel = VolumeLevel.default() +) : FiioUsbDongle( + modelName = MODEL_NAME, + productId = PRODUCT_ID +), + HardwareVolumeControl, + FiioKa13UsbCommand { + + companion object { + const val MODEL_NAME = "KA13" + const val PRODUCT_ID = 98 + } + + @IgnoredOnParcel + override val setFilter + get() = listOf( + byteArrayOf(0, 17, -96, -94, 2, 2, 1, 7, 0, 0, 0, 0, 0, 0, 0, 0), + byteArrayOf(9, 17, -128, 96, 0, 0, 5, 9, 0, 0, 1, 0, 0, 0, 0, 0) + ) + + @IgnoredOnParcel + override val setIndicatorState + get() = byteArrayOf(8, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + + @IgnoredOnParcel + override val setSpdifOut + get() = byteArrayOf(10, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + + @IgnoredOnParcel + override val setVolumeLevel + get() = listOf( + byteArrayOf(12, 17, -96, -94, 0, 16, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0), + byteArrayOf(13, 17, -96, -94, 0, 17, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0), + byteArrayOf(6, 17, 0, 98, 0, 0, 5, 9, 0, 2, 1, 0, 0, 0, 0, 0), + byteArrayOf(7, 17, 0, 102, 0, 0, 5, 9, 0, 2, 1, 0, 0, 0, 0, 0), + byteArrayOf(7, 17, 0, 98, 0, 0, 5, 9, 0, 1, 1, 0, 0, 0, 0, 0), + byteArrayOf(8, 17, 0, 102, 0, 0, 5, 9, 0, 1, 1, 0, 0, 0, 0, 0) + ) + + @IgnoredOnParcel + override val maxVolumeStepSize + get() = HardwareVolumeControl.VOLUME_STEP_SIZE_2 + + @IgnoredOnParcel + override val isVolumeControlAsc + get() = false + + override val currentVolumeLevel + get() = volumeLevel.displayValueAndPayload + + override val displayVolumeLevel: String + get() = volumeLevel.displayValueToPercent() + + override fun currentStateAsProfile(profileName: String) = Profile( + name = profileName, + vendorId = vendorId, + productId = productId, + filterId = filter.id, + firmwareVersion = firmwareVersion.displayValue, + indicatorStateId = indicatorState.id, + isSpdifOutEnabled = spdifOut.isEnabled, + sampleRate = sampleRate.displayValue, + volumeLevel = volumeLevel.displayValueAndPayload + ) + + override fun defaultStateAsProfile() = Profile( + name = "", + vendorId = vendorId, + productId = productId, + filterId = Filter.default().id, + firmwareVersion = FirmwareVersion.default().displayValue, + indicatorStateId = IndicatorState.default().id, + isSpdifOutEnabled = SpdifOut.default().isEnabled, + sampleRate = SampleRate.default().displayValue, + volumeLevel = VolumeLevel.default().displayValueAndPayload + ) +} diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/FiioKa13UsbCommand.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/FiioKa13UsbCommand.kt new file mode 100644 index 0000000..4b394cb --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/FiioKa13UsbCommand.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13 + +interface FiioKa13UsbCommand { + + val setFilter: List + val setIndicatorState: ByteArray + val setSpdifOut: ByteArray + val setVolumeLevel: List +} diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/Filter.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/Filter.kt new file mode 100644 index 0000000..21e0dfa --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/Filter.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature + +import android.os.Parcelable +import androidx.compose.runtime.Immutable +import kotlinx.parcelize.Parcelize + +@Immutable +sealed class Filter(val id: Byte, val payload: Byte) : Parcelable { + + companion object { + + fun default() = FastRollOffLowLatency + + fun findByIdOrDefault(id: Byte): Filter = when (id) { + FastRollOffLowLatency.id -> FastRollOffLowLatency + FastRollOffPhaseCompensated.id -> FastRollOffPhaseCompensated + SlowRollOffLowLatency.id -> SlowRollOffLowLatency + SlowRollOffPhaseCompensated.id -> SlowRollOffPhaseCompensated + NonOversampling.id -> NonOversampling + else -> default() + } + } + + @Parcelize + data object FastRollOffLowLatency : Filter(id = 0, payload = 2) + + @Parcelize + data object FastRollOffPhaseCompensated : Filter(id = 1, payload = 66) + + @Parcelize + data object SlowRollOffLowLatency : Filter(id = 2, payload = 130.toByte()) + + @Parcelize + data object SlowRollOffPhaseCompensated : Filter(id = 3, payload = 194.toByte()) + + @Parcelize + data object NonOversampling : Filter(id = 4, payload = 34) +} diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/FirmwareVersion.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/FirmwareVersion.kt new file mode 100644 index 0000000..5d4a1a9 --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/FirmwareVersion.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature + +import android.os.Parcelable +import androidx.compose.runtime.Immutable +import kotlinx.parcelize.Parcelize + +@Immutable +@Parcelize +data class FirmwareVersion(val displayValue: String) : Parcelable { + + companion object { + + const val DEFAULT = "0.00" + } +} + +fun FirmwareVersion.Companion.default() = FirmwareVersion(displayValue = DEFAULT) diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/IndicatorState.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/IndicatorState.kt new file mode 100644 index 0000000..416523b --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/IndicatorState.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature + +import android.os.Parcelable +import androidx.compose.runtime.Immutable +import kotlinx.parcelize.Parcelize + +@Immutable +sealed class IndicatorState(val id: Byte, val payload: ByteArray) : Parcelable { + + companion object { + + private val payloadIndicatorStateEnabled = byteArrayOf(-1, 0) + private val payloadIndicatorStateDisabledTemp = byteArrayOf(-2, 1) + private val payloadIndicatorStateDisabled = byteArrayOf(-3, 2) + + fun default() = Enabled + + fun findByIdOrDefault(id: Byte): IndicatorState = when (id) { + Enabled.id -> Enabled + DisabledTemp.id -> DisabledTemp + Disabled.id -> Disabled + else -> default() + } + } + + @Parcelize + data object Enabled : IndicatorState(id = 0, payload = payloadIndicatorStateEnabled) + + @Parcelize + data object DisabledTemp : IndicatorState(id = 1, payload = payloadIndicatorStateDisabledTemp) + + @Parcelize + data object Disabled : IndicatorState(id = 2, payload = payloadIndicatorStateDisabled) +} diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/SampleRate.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/SampleRate.kt new file mode 100644 index 0000000..6d6dce9 --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/SampleRate.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature + +import android.os.Parcelable +import androidx.compose.runtime.Immutable +import kotlinx.parcelize.Parcelize + +@Immutable +@Parcelize +data class SampleRate(val displayValue: String) : Parcelable { + + companion object { + + const val DEFAULT = "48kHz" + } +} + +fun SampleRate.Companion.create(key: Int) = SampleRate( + displayValue = sampleRates.getOrDefault( + key = key, + defaultValue = DEFAULT + ) +) + +fun SampleRate.Companion.default() = SampleRate(displayValue = DEFAULT) + +private val sampleRates = mapOf( + 0 to "44.1kHz", + 1 to "48kHz", + 2 to "88.2kHz", + 3 to "96kHz", + 4 to "176.4kHz", + 5 to "192kHz", + 6 to "352.8kHz", + 7 to "384kHz", + 8 to "705.6kHz", + 9 to "768kHz", + 16 to "DoP64", + 17 to "DoP64", + 18 to "DoP128", + 19 to "DoP128", + 20 to "DoP256", + 21 to "DoP256", + 32 to "Native64", + 33 to "Native64", + 34 to "Native128", + 35 to "Native128", + 36 to "Native256", + 37 to "Native256", + 38 to "Native512", + 39 to "Native512" +) diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/VolumeLevel.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/VolumeLevel.kt new file mode 100644 index 0000000..349592c --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka13/feature/VolumeLevel.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature + +import android.os.Parcelable +import androidx.compose.runtime.Immutable +import kotlinx.parcelize.Parcelize + +@Immutable +@Parcelize +data class VolumeLevel(val displayValueAndPayload: Int) : Parcelable { + + companion object { + const val MIN = 100 + const val MAX = 0 + const val DEFAULT = MAX + } +} + +fun VolumeLevel.Companion.createFromDisplayValue(displayValue: Int) = VolumeLevel( + displayValueAndPayload = displayValue.coerceIn( + minimumValue = MAX, + maximumValue = MIN + ) +) + +fun VolumeLevel.Companion.default() = VolumeLevel(displayValueAndPayload = DEFAULT) + +fun VolumeLevel.displayValueToPercent(): String = + "${(VolumeLevel.MIN - displayValueAndPayload) * 100 / VolumeLevel.MIN}%" diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka5/FiioKa5.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka5/FiioKa5.kt index 2b6f04e..f090c0b 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka5/FiioKa5.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/fiio/ka5/FiioKa5.kt @@ -59,72 +59,92 @@ data class FiioKa5( val volumeLevel: VolumeLevel = VolumeLevel.default(), val volumeMode: VolumeMode = VolumeMode.default() ) : FiioUsbDongle( - modelName = "KA5", + modelName = MODEL_NAME, productId = PRODUCT_ID ), HardwareVolumeControl, FiioKa5UsbCommand { companion object { + const val MODEL_NAME = "KA5" const val PRODUCT_ID = 85 } @IgnoredOnParcel - override val getFilter = byteArrayOf(-57, -91, -93) + override val getFilter + get() = byteArrayOf(-57, -91, -93) @IgnoredOnParcel - override val getOtherState = byteArrayOf(-57, -91, -92) + override val getOtherState + get() = byteArrayOf(-57, -91, -92) @IgnoredOnParcel - override val getSampleRate = byteArrayOf(-57, -91, -95) + override val getSampleRate + get() = byteArrayOf(-57, -91, -95) @IgnoredOnParcel - override val getVersion = byteArrayOf(-57, -91, -96) + override val getVersion + get() = byteArrayOf(-57, -91, -96) @IgnoredOnParcel - override val getVolumeLevel = byteArrayOf(-57, -91, -94) + override val getVolumeLevel + get() = byteArrayOf(-57, -91, -94) @IgnoredOnParcel - override val setChannelBalance = byteArrayOf(-57, -91, 5) + override val setChannelBalance + get() = byteArrayOf(-57, -91, 5) @IgnoredOnParcel - override val setDacMode = byteArrayOf(-57, -91, 6) + override val setDacMode + get() = byteArrayOf(-57, -91, 6) @IgnoredOnParcel - override val setDisplayBrightness = byteArrayOf(-57, -91, 11) + override val setDisplayBrightness + get() = byteArrayOf(-57, -91, 11) @IgnoredOnParcel - override val setDisplayInvert = byteArrayOf(-57, -91, 12) + override val setDisplayInvert + get() = byteArrayOf(-57, -91, 12) @IgnoredOnParcel - override val setDisplayTimeout = byteArrayOf(-57, -91, 9) + override val setDisplayTimeout + get() = byteArrayOf(-57, -91, 9) @IgnoredOnParcel - override val setFilter = byteArrayOf(-57, -91, 1) + override val setFilter + get() = byteArrayOf(-57, -91, 1) @IgnoredOnParcel - override val setGain = byteArrayOf(-57, -91, 2) + override val setGain + get() = byteArrayOf(-57, -91, 2) @IgnoredOnParcel - override val setHardwareMute = byteArrayOf(-57, -91, 7) + override val setHardwareMute + get() = byteArrayOf(-57, -91, 7) @IgnoredOnParcel - override val setHidMode = byteArrayOf(-57, -91, 10) + override val setHidMode + get() = byteArrayOf(-57, -91, 10) @IgnoredOnParcel - override val setSpdifOut = byteArrayOf(-57, -91, 8) + override val setSpdifOut + get() = byteArrayOf(-57, -91, 8) @IgnoredOnParcel - override val setVolumeLevel = byteArrayOf(-57, -91, 4) + override val setVolumeLevel + get() = byteArrayOf(-57, -91, 4) @IgnoredOnParcel - override val setVolumeMode = byteArrayOf(-57, -91, 13) + override val setVolumeMode + get() = byteArrayOf(-57, -91, 13) @IgnoredOnParcel - override val maxVolumeStepSize = HardwareVolumeControl.VOLUME_STEP_SIZE_MAX + override val maxVolumeStepSize + get() = HardwareVolumeControl.VOLUME_STEP_SIZE_4 @IgnoredOnParcel - override val isVolumeControlAsc = true + override val isVolumeControlAsc + get() = true override val currentVolumeLevel get() = volumeLevel.displayValue diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn.kt index 7fdc9ec..5908a48 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn.kt @@ -41,19 +41,26 @@ sealed class MoondropDawn( HardwareVolumeControl, MoondropDawnUsbCommand { - override val getAny = byteArrayOf(-64, -91, -93) + override val getAny + get() = byteArrayOf(-64, -91, -93) - override val getVolumeLevel = byteArrayOf(-64, -91, -94) + override val getVolumeLevel + get() = byteArrayOf(-64, -91, -94) - override val setFilter = byteArrayOf(-64, -91, 1) + override val setFilter + get() = byteArrayOf(-64, -91, 1) - override val setGain = byteArrayOf(-64, -91, 2) + override val setGain + get() = byteArrayOf(-64, -91, 2) - override val setIndicatorState = byteArrayOf(-64, -91, 6) + override val setIndicatorState + get() = byteArrayOf(-64, -91, 6) - override val setVolumeLevel = byteArrayOf(-64, -91, 4) + override val setVolumeLevel + get() = byteArrayOf(-64, -91, 4) - override val maxVolumeStepSize = HardwareVolumeControl.VOLUME_STEP_SIZE_MAX - 1 + override val maxVolumeStepSize + get() = HardwareVolumeControl.VOLUME_STEP_SIZE_3 override val isVolumeControlAsc: Boolean get() = false diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn35.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn35.kt index 2cbf351..d5a2777 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn35.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn35.kt @@ -34,7 +34,7 @@ data class MoondropDawn35( override val indicatorState: IndicatorState = IndicatorState.default(), override val volumeLevel: VolumeLevel = VolumeLevel.default() ) : MoondropDawn( - modelName = "Dawn 3.5mm", + modelName = MODEL_NAME, productId = PRODUCT_ID, filter = filter, gain = gain, @@ -43,7 +43,7 @@ data class MoondropDawn35( ) { companion object { - + const val MODEL_NAME = "Dawn 3.5mm" const val PRODUCT_ID = 61544 } } diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn44.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn44.kt index 37a8c30..af3fa97 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn44.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawn44.kt @@ -34,7 +34,7 @@ data class MoondropDawn44( override val indicatorState: IndicatorState = IndicatorState.default(), override val volumeLevel: VolumeLevel = VolumeLevel.default() ) : MoondropDawn( - modelName = "Dawn 4.4mm", + modelName = MODEL_NAME, productId = PRODUCT_ID, filter = filter, gain = gain, @@ -43,7 +43,7 @@ data class MoondropDawn44( ) { companion object { - + const val MODEL_NAME = "Dawn 4.4mm" const val PRODUCT_ID = 61543 } } diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawnPro.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawnPro.kt index 59c8a49..2f51635 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawnPro.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/dongle/moondrop/dawn/MoondropDawnPro.kt @@ -34,7 +34,7 @@ data class MoondropDawnPro( override val indicatorState: IndicatorState = IndicatorState.default(), override val volumeLevel: VolumeLevel = VolumeLevel.default() ) : MoondropDawn( - modelName = "Dawn Pro", + modelName = MODEL_NAME, productId = PRODUCT_ID, filter = filter, gain = gain, @@ -43,7 +43,7 @@ data class MoondropDawnPro( ) { companion object { - + const val MODEL_NAME = "Dawn Pro" const val PRODUCT_ID = 61546 } } diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/extension/UsbDeviceConnectionExtensions.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/extension/UsbDeviceConnectionExtensions.kt new file mode 100644 index 0000000..38cc23f --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/extension/UsbDeviceConnectionExtensions.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.core.extension + +import android.hardware.usb.UsbDeviceConnection +import android.hardware.usb.UsbInterface + +fun UsbDeviceConnection.claimInterface(usbInterface: UsbInterface): Boolean { + var success = claimInterface(usbInterface, false) + if (!success) { + success = claimInterface(usbInterface, true) + } + return success +} diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/volume/HardwareVolumeControl.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/volume/HardwareVolumeControl.kt index 2dc71d7..c0a2f57 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/volume/HardwareVolumeControl.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/core/volume/HardwareVolumeControl.kt @@ -28,9 +28,11 @@ interface HardwareVolumeControl { companion object { - const val VOLUME_STEP_SIZE_MIN = 1 - const val VOLUME_STEP_SIZE_MAX = 4 - const val VOLUME_STEP_SIZE_DEFAULT = VOLUME_STEP_SIZE_MIN + const val VOLUME_STEP_SIZE_1 = 1 + const val VOLUME_STEP_SIZE_2 = 2 + const val VOLUME_STEP_SIZE_3 = 3 + const val VOLUME_STEP_SIZE_4 = 4 + const val VOLUME_STEP_SIZE_DEFAULT = VOLUME_STEP_SIZE_1 } } @@ -49,7 +51,7 @@ fun HardwareVolumeControl.volumeUp(volumeStepSize: Int) = if (isVolumeControlAsc fun HardwareVolumeControl.incrementOrWrapVolumeStepSize(volumeStepSize: Int): Int { var nextVolumeStepSize = volumeStepSize.inc() if (nextVolumeStepSize > maxVolumeStepSize) { - nextVolumeStepSize = HardwareVolumeControl.VOLUME_STEP_SIZE_MIN + nextVolumeStepSize = HardwareVolumeControl.VOLUME_STEP_SIZE_1 } return nextVolumeStepSize } diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/data/FiioKa13UsbRepository.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/data/FiioKa13UsbRepository.kt new file mode 100644 index 0000000..ad72fac --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/data/FiioKa13UsbRepository.kt @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.data + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.tommygeenexus.usbdonglecontrol.core.data.UsbRepository +import io.github.tommygeenexus.usbdonglecontrol.core.di.DispatcherIo +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.Filter +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.FirmwareVersion +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.IndicatorState +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.VolumeLevel +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka5.feature.SpdifOut +import io.github.tommygeenexus.usbdonglecontrol.core.extension.claimInterface +import io.github.tommygeenexus.usbdonglecontrol.core.extension.suspendRunCatching +import javax.inject.Inject +import javax.inject.Singleton +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import timber.log.Timber + +@Singleton +class FiioKa13UsbRepository @Inject constructor( + @ApplicationContext private val context: Context, + @DispatcherIo private val dispatcherIo: CoroutineDispatcher +) : UsbRepository(context, dispatcherIo) { + + private companion object { + + const val DELAY_MS = 100L + const val TIMEOUT_MS = 200 + + const val REQUEST_INDEX_SET_FILTER_1 = 0 + const val REQUEST_INDEX_SET_FILTER_VOLUME_2 = 11 + const val REQUEST_INDEX_SET_INDICATOR_STATE_SPDIF_OUT = 2 + const val REQUEST_INDEX_SET_VOLUME_1 = 7 + + private const val REQUEST_TYPE = 33 + private const val REQUEST_ID = 9 + private const val REQUEST_VALUE = 512 + private const val REQUEST_INDEX = 0 + } + + // FIXME: Reading current state is not working properly (firmware issue?) + suspend fun getCurrentState(usbDongle: FiioKa13): Result { + return withContext(dispatcherIo) { + val (usbDevice, usbConnection) = coroutineContext.suspendRunCatching { + openFirstAttachedUsbDongleOrThrow() + }.getOrElse { exception -> + Timber.e(exception) + return@withContext Result.failure(exception) + } + coroutineContext.suspendRunCatching(onReleaseResources = { usbConnection.close() }) { + Result.success( + value = usbDongle.copy( + firmwareVersion = FirmwareVersion(displayValue = usbDevice.version) + ) + ) + }.getOrElse { exception -> + Timber.e(exception) + Result.failure(exception) + } + } + } + + // TODO: Is it actually working? + suspend fun setFilter(fiioKa13: FiioKa13, filter: Filter): Result { + return withContext(dispatcherIo) { + val (usbDevice, usbConnection) = coroutineContext.suspendRunCatching { + openFirstAttachedUsbDongleOrThrow() + }.getOrElse { exception -> + Timber.e(exception) + return@withContext Result.failure(exception) + } + coroutineContext.suspendRunCatching(onReleaseResources = { usbConnection.close() }) { + val usbInterface = usbDevice.getInterface(0) + mutex.withLock { + check(usbConnection.claimInterface(usbInterface)) + usbConnection.controlWrite( + requestType = REQUEST_TYPE, + requestId = REQUEST_ID, + requestValue = REQUEST_VALUE, + requestIndex = REQUEST_INDEX, + payload = fiioKa13.setFilter.first().apply { + set(REQUEST_INDEX_SET_FILTER_1, filter.payload) + }, + payloadSize = fiioKa13.setFilter.size, + transferTimeout = TIMEOUT_MS, + delayInMillisecondsAfterTransfer = DELAY_MS + ) + usbConnection.controlWrite( + requestType = REQUEST_TYPE, + requestId = REQUEST_ID, + requestValue = REQUEST_VALUE, + requestIndex = REQUEST_INDEX, + payload = fiioKa13.setFilter.last().apply { + set(REQUEST_INDEX_SET_FILTER_VOLUME_2, filter.payload) + }, + payloadSize = fiioKa13.setFilter.size, + transferTimeout = TIMEOUT_MS, + delayInMillisecondsAfterTransfer = DELAY_MS + ) + check(usbConnection.releaseInterface(usbInterface)) + } + Result.success(value = fiioKa13.copy(filter = filter)) + }.getOrElse { exception -> + Timber.e(exception) + Result.failure(exception) + } + } + } + + suspend fun setIndicatorState( + fiioKa13: FiioKa13, + indicatorState: IndicatorState + ): Result { + return withContext(dispatcherIo) { + val (usbDevice, usbConnection) = coroutineContext.suspendRunCatching { + openFirstAttachedUsbDongleOrThrow() + }.getOrElse { exception -> + Timber.e(exception) + return@withContext Result.failure(exception) + } + coroutineContext.suspendRunCatching(onReleaseResources = { usbConnection.close() }) { + val usbInterface = usbDevice.getInterface(0) + mutex.withLock { + check(usbConnection.claimInterface(usbInterface)) + usbConnection.controlWrite( + requestType = REQUEST_TYPE, + requestId = REQUEST_ID, + requestValue = REQUEST_VALUE, + requestIndex = REQUEST_INDEX, + payload = indicatorState.payload.copyInto( + destination = fiioKa13.setIndicatorState, + destinationOffset = REQUEST_INDEX_SET_INDICATOR_STATE_SPDIF_OUT + ), + payloadSize = fiioKa13.setIndicatorState.size, + transferTimeout = TIMEOUT_MS, + delayInMillisecondsAfterTransfer = DELAY_MS + ) + check(usbConnection.releaseInterface(usbInterface)) + } + Result.success(value = fiioKa13.copy(indicatorState = indicatorState)) + }.getOrElse { exception -> + Timber.e(exception) + Result.failure(exception) + } + } + } + + suspend fun setSpdifOut(fiioKa13: FiioKa13, spdifOut: SpdifOut): Result { + return withContext(dispatcherIo) { + val (usbDevice, usbConnection) = coroutineContext.suspendRunCatching { + openFirstAttachedUsbDongleOrThrow() + }.getOrElse { exception -> + Timber.e(exception) + return@withContext Result.failure(exception) + } + coroutineContext.suspendRunCatching(onReleaseResources = { usbConnection.close() }) { + val usbInterface = usbDevice.getInterface(0) + mutex.withLock { + check(usbConnection.claimInterface(usbInterface)) + usbConnection.controlWrite( + requestType = REQUEST_TYPE, + requestId = REQUEST_ID, + requestValue = REQUEST_VALUE, + requestIndex = REQUEST_INDEX, + payload = byteArrayOf(spdifOut.payload).copyInto( + destination = fiioKa13.setSpdifOut, + destinationOffset = REQUEST_INDEX_SET_INDICATOR_STATE_SPDIF_OUT + ), + payloadSize = fiioKa13.setSpdifOut.size, + transferTimeout = TIMEOUT_MS, + delayInMillisecondsAfterTransfer = DELAY_MS + ) + check(usbConnection.releaseInterface(usbInterface)) + } + Result.success(value = fiioKa13.copy(spdifOut = spdifOut)) + }.getOrElse { exception -> + Timber.e(exception) + Result.failure(exception) + } + } + } + + suspend fun setVolumeLevel(fiioKa13: FiioKa13, volumeLevel: VolumeLevel): Result { + return withContext(dispatcherIo) { + val (usbDevice, usbConnection) = coroutineContext.suspendRunCatching { + openFirstAttachedUsbDongleOrThrow() + }.getOrElse { exception -> + Timber.e(exception) + return@withContext Result.failure(exception) + } + coroutineContext.suspendRunCatching(onReleaseResources = { usbConnection.close() }) { + val usbInterface = usbDevice.getInterface(0) + val payload = volumeLevel.displayValueAndPayload.toByte() + mutex.withLock { + check(usbConnection.claimInterface(usbInterface)) + usbConnection.controlWrite( + requestType = REQUEST_TYPE, + requestId = REQUEST_ID, + requestValue = REQUEST_VALUE, + requestIndex = REQUEST_INDEX, + payload = fiioKa13.setVolumeLevel.first().apply { + set(REQUEST_INDEX_SET_VOLUME_1, payload) + }, + payloadSize = fiioKa13.setVolumeLevel.first().size, + transferTimeout = TIMEOUT_MS, + delayInMillisecondsAfterTransfer = DELAY_MS + ) + usbConnection.controlWrite( + requestType = REQUEST_TYPE, + requestId = REQUEST_ID, + requestValue = REQUEST_VALUE, + requestIndex = REQUEST_INDEX, + payload = fiioKa13.setVolumeLevel[1].apply { + set(REQUEST_INDEX_SET_VOLUME_1, payload) + }, + payloadSize = fiioKa13.setVolumeLevel.first().size, + transferTimeout = TIMEOUT_MS, + delayInMillisecondsAfterTransfer = DELAY_MS + ) + usbConnection.controlWrite( + requestType = REQUEST_TYPE, + requestId = REQUEST_ID, + requestValue = REQUEST_VALUE, + requestIndex = REQUEST_INDEX, + payload = fiioKa13.setVolumeLevel[2].apply { + set(REQUEST_INDEX_SET_FILTER_VOLUME_2, payload) + }, + payloadSize = fiioKa13.setVolumeLevel.first().size, + transferTimeout = TIMEOUT_MS, + delayInMillisecondsAfterTransfer = DELAY_MS + ) + usbConnection.controlWrite( + requestType = REQUEST_TYPE, + requestId = REQUEST_ID, + requestValue = REQUEST_VALUE, + requestIndex = REQUEST_INDEX, + payload = fiioKa13.setVolumeLevel[3].apply { + set(REQUEST_INDEX_SET_FILTER_VOLUME_2, payload) + }, + payloadSize = fiioKa13.setVolumeLevel.first().size, + transferTimeout = TIMEOUT_MS, + delayInMillisecondsAfterTransfer = DELAY_MS + ) + usbConnection.controlWrite( + requestType = REQUEST_TYPE, + requestId = REQUEST_ID, + requestValue = REQUEST_VALUE, + requestIndex = REQUEST_INDEX, + payload = fiioKa13.setVolumeLevel[4].apply { + set(REQUEST_INDEX_SET_FILTER_VOLUME_2, payload) + }, + payloadSize = fiioKa13.setVolumeLevel.first().size, + transferTimeout = TIMEOUT_MS, + delayInMillisecondsAfterTransfer = DELAY_MS + ) + usbConnection.controlWrite( + requestType = REQUEST_TYPE, + requestId = REQUEST_ID, + requestValue = REQUEST_VALUE, + requestIndex = REQUEST_INDEX, + payload = fiioKa13.setVolumeLevel.last().apply { + set(REQUEST_INDEX_SET_FILTER_VOLUME_2, payload) + }, + payloadSize = fiioKa13.setVolumeLevel.first().size, + transferTimeout = TIMEOUT_MS, + delayInMillisecondsAfterTransfer = DELAY_MS + ) + check(usbConnection.releaseInterface(usbInterface)) + } + Result.success(value = fiioKa13.copy(volumeLevel = volumeLevel)) + }.getOrElse { exception -> + Timber.e(exception) + Result.failure(exception) + } + } + } + + suspend fun setAll( + fiioKa13: FiioKa13, + filter: Filter, + indicatorState: IndicatorState, + spdifOut: SpdifOut, + volumeLevel: VolumeLevel + ): Result = withContext(dispatcherIo) { + setFilter(fiioKa13, filter) + setIndicatorState(fiioKa13, indicatorState) + setSpdifOut(fiioKa13, spdifOut) + setVolumeLevel(fiioKa13, volumeLevel) + } +} diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/FiioKa13Items.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/FiioKa13Items.kt new file mode 100644 index 0000000..e48195e --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/FiioKa13Items.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.VolumeLevel +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.createFromDisplayValue +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.displayValueToPercent +import io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka5.ui.ItemFilter +import io.github.tommygeenexus.usbdonglecontrol.dongle.moondrop.dawn.ui.ItemAudio +import io.github.tommygeenexus.usbdonglecontrol.dongle.moondrop.dawn.ui.ItemIndicatorState +import io.github.tommygeenexus.usbdonglecontrol.theme.cardPaddingBetween +import io.github.tommygeenexus.usbdonglecontrol.theme.cardSizeMinDp + +@Composable +fun FiioKa13Items( + modifier: Modifier = Modifier, + fiioKa13: FiioKa13 = FiioKa13(), + onFilterSelected: (Byte) -> Unit = {}, + onIndicatorStateSelected: (Byte) -> Unit = {}, + onSpdifOutSelected: (Boolean) -> Unit = {}, + onVolumeLevelSelected: (Int) -> Unit = {} +) { + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Adaptive(minSize = cardSizeMinDp), + modifier = modifier, + contentPadding = PaddingValues(all = cardPaddingBetween), + verticalItemSpacing = cardPaddingBetween, + horizontalArrangement = Arrangement.spacedBy(cardPaddingBetween) + ) { + item { + ItemInfo( + firmwareVersion = fiioKa13.firmwareVersion.displayValue, + sampleRate = fiioKa13.sampleRate.displayValue + ) + } + item { + ItemMisc( + isSpdifOutEnabled = fiioKa13.spdifOut.isEnabled, + onSpdifOutEnabledSwitched = onSpdifOutSelected + ) + } + item { + ItemIndicatorState( + indicatorStateId = fiioKa13.indicatorState.id, + onIndicatorStateSelected = onIndicatorStateSelected + ) + } + item { + ItemAudio( + volumeLevel = + VolumeLevel.MIN - fiioKa13.volumeLevel.displayValueAndPayload.toFloat(), + volumeLevelInPercent = fiioKa13.displayVolumeLevel, + volumeLevelStart = VolumeLevel.MAX.toFloat(), + volumeLevelEnd = VolumeLevel.MIN.toFloat(), + volumeLevelStepSize = 2f, + onVolumeLevelToPercent = { volumeLevel -> + VolumeLevel + .createFromDisplayValue(VolumeLevel.MIN - volumeLevel) + .displayValueToPercent() + }, + onVolumeLevelSelected = { volumeLevel -> + onVolumeLevelSelected(VolumeLevel.MIN - volumeLevel) + } + ) + } + item { + ItemFilter( + filterId = fiioKa13.filter.id, + onFilterSelected = onFilterSelected + ) + } + } +} + +@Preview +@Composable +private fun FiioKa13ItemsPreview() { + FiioKa13Items() +} diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/FiioKa13Previews.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/FiioKa13Previews.kt new file mode 100644 index 0000000..8cf6935 --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/FiioKa13Previews.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.ui + +import androidx.compose.material3.windowsizeclass.WindowSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import io.github.tommygeenexus.usbdonglecontrol.control.ui.ControlScreen +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.FiioKa13 + +private val usbDongle = FiioKa13() + +@Preview(name = "Loading") +@Composable +private fun ControlScreenPreview1() { + ControlScreen( + usbDongle = usbDongle, + isLoading = true + ) +} + +@Preview(name = "Compact") +@Composable +private fun ControlScreenPreview2() { + ControlScreen(usbDongle = usbDongle) +} + +@Preview(name = "Medium") +@Composable +private fun ControlScreenPreview3() { + ControlScreen( + windowSizeClass = WindowSizeClass.calculateFromSize(DpSize.Zero.copy(width = 600.dp)), + usbDongle = usbDongle + ) +} + +@Preview(name = "Expanded") +@Composable +private fun ControlScreenPreview4() { + ControlScreen( + windowSizeClass = WindowSizeClass.calculateFromSize(DpSize.Zero.copy(width = 840.dp)), + usbDongle = usbDongle + ) +} diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/ItemInfo.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/ItemInfo.kt new file mode 100644 index 0000000..d68266d --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/ItemInfo.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.BuildCircle +import androidx.compose.material.icons.outlined.MusicNote +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import io.github.tommygeenexus.usbdonglecontrol.R +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.FirmwareVersion +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.SampleRate +import io.github.tommygeenexus.usbdonglecontrol.core.dongle.fiio.ka13.feature.default +import io.github.tommygeenexus.usbdonglecontrol.theme.cardPadding + +@Composable +fun ItemInfo( + modifier: Modifier = Modifier, + firmwareVersion: String = FirmwareVersion.default().displayValue, + sampleRate: String = SampleRate.default().displayValue +) { + ElevatedCard(modifier = modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(all = cardPadding)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = Icons.Outlined.BuildCircle, + contentDescription = stringResource(id = R.string.fw_version) + ) + Text( + text = stringResource(id = R.string.fw_version, firmwareVersion), + modifier = Modifier.padding(horizontal = cardPadding), + style = MaterialTheme.typography.bodyMedium + ) + } + Row( + modifier = Modifier.padding(top = cardPadding), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Outlined.MusicNote, + contentDescription = stringResource(id = R.string.sample_rate) + ) + Text( + text = stringResource(id = R.string.sample_rate, sampleRate), + modifier = Modifier.padding(horizontal = cardPadding), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } +} + +@Preview +@Composable +private fun ItemInfoPreview() { + ItemInfo() +} diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/ItemMisc.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/ItemMisc.kt new file mode 100644 index 0000000..8da0e9a --- /dev/null +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka13/ui/ItemMisc.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.github.tommygeenexus.usbdonglecontrol.dongle.fiio.ka13.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import io.github.tommygeenexus.usbdonglecontrol.R +import io.github.tommygeenexus.usbdonglecontrol.theme.cardPadding + +@Composable +fun ItemMisc( + modifier: Modifier = Modifier, + isSpdifOutEnabled: Boolean = false, + onSpdifOutEnabledSwitched: (Boolean) -> Unit = {} +) { + ElevatedCard(modifier = modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(all = cardPadding), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(id = R.string.spdif_out), + style = MaterialTheme.typography.titleMedium + ) + Switch( + checked = isSpdifOutEnabled, + onCheckedChange = onSpdifOutEnabledSwitched + ) + } + } +} + +@Preview +@Composable +private fun ItemMiscPreview() { + ItemMisc() +} diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka5/data/FiioKa5UsbRepository.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka5/data/FiioKa5UsbRepository.kt index 49c5ea2..a964a6a 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka5/data/FiioKa5UsbRepository.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/fiio/ka5/data/FiioKa5UsbRepository.kt @@ -80,7 +80,7 @@ class FiioKa5UsbRepository @Inject constructor( suspend fun getCurrentState(usbDongle: FiioKa5): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -200,7 +200,7 @@ class FiioKa5UsbRepository @Inject constructor( suspend fun getVolumeLevelAndMode(usbDongle: FiioKa5): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -254,7 +254,7 @@ class FiioKa5UsbRepository @Inject constructor( channelBalance: ChannelBalance ): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -283,7 +283,7 @@ class FiioKa5UsbRepository @Inject constructor( suspend fun setDacMode(fiioKa5: FiioKa5, dacMode: DacMode): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -314,7 +314,7 @@ class FiioKa5UsbRepository @Inject constructor( displayBrightness: DisplayBrightness ): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -342,7 +342,7 @@ class FiioKa5UsbRepository @Inject constructor( suspend fun setDisplayInvert(fiioKa5: FiioKa5, displayInvert: DisplayInvert): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -373,7 +373,7 @@ class FiioKa5UsbRepository @Inject constructor( displayTimeout: DisplayTimeout ): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -401,7 +401,7 @@ class FiioKa5UsbRepository @Inject constructor( suspend fun setFilter(fiioKa5: FiioKa5, filter: Filter): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -429,7 +429,7 @@ class FiioKa5UsbRepository @Inject constructor( suspend fun setGain(fiioKa5: FiioKa5, gain: Gain): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -457,7 +457,7 @@ class FiioKa5UsbRepository @Inject constructor( suspend fun setHardwareMute(fiioKa5: FiioKa5, hardwareMute: HardwareMute): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -485,7 +485,7 @@ class FiioKa5UsbRepository @Inject constructor( suspend fun setHidMode(fiioKa5: FiioKa5, hidMode: HidMode): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -513,7 +513,7 @@ class FiioKa5UsbRepository @Inject constructor( suspend fun setSpdifOut(fiioKa5: FiioKa5, spdifOut: SpdifOut): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -541,7 +541,7 @@ class FiioKa5UsbRepository @Inject constructor( suspend fun setVolumeMode(fiioKa5: FiioKa5, volumeMode: VolumeMode): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -569,7 +569,7 @@ class FiioKa5UsbRepository @Inject constructor( suspend fun setVolumeLevel(fiioKa5: FiioKa5, volumeLevel: VolumeLevel): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -611,7 +611,7 @@ class FiioKa5UsbRepository @Inject constructor( volumeMode: VolumeMode ): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/moondrop/dawn/data/MoondropDawnUsbRepository.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/moondrop/dawn/data/MoondropDawnUsbRepository.kt index eaebe3c..b0bdf70 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/moondrop/dawn/data/MoondropDawnUsbRepository.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/moondrop/dawn/data/MoondropDawnUsbRepository.kt @@ -59,7 +59,7 @@ class MoondropDawnUsbRepository @Inject constructor( suspend fun getCurrentState(usbDongle: MoondropDawn): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -140,7 +140,7 @@ class MoondropDawnUsbRepository @Inject constructor( suspend fun setFilter(moondropDawn: MoondropDawn, filter: Filter): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -178,7 +178,7 @@ class MoondropDawnUsbRepository @Inject constructor( suspend fun setGain(moondropDawn: MoondropDawn, gain: Gain): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -219,7 +219,7 @@ class MoondropDawnUsbRepository @Inject constructor( indicatorState: IndicatorState ): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -260,7 +260,7 @@ class MoondropDawnUsbRepository @Inject constructor( volumeLevel: VolumeLevel ): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) @@ -304,7 +304,7 @@ class MoondropDawnUsbRepository @Inject constructor( volumeLevel: VolumeLevel ): Result { return withContext(dispatcherIo) { - val usbConnection = coroutineContext.suspendRunCatching { + val (_, usbConnection) = coroutineContext.suspendRunCatching { openFirstAttachedUsbDongleOrThrow() }.getOrElse { exception -> Timber.e(exception) diff --git a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/moondrop/dawn/ui/ItemAudio.kt b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/moondrop/dawn/ui/ItemAudio.kt index 73c1eb6..7490433 100644 --- a/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/moondrop/dawn/ui/ItemAudio.kt +++ b/app/src/main/kotlin/io/github/tommygeenexus/usbdonglecontrol/dongle/moondrop/dawn/ui/ItemAudio.kt @@ -46,6 +46,7 @@ fun ItemAudio( volumeLevelInPercent: String = VolumeLevel.default().displayValueToPercent(), volumeLevelStart: Float = VolumeLevel.MAX.toFloat(), volumeLevelEnd: Float = VolumeLevel.MIN.toFloat(), + volumeLevelStepSize: Float = 0f, onVolumeLevelToPercent: (Int) -> String = { _ -> volumeLevelInPercent }, onVolumeLevelSelected: (Int) -> Unit = {} ) { @@ -56,16 +57,14 @@ fun ItemAudio( style = MaterialTheme.typography.titleMedium ) Text( - text = stringResource( - id = R.string.volume_level, - volumeLevelInPercent - ), + text = stringResource(id = R.string.volume_level, volumeLevelInPercent), modifier = Modifier.padding(top = cardPadding), style = MaterialTheme.typography.bodyMedium ) AndroidView( factory = { context -> Slider(context).apply { + stepSize = volumeLevelStepSize setLabelFormatter { value -> onVolumeLevelToPercent(value.roundToInt()) } diff --git a/app/src/main/res/xml/device_filter.xml b/app/src/main/res/xml/device_filter.xml index 5c42d8c..882db37 100644 --- a/app/src/main/res/xml/device_filter.xml +++ b/app/src/main/res/xml/device_filter.xml @@ -1,5 +1,7 @@ + +