+
{{ i18n.ts.reduceUiMargin }}
+
{{ i18n.ts.largeNoteText }}
+
{{ i18n.ts.hideNavFooter }}
{{ i18n.ts.reduceUiAnimation }}
{{ i18n.ts.useBlurEffect }}
{{ i18n.ts.useBlurEffectForModal }}
@@ -284,6 +287,9 @@ const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showC
const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction'));
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
+const reduceMargin = computed(defaultStore.makeGetterSetter('reduceMargin'));
+const largeNoteText = computed(defaultStore.makeGetterSetter('largeNoteText'));
+const hideNavFooter = computed(defaultStore.makeGetterSetter('hideNavFooter'));
const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal'));
const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect'));
@@ -364,6 +370,9 @@ watch([
alwaysConfirmFollow,
confirmWhenRevealingSensitiveMedia,
contextMenu,
+ reduceMargin,
+ largeNoteText,
+ hideNavFooter,
], async () => {
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
});
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 77b5328a339e..d8f1161b7168 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -63,6 +63,16 @@ provide('shouldOmitHeaderTitle', true);
const tlComponent = shallowRef>();
const rootEl = shallowRef();
+const DESKTOP_THRESHOLD = 1100;
+const MOBILE_THRESHOLD = 500;
+
+const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
+const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
+
+window.addEventListener('resize', () => {
+ isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
+});
+
const router = useRouter();
type TimelinePageSrc = BasicTimelineType | `list:${string}`;
@@ -351,6 +361,10 @@ const headerTabsWhenNotLogin = computed(() => [...availableBasicTimelines().map(
iconOnly: true,
}))] as Tab[]);
+const marginMin = computed(() =>
+ (defaultStore.state.reduceMargin && isMobile.value) ? 0 : 16
+);
+
definePageMetadata(() => ({
title: i18n.ts.timeline,
icon: isBasicTimeline(src.value) ? basicTimelineIconClass(src.value) : 'ti ti-home',
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 94329f45c9e5..b949a1fc87cc 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -222,6 +222,18 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
+ hideNavFooter: {
+ where: 'device',
+ default: false,
+ },
+ reduceMargin: {
+ where: 'device',
+ default: true,
+ },
+ largeNoteText: {
+ where: 'device',
+ default: true,
+ },
animation: {
where: 'device',
default: !window.matchMedia('(prefers-reduced-motion)').matches,
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 7711b248cdfd..504c11f20713 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -26,7 +26,13 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
+
+
{
const pageMetadata = ref(null);
const widgetsShowing = ref(false);
+const navFooterShowing = ref(true);
const navFooter = shallowRef();
const contents = shallowRef>();
@@ -194,12 +202,35 @@ defaultStore.loaded.then(() => {
}
});
+let scrollHistory = [];
onMounted(() => {
if (!isDesktop.value) {
window.addEventListener('resize', () => {
if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
}, { passive: true });
}
+ if (defaultStore.state.hideNavFooter) {
+ contents.value.rootEl.addEventListener('scroll', () => {
+ const now = new Date();
+ scrollHistory = scrollHistory.filter(x => (now - x.time < 2000) && (now > x.time));
+ let scrollPosition = contents.value.rootEl.scrollTop;
+ scrollHistory.push({ time: now, position: scrollPosition });
+ if (scrollHistory.length === 1) {
+ return;
+ }
+ let diffPosition = scrollPosition - scrollHistory[0].position;
+ let diffTime = now - scrollHistory[0].time;
+ let scrollSpeed = diffPosition / diffTime;
+ if (scrollPosition === 0) {
+ navFooterShowing.value = true;
+ scrollHistory = [];
+ } else if (scrollSpeed > 0.2 && diffPosition > 300 || scrollSpeed < -0.5 && diffPosition < -600) {
+ navFooterShowing.value = false;
+ } else if (-0.2 < scrollSpeed && scrollSpeed < 0.02) {
+ navFooterShowing.value = true;
+ }
+ }, { passive: true });
+ }
});
const iconOnly = ref(false);
@@ -284,6 +315,20 @@ body {
$ui-font-size: 1em; // TODO: どこかに集約したい
$widgets-hide-threshold: 1090px;
+.transition_navFooter_enterActive {
+ opacity: 1;
+ transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+.transition_navFooter_leaveActive {
+ opacity: 1;
+ transition: opacity 800ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+
+.transition_navFooter_enterFrom,
+.transition_navFooter_leaveTo {
+ opacity: 0;
+}
+
.transition_menuDrawerBg_enterActive,
.transition_menuDrawerBg_leaveActive {
opacity: 1;