Skip to content

Commit

Permalink
Merge pull request #41 from miyaoka/event-notify
Browse files Browse the repository at this point in the history
登録イベントの通知機能を実装
  • Loading branch information
miyaoka authored Oct 3, 2024
2 parents 8dbc7ed + 3c6d075 commit aef7840
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 126 deletions.
7 changes: 3 additions & 4 deletions src/components/menu/bookmarkedEvent/BookmarkedEventButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ import BookmarkedEventPopover from "./BookmarkedEventPopover.vue";
import type { LiverEvent } from "@/services/api";
import { usePopover } from "@/composable/usePopover";
import { useBookmarkStore } from "@/store/bookmarkStore";
import { useEventListStore } from "@/store/eventListStore";
import { useStorageStore } from "@/store/storageStore";
const eventListStore = useEventListStore();
const storageStore = useStorageStore();
const bookmarkStore = useBookmarkStore();
const popover = usePopover();
const bookmarkEventList = computed(() => {
const list: LiverEvent[] = [];
storageStore.bookmarkEventSet.forEach((id) => {
bookmarkStore.bookmarkEventMap.forEach((_value, id) => {
const liverEvent = eventListStore.liverEventMap.get(id);
if (liverEvent) {
list.push(liverEvent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { computed, onMounted } from "vue";
import type { LiverEvent } from "@/services/api";
import { scrollToLiverEventTop } from "@/lib/scroll";
import { getThumnail } from "@/lib/youtube";
import { useBookmarkStore } from "@/store/bookmarkStore";
import { useDateStore } from "@/store/dateStore";
import { useFocusStore } from "@/store/focusStore";
import { useStorageStore } from "@/store/storageStore";
import { compareDate, getDateTime } from "@/utils/date";
import { hhmmDateFormatter, toRelativeTime } from "@/utils/dateFormat";
import { closePopover } from "@/utils/popover";
Expand All @@ -16,7 +16,7 @@ const props = defineProps<{
const focusStore = useFocusStore();
const dateStore = useDateStore();
const storageStore = useStorageStore();
const bookmarkStore = useBookmarkStore();
const bookmarkCount = computed(() => props.bookmarkEventList.length);
Expand Down Expand Up @@ -99,7 +99,7 @@ onMounted(() => {
<div class="flex justify-center p-2">
<button
class="rounded bg-gray-200 px-4 py-2 text-sm hover:bg-gray-300"
@click.prevent="storageStore.resetBookmarkEventSet"
@click.prevent="bookmarkStore.bookmarkEventMap.clear"
>
clear all
</button>
Expand Down
101 changes: 21 additions & 80 deletions src/components/streams/LiverEventCard.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
<script setup lang="ts">
import { computed, toRaw } from "vue";
import { computed, toRaw, toRef } from "vue";
import { useLiverEvent } from "./useLiverEvent";
import type { LiverEvent } from "@/services/api";
import { getThumnail } from "@/lib/youtube";
import { useDateStore } from "@/store/dateStore";
import { useEventListStore } from "@/store/eventListStore";
import { useFocusStore } from "@/store/focusStore";
import { useSearchStore } from "@/store/searchStore";
import { useStorageStore } from "@/store/storageStore";
import { hhss } from "@/utils/dateFormat";
import { getChannelIcon } from "@/utils/icons";
Expand All @@ -15,40 +13,10 @@ const props = defineProps<{
}>();
const focusStore = useFocusStore();
const dateStore = useDateStore();
const eventListStore = useEventListStore();
const storageStore = useStorageStore();
const searchStore = useSearchStore();
const oneHour = 60 * 60 * 1000;
// ホロライブのライブ判定開始用
const liveStartDuration = 20 * 60 * 1000;
const liveEndDuration = 60 * 60 * 1000;
const elapsedTime = computed(() => {
const { isLive, endAt } = props.liverEvent;
const time = (() => {
// 終了時間があれば終了時間から開始時間を引く
if (endAt) {
return endAt.getTime() - props.liverEvent.startAt.getTime();
}
// ライブ中なら現在時刻から開始時間を引く
if (isLive) {
return dateStore.currentTime - props.liverEvent.startAt.getTime();
}
return 0;
})();
if (time === 0) return null;
const hour = time / oneHour;
return {
fixed: hour.toFixed(1),
count: Math.min(12, Math.max(1, Math.round(hour))),
};
});
const { isFinished, elapsedTime, isNew, hasBookmark, hasNotify } = useLiverEvent(
toRef(props.liverEvent),
);
const timeDisplay = computed(() => {
const { isLive, startAt } = props.liverEvent;
Expand All @@ -68,34 +36,6 @@ const timeDisplay = computed(() => {
return strs.join(" ");
});
// 配信終了判定
const isFinished = computed(() => {
// 終了時刻が設定されているか(にじさんじのみ)
if (props.liverEvent.endAt) return true;
// 配信中か
if (props.liverEvent.isLive) return false;
// 配信していない場合
const now = dateStore.currentTime;
const startTime = props.liverEvent.startAt.getTime();
const elapsed = now - startTime;
// 現在時刻を過ぎていなければ開始前
if (elapsed < 0) return false;
// ホロライブの場合
if (props.liverEvent.affilication === "hololive") {
// 配信開始直後は開始時間が更新されてもliveになっていない場合があるので一定時間判定しない
if (elapsed < liveStartDuration) return false;
// startTimeの秒数が0以外あれば配信開始済み
if (props.liverEvent.startAt.getSeconds() !== 0) return true;
// 秒数が0の場合、1時間経過していたら終了と見なす
if (elapsed > liveEndDuration) return true;
}
// それ以外の場合:未終了
return false;
});
const isHovered = computed(() => {
if (!focusStore.hoveredTalent) return false;
Expand All @@ -122,14 +62,6 @@ const hasHoveredHash = computed(() => {
return hashSet.intersection(focusStore.hoveredHashSet).size > 0;
});
const isNew = computed(() => {
return eventListStore.addedEventIdSet.has(props.liverEvent.id);
});
const isBookmark = computed(() => {
return storageStore.bookmarkEventSet.has(props.liverEvent.id);
});
// 通常クリック時はpreventしてダイアログを開き、ホイールクリックはリンクを開く
function onClickCard(evt: MouseEvent) {
evt.preventDefault();
Expand Down Expand Up @@ -233,13 +165,22 @@ function setSearchString(str: string) {
>
<i class="i-mdi-sparkles size-7 text-purple-600" />
</div>
<div
v-if="isBookmark"
class="grid size-10 place-items-center rounded-full border-2 border-green-800 bg-white shadow-md"
title="bookmark"
>
<i class="i-mdi-bookmark size-7 text-green-600" />
</div>
<template v-if="hasBookmark">
<div
v-if="hasNotify"
class="grid size-10 place-items-center rounded-full border-2 border-yellow-800 bg-white shadow-md"
title="bookmark"
>
<i class="i-mdi-bell size-7 text-yellow-600" />
</div>
<div
v-else
class="grid size-10 place-items-center rounded-full border-2 border-green-800 bg-white shadow-md"
title="bookmark"
>
<i class="i-mdi-bookmark size-7 text-green-600" />
</div>
</template>
</div>

<div
Expand Down
111 changes: 91 additions & 20 deletions src/components/streams/detail/LiverEventDetailPopover.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
<script setup lang="ts">
import { computed } from "vue";
import { computed, toRef } from "vue";
import { useLiverEvent } from "../useLiverEvent";
import type { LiverEvent } from "@/services/api";
import { usePopover } from "@/composable/usePopover";
import { parseSegment } from "@/lib/text";
import { getThumnail } from "@/lib/youtube";
import { useBookmarkStore } from "@/store/bookmarkStore";
import { useFocusStore } from "@/store/focusStore";
import { useNotificationStore } from "@/store/notificationStore";
import { useSearchStore } from "@/store/searchStore";
import { useStorageStore } from "@/store/storageStore";
import { fullDateFormatter } from "@/utils/dateFormat";
import { closePopover } from "@/utils/popover";
const props = defineProps<{
liverEvent: LiverEvent;
}>();
const bookmarkStore = useBookmarkStore();
const focusStore = useFocusStore();
const storageStore = useStorageStore();
const searchStore = useSearchStore();
const notificationStore = useNotificationStore();
const { isFinished, hasBookmark, hasNotify, beforeStartTime } = useLiverEvent(
toRef(props.liverEvent),
);
const permissionPopover = usePopover();
const isBookmark = computed(() => {
return storageStore.bookmarkEventSet.has(props.liverEvent.id);
});
const fullDate = computed(() => {
return fullDateFormatter.format(props.liverEvent.startAt);
});
// セグメント化したタイトル
const segmentList = computed(() => {
const { title, keywordList, hashtagList: hashList } = props.liverEvent;
return parseSegment(title, keywordList, hashList);
const { title, keywordList, hashtagList } = props.liverEvent;
return parseSegment(title, keywordList, hashtagList);
});
function setSearchString(str: string) {
Expand All @@ -40,6 +46,33 @@ function setSearchString(str: string) {
}
searchStore.setSearchString(formattedStr);
}
// 通知機能が使えるか
const canSetNotify = computed(() => {
if (!notificationStore.isSupported) return false;
// 開始時間前なら通知可能
return beforeStartTime.value && !isFinished.value;
});
async function onClickNotify(id: string) {
if (!notificationStore.isSupported) return;
// 通知許可済みなら通知をトグル
if (notificationStore.permissionGranted) {
bookmarkStore.toggleNotifyEvent(id);
return;
}
// 通知許可ポップオーバーを表示
permissionPopover.showPopover();
// 通知許可を求める
const permission = await notificationStore.ensurePermissions();
if (!permission) return;
// 許可されたらポップオーバーを閉じて通知をトグル
permissionPopover.hidePopover();
bookmarkStore.toggleNotifyEvent(id);
}
</script>

<template>
Expand Down Expand Up @@ -118,21 +151,47 @@ function setSearchString(str: string) {
</button>
</div>
</div>
<button
class="group/fav grid size-11 place-items-center"
@click="storageStore.toggleBookmarkEvent(liverEvent.id)"
title="bookmark"
>
<div
:class="`size-10 place-items-center bg-white rounded-full grid border-2 ${isBookmark ? 'border-green-800' : 'border-gray-400'} group-hover/fav:bg-gray-100`"
<div class="flex flex-col place-items-center">
<button
class="group/fav grid size-11 place-items-center"
@click="bookmarkStore.toggleBookmarkEvent(liverEvent.id)"
title="add bookmark"
>
<i
:class="`size-7 ${isBookmark ? 'i-mdi-bookmark text-green-600' : 'i-mdi-bookmark-outline text-gray-400'}`"
/>
</div>
</button>
<div
:class="`size-10 place-items-center bg-white rounded-full grid border-2 group-hover/fav:bg-gray-100 ${hasBookmark ? 'border-green-800' : 'border-gray-400'} `"
>
<i
:class="`size-7 ${hasBookmark ? 'i-mdi-bookmark text-green-600' : 'i-mdi-bookmark-outline text-gray-400'}`"
/>
</div>
</button>
<button
v-if="canSetNotify"
class="group/fav grid size-11 place-items-center"
@click="onClickNotify(liverEvent.id)"
title="add notification"
>
<div
:class="`size-10 place-items-center bg-white rounded-full grid border-2 border-gray-400 group-hover/fav:bg-gray-100
${hasNotify ? 'border-yellow-800' : 'border-gray-400'}
`"
>
<i
:class="`size-7 ${hasNotify ? 'i-mdi-bell text-yellow-600' : 'i-mdi-bell-outline text-gray-400'}`"
/>
</div>
</button>
</div>
</div>
</div>
<permissionPopover.PopOver class="bottom-4 top-auto overflow-visible bg-transparent p-0">
<button
class="rounded-full bg-yellow-400 p-4 text-sm shadow-md"
@click="permissionPopover.hidePopover()"
>
通知を許可してください
</button>
</permissionPopover.PopOver>
</div>
</template>

Expand All @@ -143,4 +202,16 @@ function setSearchString(str: string) {
opacity: 0;
}
}
[popover] {
&:popover-open {
animation: fadeIn 0.2s ease-in-out forwards;
}
}
@keyframes fadeIn {
from {
translate: 0 50%;
}
}
</style>
Loading

0 comments on commit aef7840

Please sign in to comment.