From eedf038a70928b540b721719144026d0d1c17ba4 Mon Sep 17 00:00:00 2001 From: InfinityPacer <160988576+InfinityPacer@users.noreply.github.com> Date: Sat, 1 Aug 2026 07:27:58 +0800 Subject: [PATCH] fix(glass): disable mobile motion dynamics (#623) --- src/components/dialog/GlassSettingsDialog.vue | 19 +++- .../__tests__/GlassSettingsDialog.spec.ts | 54 ++++++++++- src/components/theme/GlassOpticalLayer.vue | 7 +- .../theme/__tests__/GlassOpticalLayer.spec.ts | 49 +++++++++- .../__tests__/useGlassOpticalRenderer.spec.ts | 81 ++++++++++++++++ .../useGlassPresentationCapabilities.spec.ts | 49 ++++++++++ src/composables/useGlassOpticalRenderer.ts | 92 +++++++++++++++---- .../useGlassPresentationCapabilities.ts | 13 +++ src/locales/en-US.ts | 5 +- src/locales/zh-CN.ts | 4 +- src/locales/zh-TW.ts | 4 +- 11 files changed, 349 insertions(+), 28 deletions(-) create mode 100644 src/composables/__tests__/useGlassPresentationCapabilities.spec.ts create mode 100644 src/composables/useGlassPresentationCapabilities.ts diff --git a/src/components/dialog/GlassSettingsDialog.vue b/src/components/dialog/GlassSettingsDialog.vue index d9c158d1..df1b3026 100644 --- a/src/components/dialog/GlassSettingsDialog.vue +++ b/src/components/dialog/GlassSettingsDialog.vue @@ -19,6 +19,7 @@ import { type GlassOpticalPreset, type GlassOpticalPresetOverrides, } from '@/utils/glassOptics' +import { useGlassMobilePresentation } from '@/composables/useGlassPresentationCapabilities' import { useDisplay } from 'vuetify' import { useI18n } from 'vue-i18n' @@ -38,6 +39,7 @@ const emit = defineEmits<{ const { t } = useI18n() const display = useDisplay() +const usesMobilePresentation = useGlassMobilePresentation() const { settings } = useThemeCustomizer() const draftAppearance = ref(settings.value.glassAppearance) const draftDeformationStrength = ref(settings.value.glassDeformationStrength) @@ -51,7 +53,7 @@ const draftTranslationStrength = ref(settings.value.glassTranslationStrength) const draftTransparencyStrength = ref(settings.value.glassTransparencyStrength) const isSaving = ref(false) const usesRealtimeOptics = computed(() => draftQuality.value !== 'css') -const showsDynamicTuning = computed(() => usesRealtimeOptics.value) +const showsDynamicTuning = computed(() => usesRealtimeOptics.value && !usesMobilePresentation.value) const availablePresets = computed(() => getAvailableGlassOpticalPresets(draftQuality.value)) const activePreset = computed(() => availablePresets.value.includes(draftPreset.value) ? draftPreset.value : 'natural', @@ -105,7 +107,11 @@ const qualityOptions: Array<{ { hint: 'theme.glassQualityBalancedHint', label: 'theme.glassQualityBalanced', value: 'balanced' }, { hint: 'theme.glassQualityHighHint', label: 'theme.glassQualityHigh', value: 'high' }, ] -const qualityHint = computed(() => qualityOptions.find(option => option.value === draftQuality.value)?.hint ?? '') +const qualityHint = computed(() => { + if (usesMobilePresentation.value && draftQuality.value !== 'css') return 'theme.glassQualityMobileHint' + + return qualityOptions.find(option => option.value === draftQuality.value)?.hint ?? '' +}) const presetOptions: Array<{ label: string; value: GlassOpticalPreset }> = [ { label: 'theme.glassPresetNatural', value: 'natural' }, { label: 'theme.glassPresetGlide', value: 'glide' }, @@ -410,6 +416,9 @@ onScopeDispose(cancelGlassPreview) thumb-label @update:model-value="updateReflectionStrength" /> +

+ {{ t('theme.glassMaterialStrengthHint') }} +

{{ t('theme.glassDynamicTuning') }}

@@ -463,10 +472,10 @@ onScopeDispose(cancelGlassPreview) thumb-label @update:model-value="updateFlowStrength" /> +

+ {{ t('theme.glassOpticalStrengthHint') }} +

-

- {{ t(showsDynamicTuning ? 'theme.glassOpticalStrengthHint' : 'theme.glassOpticalStrengthUnavailableHint') }} -

diff --git a/src/components/dialog/__tests__/GlassSettingsDialog.spec.ts b/src/components/dialog/__tests__/GlassSettingsDialog.spec.ts index 6a839aec..97f3a454 100644 --- a/src/components/dialog/__tests__/GlassSettingsDialog.spec.ts +++ b/src/components/dialog/__tests__/GlassSettingsDialog.spec.ts @@ -23,6 +23,10 @@ const mocks = vi.hoisted(() => ({ cancelGlassPreview: vi.fn(), commitGlassPreview: vi.fn(), previewGlassSettings: vi.fn(), + usesMobilePresentation: null as { value: boolean } | null, + display: { + smAndDown: { value: false }, + }, settings: { value: { glassAppearance: 'clear', @@ -53,14 +57,25 @@ vi.mock('vue-i18n', () => ({ })) vi.mock('vuetify', () => ({ - useDisplay: () => ({ smAndDown: { value: true } }), + useDisplay: () => mocks.display, })) +vi.mock('@/composables/useGlassPresentationCapabilities', async () => { + const { ref } = await vi.importActual('vue') + mocks.usesMobilePresentation = ref(false) + + return { + useGlassMobilePresentation: () => mocks.usesMobilePresentation, + } +}) + describe('GlassSettingsDialog', () => { beforeEach(() => { mocks.cancelGlassPreview.mockClear() mocks.commitGlassPreview.mockClear() mocks.previewGlassSettings.mockClear() + mocks.usesMobilePresentation!.value = false + mocks.display.smAndDown.value = false mocks.settings.value.glassAppearance = 'clear' mocks.settings.value.glassDeformationStrength = 50 mocks.settings.value.glassFlowStrength = 50 @@ -74,6 +89,7 @@ describe('GlassSettingsDialog', () => { }) it('cancels an active preview when the parent closes the dialog', async () => { + mocks.display.smAndDown.value = true const wrapper = shallowMount(GlassSettingsDialog, { global: { stubs: { @@ -367,4 +383,40 @@ describe('GlassSettingsDialog', () => { 'theme.glassFlowStrength', ]) }) + + it('restores motion tuning when a mobile presentation returns to desktop', async () => { + mocks.usesMobilePresentation!.value = true + mocks.settings.value.glassQuality = 'high' + const wrapper = shallowMount(GlassSettingsDialog, { + global: { + stubs: { + VCard: slotStub, + VCardActions: slotStub, + VCardText: slotStub, + VDialog: dialogStub, + VDialogCloseBtn: true, + VSlider: sliderStub, + }, + }, + props: { modelValue: true }, + }) + + expect(wrapper.findAll('.slider-stub').map(slider => slider.attributes('aria-label'))).toEqual([ + 'theme.glassTransparencyStrength', + 'theme.glassTransmissionStrength', + 'theme.glassReflectionStrength', + ]) + expect(wrapper.find('.glass-settings-dialog__live-controls').exists()).toBe(false) + expect(wrapper.text()).toContain('theme.glassMaterialStrengthHint') + expect(wrapper.text()).not.toContain('theme.glassOpticalStrengthHint') + expect(wrapper.text()).toContain('theme.glassQualityMobileHint') + + mocks.usesMobilePresentation!.value = false + await wrapper.vm.$nextTick() + + expect(wrapper.findAll('.slider-stub')).toHaveLength(6) + expect(wrapper.find('.glass-settings-dialog__live-controls').exists()).toBe(true) + expect(wrapper.text()).toContain('theme.glassMaterialStrengthHint') + expect(wrapper.text()).toContain('theme.glassOpticalStrengthHint') + }) }) diff --git a/src/components/theme/GlassOpticalLayer.vue b/src/components/theme/GlassOpticalLayer.vue index dfb9e29c..2d900fda 100644 --- a/src/components/theme/GlassOpticalLayer.vue +++ b/src/components/theme/GlassOpticalLayer.vue @@ -1,5 +1,6 @@