mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
一个简单的安卓岛
This commit is contained in:
@@ -96,8 +96,8 @@ export function GlobalSessionMonitor() {
|
||||
if (!isCurrentSession && (!oldSession || newSession.lastTimestamp > oldSession.lastTimestamp)) {
|
||||
// 这是新消息事件
|
||||
|
||||
// 免打扰、折叠群、折叠入口不弹通知
|
||||
if (newSession.isMuted || newSession.isFolded) continue
|
||||
// 折叠群、折叠入口不弹通知
|
||||
if (newSession.isFolded) continue
|
||||
if (newSession.username.toLowerCase().includes('placeholder_foldgroup')) continue
|
||||
|
||||
// 1. 群聊过滤自己发送的消息
|
||||
|
||||
@@ -134,6 +134,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.top-center {
|
||||
top: 24px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -20px) scale(0.95);
|
||||
|
||||
&.visible {
|
||||
transform: translate(-50%, 0) scale(1);
|
||||
}
|
||||
|
||||
// 灵动岛样式
|
||||
border-radius: 40px !important;
|
||||
padding: 12px 16px;
|
||||
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.2);
|
||||
|
||||
&.static {
|
||||
border-radius: 40px !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.16) !important;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ interface NotificationToastProps {
|
||||
onClose: () => void
|
||||
onClick: (sessionId: string) => void
|
||||
duration?: number
|
||||
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
|
||||
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center'
|
||||
isStatic?: boolean
|
||||
initialVisible?: boolean
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'
|
||||
import { Search, MessageSquare, AlertCircle, Loader2, RefreshCw, X, ChevronDown, ChevronLeft, Info, Calendar, Database, Hash, Play, Pause, Image as ImageIcon, Link, Mic, CheckCircle, Copy, Check, CheckSquare, Download, BarChart3, Edit2, Trash2, BellOff, Users, FolderClosed, UserCheck, Crown, Aperture } from 'lucide-react'
|
||||
import { Search, MessageSquare, AlertCircle, Loader2, RefreshCw, X, ChevronDown, ChevronLeft, Info, Calendar, Database, Hash, Play, Pause, Image as ImageIcon, Link, Mic, CheckCircle, Copy, Check, CheckSquare, Download, BarChart3, Edit2, Trash2, Users, FolderClosed, UserCheck, Crown, Aperture } from 'lucide-react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useChatStore } from '../stores/chatStore'
|
||||
@@ -377,7 +377,7 @@ const SessionItem = React.memo(function SessionItem({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`session-item ${isActive ? 'active' : ''} ${session.isMuted ? 'muted' : ''}`}
|
||||
className={`session-item ${isActive ? 'active' : ''}`}
|
||||
onClick={() => onSelect(session)}
|
||||
>
|
||||
<Avatar
|
||||
@@ -394,9 +394,8 @@ const SessionItem = React.memo(function SessionItem({
|
||||
<div className="session-bottom">
|
||||
<span className="session-summary">{session.summary || '暂无消息'}</span>
|
||||
<div className="session-badges">
|
||||
{session.isMuted && <BellOff size={12} className="mute-icon" />}
|
||||
{session.unreadCount > 0 && (
|
||||
<span className={`unread-badge ${session.isMuted ? 'muted' : ''}`}>
|
||||
<span className="unread-badge">
|
||||
{session.unreadCount > 99 ? '99+' : session.unreadCount}
|
||||
</span>
|
||||
)}
|
||||
@@ -414,7 +413,6 @@ const SessionItem = React.memo(function SessionItem({
|
||||
prevProps.session.unreadCount === nextProps.session.unreadCount &&
|
||||
prevProps.session.lastTimestamp === nextProps.session.lastTimestamp &&
|
||||
prevProps.session.sortTimestamp === nextProps.session.sortTimestamp &&
|
||||
prevProps.session.isMuted === nextProps.session.isMuted &&
|
||||
prevProps.isActive === nextProps.isActive
|
||||
)
|
||||
})
|
||||
@@ -1898,16 +1896,14 @@ function ChatPage(props: ChatPageProps) {
|
||||
if (!status) return session
|
||||
|
||||
const nextIsFolded = status.isFolded ?? session.isFolded
|
||||
const nextIsMuted = status.isMuted ?? session.isMuted
|
||||
if (nextIsFolded === session.isFolded && nextIsMuted === session.isMuted) {
|
||||
if (nextIsFolded === session.isFolded) {
|
||||
return session
|
||||
}
|
||||
|
||||
hasChanges = true
|
||||
return {
|
||||
...session,
|
||||
isFolded: nextIsFolded,
|
||||
isMuted: nextIsMuted
|
||||
isFolded: nextIsFolded
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -10,6 +10,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes noti-enter-center {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px) scale(0.7);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes noti-exit {
|
||||
0% {
|
||||
opacity: 1;
|
||||
@@ -24,6 +36,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes noti-exit-center {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px) scale(0.7);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
// Ensure the body background is transparent to let the rounded corners show
|
||||
background: transparent;
|
||||
@@ -41,6 +65,10 @@ body {
|
||||
// New notification slides in
|
||||
animation: noti-enter 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
will-change: transform, opacity;
|
||||
|
||||
&.anim-center {
|
||||
animation: noti-enter-center 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
}
|
||||
|
||||
#notification-prev {
|
||||
@@ -51,4 +79,8 @@ body {
|
||||
|
||||
// Ensure it stays behind
|
||||
z-index: 0 !important;
|
||||
|
||||
&.anim-center {
|
||||
animation: noti-exit-center 0.5s cubic-bezier(0.33, 1, 0.68, 1) forwards;
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,9 @@ import './NotificationWindow.scss'
|
||||
export default function NotificationWindow() {
|
||||
const [notification, setNotification] = useState<NotificationData | null>(null)
|
||||
const [prevNotification, setPrevNotification] = useState<NotificationData | null>(null)
|
||||
const [position, setPosition] = useState<string>('top-right')
|
||||
|
||||
// We need a ref to access the current notification inside the callback
|
||||
// We need a ref to access the current notification inside the callback
|
||||
// without satisfying the dependency array which would recreate the listener
|
||||
// Actually, setNotification(prev => ...) pattern is better, but we need the VALUE of current to set as prev.
|
||||
// So we use setNotification callback: setNotification(current => { ... return newNode })
|
||||
@@ -34,6 +35,11 @@ export default function NotificationWindow() {
|
||||
avatarUrl: data.avatarUrl
|
||||
}
|
||||
|
||||
// 获取位置配置
|
||||
if (data.position) {
|
||||
setPosition(data.position)
|
||||
}
|
||||
|
||||
// Set previous to current (ref)
|
||||
if (notificationRef.current) {
|
||||
setPrevNotification(notificationRef.current)
|
||||
@@ -117,6 +123,7 @@ export default function NotificationWindow() {
|
||||
<div
|
||||
id="notification-prev"
|
||||
key={prevNotification.id}
|
||||
className={position === 'top-center' ? 'anim-center' : ''}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 2, // Match padding
|
||||
@@ -131,7 +138,7 @@ export default function NotificationWindow() {
|
||||
data={prevNotification}
|
||||
onClose={() => { }} // No-op for background item
|
||||
onClick={() => { }}
|
||||
position="top-right"
|
||||
position={position as any}
|
||||
isStatic={true}
|
||||
initialVisible={true}
|
||||
/>
|
||||
@@ -143,6 +150,7 @@ export default function NotificationWindow() {
|
||||
<div
|
||||
id="notification-current"
|
||||
key={notification.id}
|
||||
className={position === 'top-center' ? 'anim-center' : ''}
|
||||
style={{
|
||||
position: 'relative', // Takes up space
|
||||
zIndex: 2,
|
||||
@@ -154,7 +162,7 @@ export default function NotificationWindow() {
|
||||
data={notification}
|
||||
onClose={handleClose}
|
||||
onClick={handleClick}
|
||||
position="top-right"
|
||||
position={position as any}
|
||||
isStatic={true}
|
||||
initialVisible={true}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.settings-modal-overlay {
|
||||
position: fixed;
|
||||
top: 41px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
@@ -102,7 +102,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const [transcribeLanguages, setTranscribeLanguages] = useState<string[]>(['zh'])
|
||||
|
||||
const [notificationEnabled, setNotificationEnabled] = useState(true)
|
||||
const [notificationPosition, setNotificationPosition] = useState<'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'>('top-right')
|
||||
const [notificationPosition, setNotificationPosition] = useState<'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center'>('top-right')
|
||||
const [notificationFilterMode, setNotificationFilterMode] = useState<'all' | 'whitelist' | 'blacklist'>('all')
|
||||
const [notificationFilterList, setNotificationFilterList] = useState<string[]>([])
|
||||
const [filterSearchKeyword, setFilterSearchKeyword] = useState('')
|
||||
@@ -1102,12 +1102,14 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
<span className="custom-select-value">
|
||||
{notificationPosition === 'top-right' ? '右上角' :
|
||||
notificationPosition === 'bottom-right' ? '右下角' :
|
||||
notificationPosition === 'top-left' ? '左上角' : '左下角'}
|
||||
notificationPosition === 'top-left' ? '左上角' :
|
||||
notificationPosition === 'top-center' ? '中间上方' : '左下角'}
|
||||
</span>
|
||||
<ChevronDown size={14} className={`custom-select-arrow ${positionDropdownOpen ? 'rotate' : ''}`} />
|
||||
</div>
|
||||
<div className={`custom-select-dropdown ${positionDropdownOpen ? 'open' : ''}`}>
|
||||
{[
|
||||
{ value: 'top-center', label: '中间上方' },
|
||||
{ value: 'top-right', label: '右上角' },
|
||||
{ value: 'bottom-right', label: '右下角' },
|
||||
{ value: 'top-left', label: '左上角' },
|
||||
@@ -1117,7 +1119,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
key={option.value}
|
||||
className={`custom-select-option ${notificationPosition === option.value ? 'selected' : ''}`}
|
||||
onClick={async () => {
|
||||
const val = option.value as 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
|
||||
const val = option.value as 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center'
|
||||
setNotificationPosition(val)
|
||||
setPositionDropdownOpen(false)
|
||||
await configService.setNotificationPosition(val)
|
||||
|
||||
Reference in New Issue
Block a user