diff --git a/pr_body.txt b/pr_body.txt index a72390c3..1f7ed264 100644 --- a/pr_body.txt +++ b/pr_body.txt @@ -1,28 +1,48 @@ -### AI: Resolves #118 +### AI: Resolves #120 -This Pull Request was automatically generated by OpenCode to address Issue #118. +This Pull Request was automatically generated by OpenCode to address Issue #120. ### 📝 AI Modification Summary & Conclusion: -# Fix: Prevent Premature `checkLogin()` Dialog on Component Activation +# Implementation Summary: Offline / Poor Network Alert Popup -## Files Modified - -### 1. `src/views/Friends.vue` (lines 50–62) -- **Import change**: Added `nextTick` to the `from 'vue'` import. -- **Before**: `onActivated(checkLogin)` — passed the function reference directly, executing synchronously on every activation. -- **After**: Wrapped in an async arrow function that calls `await nextTick()` first, then reads `storageManager.getObj('userInfo').value?.Nickname`. If the nickname is truthy (user is already logged in), it returns early without calling `checkLogin()`. Only when nickname is still null does it proceed to `checkLogin()`. - -### 2. `src/views/Notifications.vue` (lines 53–63) -- **Import change**: Added `nextTick` to the `from 'vue'` import. -- **Before**: `onActivated(() => { clearNotificationUnread(); checkLogin(); })`. -- **After**: The callback is now `async`. It runs `clearNotificationUnread()` immediately, then `await nextTick()` before checking the nickname guard. If the nickname exists, `checkLogin()` is skipped; otherwise it proceeds. +## Objective +Show a popup banner to alert users when they are offline or have a poor network connection, inform them that the application is reading data from cache, and support i18n for all messages. -## Technical Solution - -- **`await nextTick()`** ensures Vue's current rendering cycle and any internal reactivity/state initializations have settled before we attempt to read login state. This prevents the scenario where `onActivated` fires before the state layer has finished hydrating. -- **Early-return guard** (`if (nickname) return`): if `userInfo.Nickname` is already present in `localStorage` (read via `storageManager.getObj('userInfo')`), the login dialog is suppressed because the user is confirmed logged in. -- **Only calls `checkLogin()` when data is genuinely absent**, either because the user is not logged in or because the state truly hasn't been initialized yet. In either case, `checkLogin()` will show the dialog only when `showLoginLeader` is true (default) and `Nickname` is null. - -## Summary +## Files Modified -The root cause was that `onActivated` fired `checkLogin()` synchronously during the component's activation lifecycle hook. If the user state (stored in `localStorage` and accessed via the custom `storageManager`) had not yet been fully read/initialized, `Nickname` would be `null`, causing `checkLogin()` to display a blocking login dialog overlay. By deferring the check with `await nextTick()` and adding a guard for already-present user data, the dialog is no longer shown prematurely. +### 1. `src/i18n/en.ts`, `src/i18n/zh.ts`, `src/i18n/de.ts`, `src/i18n/fr.ts`, `src/i18n/ja.ts` +- Added a new `network` translation section in each language file with three keys: + - `offline` — "You are currently offline" (translated per locale) + - `poorConnection` — "Your network connection is unstable" (translated per locale) + - `usingCache` — "Reading data from cache" (translated per locale) + +### 2. `src/services/useNetworkStatus.ts` (new file) +- Created a Vue 3 Composition API composable that monitors: + - `navigator.onLine` + browser `online`/`offline` events for binary connectivity + - Network Information API (`navigator.connection`) for connection quality (`effectiveType` and `saveData`) +- Returns two reactive refs: + - `isOnline` — `true`/`false` reflecting browser online status + - `isPoorConnection` — `true` when `effectiveType` is `'slow-2g'` or `'2g'`, or `saveData` is enabled +- Properly registers and cleans up event listeners via `onMounted`/`onUnmounted` + +### 3. `src/components/utils/NetworkStatusBanner.vue` (new file) +- A fixed-position banner component displayed at the top of the viewport +- Uses Vue `` for smooth slide-in/out animation +- Two visual states: + - `--offline` (red background, white text) when the user is completely disconnected + - `--poor` (yellow background, dark text) when the connection is slow/unstable +- Shows an icon, a status message (`network.offline` or `network.poorConnection`), and a secondary cache notice (`network.usingCache`) +- Uses `useI18n()` for translated messages +- Accessible with `role="alert"` and `aria-live="assertive"` + +### 4. `src/App.vue` +- Imported and rendered `` before `` at the top of the app +- Imported `useNetworkStatus` composable and passed its reactive state (`isOnline`, `isPoorConnection`) as props to the banner + +## Technical Decisions +- **Composables pattern** — Followed the existing Vue 3 Composition API style used throughout the project +- **No external dependencies** — Relies only on browser-native APIs (`navigator.onLine`, `Network Information API`) and Vue built-ins +- **i18n-first** — All user-facing strings are defined in the `network` translation section and accessed via `useI18n()` `t()` function — consistent with the existing i18n architecture +- **Banner vs Notification** — Chose a top-of-page fixed banner (over Naive UI corner notifications) for maximum visibility and persistence +- **Graceful degradation** — If the Network Information API is unavailable, the composable gracefully falls back to only tracking online/offline events +- **Component pattern** — Follows the existing `CookieNotice.vue` component structure (reactive visibility, scoped CSS, flex layout) for consistency diff --git a/src/App.vue b/src/App.vue index 663b54a1..8f586477 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,5 +1,6 @@