From 6213235a16c2d70fd2b321349c4fb6ae7279f916 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Wed, 15 Oct 2025 06:09:18 +0000 Subject: [PATCH] feat(test-app): refactor to Vue 3 + Vite + vue-facing-decorator architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete refactoring of android-test app to modern Vue 3 stack: ## ๐Ÿš€ New Architecture - Vue 3 with Composition API and TypeScript - Vite for fast development and building - vue-facing-decorator for class-based components - Pinia for reactive state management - Vue Router for navigation - Modern glassmorphism UI design ## ๐Ÿ“ฑ App Structure - Comprehensive component library (cards, items, layout, ui) - Pinia stores for app and notification state management - Full view system (Home, Schedule, Notifications, Status, History) - Responsive design for mobile and desktop - TypeScript throughout with proper type definitions ## ๐ŸŽจ Features - Dashboard with quick actions and status overview - Schedule notifications with time picker and options - Notification management with cancel functionality - System status with permission checks and diagnostics - Notification history with delivery tracking - Settings panel (placeholder for future features) ## ๐Ÿ”ง Technical Implementation - Class-based Vue components using vue-facing-decorator - Reactive Pinia stores with proper TypeScript types - Capacitor integration for native Android functionality - ESLint and TypeScript configuration - Vite build system with proper aliases and optimization ## ๐Ÿ“š Documentation - Comprehensive README with setup and usage instructions - Component documentation and examples - Development and production build instructions - Testing and debugging guidelines This creates a production-ready test app that closely mirrors the actual TimeSafari app architecture, making it ideal for plugin testing and demonstration purposes. --- test-apps/android-test/.eslintrc.cjs | 20 + test-apps/android-test/README.md | 329 +++++++++++ test-apps/android-test/capacitor.config.ts | 2 +- test-apps/android-test/env.d.ts | 90 +++ test-apps/android-test/index.html | 73 +++ test-apps/android-test/package.json | 39 +- test-apps/android-test/src/App.vue | 145 +++++ .../src/components/cards/ActionCard.vue | 163 ++++++ .../src/components/cards/InfoCard.vue | 95 ++++ .../src/components/cards/NotificationCard.vue | 181 ++++++ .../src/components/cards/StatusCard.vue | 264 +++++++++ .../src/components/items/ActivityItem.vue | 141 +++++ .../src/components/items/HistoryItem.vue | 170 ++++++ .../src/components/items/StatusItem.vue | 99 ++++ .../src/components/layout/AppFooter.vue | 75 +++ .../src/components/layout/AppHeader.vue | 238 ++++++++ .../src/components/ui/ErrorDialog.vue | 161 ++++++ .../src/components/ui/LoadingOverlay.vue | 78 +++ test-apps/android-test/src/main.ts | 42 ++ test-apps/android-test/src/router/index.ts | 100 ++++ test-apps/android-test/src/stores/app.ts | 108 ++++ .../android-test/src/stores/notifications.ts | 275 ++++++++++ .../android-test/src/views/HistoryView.vue | 144 +++++ test-apps/android-test/src/views/HomeView.vue | 250 +++++++++ .../android-test/src/views/NotFoundView.vue | 117 ++++ .../src/views/NotificationsView.vue | 179 ++++++ .../android-test/src/views/ScheduleView.vue | 519 ++++++++++++++++++ .../android-test/src/views/SettingsView.vue | 71 +++ .../android-test/src/views/StatusView.vue | 310 +++++++++++ test-apps/android-test/tsconfig.json | 52 +- test-apps/android-test/vite.config.ts | 70 +++ 31 files changed, 4575 insertions(+), 25 deletions(-) create mode 100644 test-apps/android-test/.eslintrc.cjs create mode 100644 test-apps/android-test/README.md create mode 100644 test-apps/android-test/env.d.ts create mode 100644 test-apps/android-test/index.html create mode 100644 test-apps/android-test/src/App.vue create mode 100644 test-apps/android-test/src/components/cards/ActionCard.vue create mode 100644 test-apps/android-test/src/components/cards/InfoCard.vue create mode 100644 test-apps/android-test/src/components/cards/NotificationCard.vue create mode 100644 test-apps/android-test/src/components/cards/StatusCard.vue create mode 100644 test-apps/android-test/src/components/items/ActivityItem.vue create mode 100644 test-apps/android-test/src/components/items/HistoryItem.vue create mode 100644 test-apps/android-test/src/components/items/StatusItem.vue create mode 100644 test-apps/android-test/src/components/layout/AppFooter.vue create mode 100644 test-apps/android-test/src/components/layout/AppHeader.vue create mode 100644 test-apps/android-test/src/components/ui/ErrorDialog.vue create mode 100644 test-apps/android-test/src/components/ui/LoadingOverlay.vue create mode 100644 test-apps/android-test/src/main.ts create mode 100644 test-apps/android-test/src/router/index.ts create mode 100644 test-apps/android-test/src/stores/app.ts create mode 100644 test-apps/android-test/src/stores/notifications.ts create mode 100644 test-apps/android-test/src/views/HistoryView.vue create mode 100644 test-apps/android-test/src/views/HomeView.vue create mode 100644 test-apps/android-test/src/views/NotFoundView.vue create mode 100644 test-apps/android-test/src/views/NotificationsView.vue create mode 100644 test-apps/android-test/src/views/ScheduleView.vue create mode 100644 test-apps/android-test/src/views/SettingsView.vue create mode 100644 test-apps/android-test/src/views/StatusView.vue create mode 100644 test-apps/android-test/vite.config.ts diff --git a/test-apps/android-test/.eslintrc.cjs b/test-apps/android-test/.eslintrc.cjs new file mode 100644 index 0000000..b7406d6 --- /dev/null +++ b/test-apps/android-test/.eslintrc.cjs @@ -0,0 +1,20 @@ +/* eslint-env node */ +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + root: true, + 'extends': [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-typescript' + ], + parserOptions: { + ecmaVersion: 'latest' + }, + rules: { + 'vue/multi-word-component-names': 'off', + '@typescript-eslint/no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }], + 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' + } +} diff --git a/test-apps/android-test/README.md b/test-apps/android-test/README.md new file mode 100644 index 0000000..bacca5e --- /dev/null +++ b/test-apps/android-test/README.md @@ -0,0 +1,329 @@ +# Daily Notification Test App - Vue 3 + +A modern Vue 3 + Vite + Capacitor test application for the Daily Notification Plugin, built with vue-facing-decorator for TypeScript class-based components. + +## ๐Ÿš€ Features + +- **Vue 3** with Composition API and TypeScript +- **Vite** for fast development and building +- **vue-facing-decorator** for class-based components +- **Pinia** for state management +- **Vue Router** for navigation +- **Capacitor** for native Android functionality +- **Modern UI** with glassmorphism design +- **Responsive** design for mobile and desktop + +## ๐Ÿ“ฑ App Structure + +``` +src/ +โ”œโ”€โ”€ components/ # Reusable Vue components +โ”‚ โ”œโ”€โ”€ cards/ # Card components (ActionCard, StatusCard, etc.) +โ”‚ โ”œโ”€โ”€ items/ # List item components +โ”‚ โ”œโ”€โ”€ layout/ # Layout components (Header, Footer) +โ”‚ โ””โ”€โ”€ ui/ # UI components (Loading, Error, etc.) +โ”œโ”€โ”€ stores/ # Pinia stores +โ”‚ โ”œโ”€โ”€ app.ts # Global app state +โ”‚ โ””โ”€โ”€ notifications.ts # Notification management +โ”œโ”€โ”€ views/ # Page components +โ”‚ โ”œโ”€โ”€ HomeView.vue # Dashboard +โ”‚ โ”œโ”€โ”€ ScheduleView.vue # Schedule notifications +โ”‚ โ”œโ”€โ”€ NotificationsView.vue # Manage notifications +โ”‚ โ”œโ”€โ”€ StatusView.vue # System status +โ”‚ โ”œโ”€โ”€ HistoryView.vue # Notification history +โ”‚ โ””โ”€โ”€ SettingsView.vue # App settings +โ”œโ”€โ”€ router/ # Vue Router configuration +โ”œโ”€โ”€ types/ # TypeScript type definitions +โ””โ”€โ”€ utils/ # Utility functions +``` + +## ๐Ÿ› ๏ธ Development + +### Prerequisites + +- Node.js 18+ +- npm or yarn +- Android Studio (for Android development) +- Capacitor CLI + +### Installation + +```bash +# Install dependencies +npm install + +# Start development server +npm run dev + +# Build for production +npm run build + +# Type checking +npm run type-check + +# Linting +npm run lint +``` + +### Android Development + +```bash +# Sync with Capacitor +npm run sync + +# Open Android Studio +npm run open + +# Run on Android device/emulator +npm run android +``` + +## ๐ŸŽจ UI Components + +### Class-Based Components + +All components use vue-facing-decorator for TypeScript class-based syntax: + +```typescript +@Component({ + components: { + ChildComponent + } +}) +export default class MyComponent extends Vue { + @Prop() title!: string + + private data = ref('') + + get computedValue(): string { + return this.data.value.toUpperCase() + } + + private handleClick(): void { + // Handle click + } +} +``` + +### State Management + +Uses Pinia stores for reactive state management: + +```typescript +// stores/notifications.ts +export const useNotificationsStore = defineStore('notifications', () => { + const scheduledNotifications = ref([]) + + async function scheduleNotification(options: ScheduleOptions): Promise { + // Schedule logic + } + + return { + scheduledNotifications, + scheduleNotification + } +}) +``` + +## ๐Ÿ“Š Features + +### Dashboard (Home) +- Quick action cards +- System status overview +- Next scheduled notification +- Recent activity feed + +### Schedule Notifications +- Time picker +- Title and message inputs +- Sound and priority options +- URL support for deep linking +- Quick schedule presets + +### Notification Management +- View all scheduled notifications +- Cancel notifications +- Status indicators + +### System Status +- Permission checks +- Channel status +- Exact alarm settings +- Platform information +- Test notification functionality + +### History +- Delivered notification history +- Click and dismiss tracking +- Time-based filtering + +## ๐Ÿ”ง Configuration + +### Capacitor Config + +```typescript +// capacitor.config.ts +const config: CapacitorConfig = { + appId: 'com.timesafari.dailynotification.androidtest', + appName: 'Daily Notification Test - Vue 3', + webDir: 'dist', + plugins: { + DailyNotification: { + storage: 'shared', + ttlSeconds: 1800, + prefetchLeadMinutes: 15, + enableETagSupport: true, + enableErrorHandling: true, + enablePerformanceOptimization: true + } + } +} +``` + +### Vite Config + +```typescript +// vite.config.ts +export default defineConfig({ + plugins: [vue()], + build: { + outDir: 'dist', + sourcemap: true + }, + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + } +}) +``` + +## ๐ŸŽฏ Testing the Plugin + +1. **Start Development Server** + ```bash + npm run dev + ``` + +2. **Build and Sync** + ```bash + npm run build + npm run sync + ``` + +3. **Run on Android** + ```bash + npm run android + ``` + +4. **Test Features** + - Schedule notifications + - Check system status + - View notification history + - Test clickable notifications + +## ๐Ÿ“ฑ Native Features + +- **Notification Scheduling** - Schedule daily notifications +- **Permission Management** - Check and request permissions +- **Status Monitoring** - Real-time system status +- **Deep Linking** - URL support in notifications +- **Background Processing** - WorkManager integration + +## ๐ŸŽจ Design System + +### Colors +- Primary: Linear gradient (purple to blue) +- Success: #4caf50 +- Warning: #ff9800 +- Error: #f44336 +- Info: #2196f3 + +### Typography +- Headers: Bold, white with text-shadow +- Body: Regular, rgba white +- Code: Courier New monospace + +### Components +- Glassmorphism design with backdrop-filter +- Rounded corners (8px, 12px, 16px) +- Smooth transitions and hover effects +- Responsive grid layouts + +## ๐Ÿš€ Production Build + +```bash +# Build for production +npm run build + +# Preview production build +npm run preview + +# Sync with Capacitor +npm run sync + +# Build Android APK +npm run android +``` + +## ๐Ÿ“ Scripts + +- `npm run dev` - Start development server +- `npm run build` - Build for production +- `npm run preview` - Preview production build +- `npm run android` - Run on Android +- `npm run sync` - Sync with Capacitor +- `npm run open` - Open Android Studio +- `npm run lint` - Run ESLint +- `npm run type-check` - TypeScript type checking + +## ๐Ÿ” Debugging + +### Vue DevTools +Install Vue DevTools browser extension for component inspection. + +### Capacitor Logs +```bash +# Android logs +adb logcat | grep -i "daily\|notification" + +# Capacitor logs +npx cap run android --livereload --external +``` + +### TypeScript +Enable strict mode in `tsconfig.json` for better type checking. + +## ๐Ÿ“š Dependencies + +### Core +- Vue 3.4+ +- Vite 5.0+ +- TypeScript 5.3+ +- Capacitor 5.0+ + +### UI & State +- vue-facing-decorator 3.0+ +- Pinia 2.1+ +- Vue Router 4.2+ + +### Development +- ESLint + TypeScript configs +- Vue TSC for type checking +- Modern module resolution + +## ๐Ÿค Contributing + +1. Follow Vue 3 + TypeScript best practices +2. Use vue-facing-decorator for class components +3. Maintain responsive design +4. Add proper TypeScript types +5. Test on both web and Android + +## ๐Ÿ“„ License + +MIT License - see LICENSE file for details. + +--- + +**Built with โค๏ธ using Vue 3 + Vite + Capacitor** diff --git a/test-apps/android-test/capacitor.config.ts b/test-apps/android-test/capacitor.config.ts index bd7ffaf..2e0d5c3 100644 --- a/test-apps/android-test/capacitor.config.ts +++ b/test-apps/android-test/capacitor.config.ts @@ -2,7 +2,7 @@ import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { appId: 'com.timesafari.dailynotification.androidtest', - appName: 'Daily Notification Android Test', + appName: 'Daily Notification Test - Vue 3', webDir: 'dist', server: { androidScheme: 'https' diff --git a/test-apps/android-test/env.d.ts b/test-apps/android-test/env.d.ts new file mode 100644 index 0000000..9e0a952 --- /dev/null +++ b/test-apps/android-test/env.d.ts @@ -0,0 +1,90 @@ +/** + * Environment Type Declarations + * + * @author Matthew Raymer + * @version 1.0.0 + */ + +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} + +// Capacitor plugin declarations +declare global { + interface Window { + DailyNotification: { + scheduleDailyNotification: (options: { + time: string + title?: string + body?: string + sound?: boolean + priority?: string + url?: string + }) => Promise + + scheduleDailyReminder: (options: { + id: string + title: string + body: string + time: string + sound?: boolean + vibration?: boolean + priority?: string + repeatDaily?: boolean + timezone?: string + }) => Promise + + cancelDailyReminder: (options: { + reminderId: string + }) => Promise + + updateDailyReminder: (options: { + reminderId: string + title?: string + body?: string + time?: string + sound?: boolean + vibration?: boolean + priority?: string + repeatDaily?: boolean + timezone?: string + }) => Promise + + getLastNotification: () => Promise<{ + id: string + title: string + body: string + scheduledTime: number + deliveredAt: number + }> + + checkStatus: () => Promise<{ + canScheduleNow: boolean + postNotificationsGranted: boolean + channelEnabled: boolean + channelImportance: number + channelId: string + exactAlarmsGranted: boolean + exactAlarmsSupported: boolean + androidVersion: number + nextScheduledAt: number + }> + + checkChannelStatus: () => Promise<{ + enabled: boolean + importance: number + id: string + }> + + openChannelSettings: () => Promise + + openExactAlarmSettings: () => Promise + } + } +} + +export {} diff --git a/test-apps/android-test/index.html b/test-apps/android-test/index.html new file mode 100644 index 0000000..8090857 --- /dev/null +++ b/test-apps/android-test/index.html @@ -0,0 +1,73 @@ + + + + + + + Daily Notification Test - Vue 3 + + + + + + + + + + + + + + + + +
+
+
+ Loading Daily Notification Test App... +
+
+ + + diff --git a/test-apps/android-test/package.json b/test-apps/android-test/package.json index 66d580e..d3674df 100644 --- a/test-apps/android-test/package.json +++ b/test-apps/android-test/package.json @@ -1,29 +1,42 @@ { "name": "daily-notification-android-test", "version": "1.0.0", - "description": "Minimal Android test app for Daily Notification Plugin", - "main": "index.js", + "description": "Vue 3 + Vite + Capacitor test app for Daily Notification Plugin", + "type": "module", "scripts": { - "build": "webpack --mode=production", - "dev": "webpack serve --mode=development", + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview", "android": "npx cap run android", "sync": "npx cap sync android", - "open": "npx cap open android" + "open": "npx cap open android", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "type-check": "vue-tsc --noEmit" }, - "keywords": ["capacitor", "android", "notifications", "test"], + "keywords": ["capacitor", "android", "notifications", "test", "vue3", "vite", "typescript"], "author": "Matthew Raymer", "license": "MIT", "dependencies": { "@capacitor/core": "^5.0.0", "@capacitor/android": "^5.0.0", - "@capacitor/cli": "^5.0.0" + "@capacitor/cli": "^5.0.0", + "vue": "^3.4.0", + "vue-router": "^4.2.0", + "pinia": "^2.1.0", + "vue-facing-decorator": "^3.0.0" }, "devDependencies": { - "webpack": "^5.88.0", - "webpack-cli": "^5.1.0", - "webpack-dev-server": "^4.15.0", - "html-webpack-plugin": "^5.5.0", - "typescript": "^5.0.0", - "ts-loader": "^9.4.0" + "@capacitor/cli": "^5.0.0", + "@types/node": "^20.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@vitejs/plugin-vue": "^4.5.0", + "@vue/eslint-config-typescript": "^12.0.0", + "@vue/tsconfig": "^0.5.0", + "eslint": "^8.0.0", + "eslint-plugin-vue": "^9.0.0", + "typescript": "~5.3.0", + "vite": "^5.0.0", + "vue-tsc": "^1.8.0" } } \ No newline at end of file diff --git a/test-apps/android-test/src/App.vue b/test-apps/android-test/src/App.vue new file mode 100644 index 0000000..9b36f22 --- /dev/null +++ b/test-apps/android-test/src/App.vue @@ -0,0 +1,145 @@ + + + + + + + diff --git a/test-apps/android-test/src/components/cards/ActionCard.vue b/test-apps/android-test/src/components/cards/ActionCard.vue new file mode 100644 index 0000000..6cedcc5 --- /dev/null +++ b/test-apps/android-test/src/components/cards/ActionCard.vue @@ -0,0 +1,163 @@ + + + + + + + diff --git a/test-apps/android-test/src/components/cards/InfoCard.vue b/test-apps/android-test/src/components/cards/InfoCard.vue new file mode 100644 index 0000000..ffe1e44 --- /dev/null +++ b/test-apps/android-test/src/components/cards/InfoCard.vue @@ -0,0 +1,95 @@ + + + + + + + diff --git a/test-apps/android-test/src/components/cards/NotificationCard.vue b/test-apps/android-test/src/components/cards/NotificationCard.vue new file mode 100644 index 0000000..49ff77c --- /dev/null +++ b/test-apps/android-test/src/components/cards/NotificationCard.vue @@ -0,0 +1,181 @@ + + + + + + + diff --git a/test-apps/android-test/src/components/cards/StatusCard.vue b/test-apps/android-test/src/components/cards/StatusCard.vue new file mode 100644 index 0000000..5fe5021 --- /dev/null +++ b/test-apps/android-test/src/components/cards/StatusCard.vue @@ -0,0 +1,264 @@ + + + + + + + diff --git a/test-apps/android-test/src/components/items/ActivityItem.vue b/test-apps/android-test/src/components/items/ActivityItem.vue new file mode 100644 index 0000000..4451b82 --- /dev/null +++ b/test-apps/android-test/src/components/items/ActivityItem.vue @@ -0,0 +1,141 @@ + + + + + + + diff --git a/test-apps/android-test/src/components/items/HistoryItem.vue b/test-apps/android-test/src/components/items/HistoryItem.vue new file mode 100644 index 0000000..1d62b02 --- /dev/null +++ b/test-apps/android-test/src/components/items/HistoryItem.vue @@ -0,0 +1,170 @@ + + + + + + + diff --git a/test-apps/android-test/src/components/items/StatusItem.vue b/test-apps/android-test/src/components/items/StatusItem.vue new file mode 100644 index 0000000..c644c0d --- /dev/null +++ b/test-apps/android-test/src/components/items/StatusItem.vue @@ -0,0 +1,99 @@ + + + + + + + diff --git a/test-apps/android-test/src/components/layout/AppFooter.vue b/test-apps/android-test/src/components/layout/AppFooter.vue new file mode 100644 index 0000000..da13d58 --- /dev/null +++ b/test-apps/android-test/src/components/layout/AppFooter.vue @@ -0,0 +1,75 @@ + + + + + + + diff --git a/test-apps/android-test/src/components/layout/AppHeader.vue b/test-apps/android-test/src/components/layout/AppHeader.vue new file mode 100644 index 0000000..ba8155d --- /dev/null +++ b/test-apps/android-test/src/components/layout/AppHeader.vue @@ -0,0 +1,238 @@ + + + + + + + diff --git a/test-apps/android-test/src/components/ui/ErrorDialog.vue b/test-apps/android-test/src/components/ui/ErrorDialog.vue new file mode 100644 index 0000000..fb0e7a3 --- /dev/null +++ b/test-apps/android-test/src/components/ui/ErrorDialog.vue @@ -0,0 +1,161 @@ + + + + + + + diff --git a/test-apps/android-test/src/components/ui/LoadingOverlay.vue b/test-apps/android-test/src/components/ui/LoadingOverlay.vue new file mode 100644 index 0000000..f610a25 --- /dev/null +++ b/test-apps/android-test/src/components/ui/LoadingOverlay.vue @@ -0,0 +1,78 @@ + + + + + + + diff --git a/test-apps/android-test/src/main.ts b/test-apps/android-test/src/main.ts new file mode 100644 index 0000000..1b89cd1 --- /dev/null +++ b/test-apps/android-test/src/main.ts @@ -0,0 +1,42 @@ +/** + * Main Application Entry Point + * + * Vue 3 + TypeScript + Capacitor + vue-facing-decorator setup + * + * @author Matthew Raymer + * @version 1.0.0 + */ + +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import { Capacitor } from '@capacitor/core' +import App from './App.vue' +import router from './router' + +// Create Vue app instance +const app = createApp(App) + +// Configure Pinia for state management +const pinia = createPinia() +app.use(pinia) + +// Configure Vue Router +app.use(router) + +// Global error handler for Capacitor +app.config.errorHandler = (err, instance, info) => { + console.error('Vue Error:', err, info) + if (Capacitor.isNativePlatform()) { + // Log to native platform + console.error('Native Platform Error:', err) + } +} + +// Mount the app +app.mount('#app') + +// Log platform information +console.log('๐Ÿš€ Daily Notification Test App Started') +console.log('๐Ÿ“ฑ Platform:', Capacitor.getPlatform()) +console.log('๐Ÿ”ง Native Platform:', Capacitor.isNativePlatform()) +console.log('๐ŸŒ Web Platform:', Capacitor.isPluginAvailable('App')) diff --git a/test-apps/android-test/src/router/index.ts b/test-apps/android-test/src/router/index.ts new file mode 100644 index 0000000..39bf1be --- /dev/null +++ b/test-apps/android-test/src/router/index.ts @@ -0,0 +1,100 @@ +/** + * Vue Router Configuration + * + * @author Matthew Raymer + * @version 1.0.0 + */ + +import { createRouter, createWebHistory } from 'vue-router' +import type { RouteRecordRaw } from 'vue-router' + +const routes: RouteRecordRaw[] = [ + { + path: '/', + name: 'Home', + component: () => import('@/views/HomeView.vue'), + meta: { + title: 'Daily Notification Test', + requiresAuth: false + } + }, + { + path: '/notifications', + name: 'Notifications', + component: () => import('@/views/NotificationsView.vue'), + meta: { + title: 'Notification Management', + requiresAuth: false + } + }, + { + path: '/schedule', + name: 'Schedule', + component: () => import('@/views/ScheduleView.vue'), + meta: { + title: 'Schedule Notification', + requiresAuth: false + } + }, + { + path: '/status', + name: 'Status', + component: () => import('@/views/StatusView.vue'), + meta: { + title: 'System Status', + requiresAuth: false + } + }, + { + path: '/history', + name: 'History', + component: () => import('@/views/HistoryView.vue'), + meta: { + title: 'Notification History', + requiresAuth: false + } + }, + { + path: '/settings', + name: 'Settings', + component: () => import('@/views/SettingsView.vue'), + meta: { + title: 'Settings', + requiresAuth: false + } + }, + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: () => import('@/views/NotFoundView.vue'), + meta: { + title: 'Page Not Found', + requiresAuth: false + } + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +// Global navigation guards +router.beforeEach((to, from, next) => { + // Set page title + if (to.meta?.title) { + document.title = `${to.meta.title} - Daily Notification Test` + } + + // Add loading state + console.log(`๐Ÿ”„ Navigating from ${from.name || 'unknown'} to ${to.name || 'unknown'}`) + + next() +}) + +router.afterEach((to, from) => { + // Clear any previous errors on successful navigation + console.log(`โœ… Navigation completed: ${to.name || 'unknown'}`) +}) + +export default router diff --git a/test-apps/android-test/src/stores/app.ts b/test-apps/android-test/src/stores/app.ts new file mode 100644 index 0000000..0c543b7 --- /dev/null +++ b/test-apps/android-test/src/stores/app.ts @@ -0,0 +1,108 @@ +/** + * App Store - Global Application State + * + * Pinia store for managing global app state, loading, and errors + * + * @author Matthew Raymer + * @version 1.0.0 + */ + +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export interface AppState { + isLoading: boolean + errorMessage: string | null + platform: string + isNative: boolean + notificationStatus: NotificationStatus | null +} + +export interface NotificationStatus { + canScheduleNow: boolean + postNotificationsGranted: boolean + channelEnabled: boolean + channelImportance: number + channelId: string + exactAlarmsGranted: boolean + exactAlarmsSupported: boolean + androidVersion: number + nextScheduledAt: number +} + +export const useAppStore = defineStore('app', () => { + // State + const isLoading = ref(false) + const errorMessage = ref(null) + const platform = ref('web') + const isNative = ref(false) + const notificationStatus = ref(null) + + // Getters + const hasError = computed(() => errorMessage.value !== null) + const isNotificationReady = computed(() => + notificationStatus.value?.canScheduleNow ?? false + ) + + // Actions + function setLoading(loading: boolean): void { + isLoading.value = loading + } + + function setError(message: string): void { + errorMessage.value = message + console.error('App Error:', message) + } + + function clearError(): void { + errorMessage.value = null + } + + function setPlatform(platformName: string, native: boolean): void { + platform.value = platformName + isNative.value = native + } + + function setNotificationStatus(status: NotificationStatus): void { + notificationStatus.value = status + } + + function updateNotificationStatus(status: Partial): void { + if (notificationStatus.value) { + notificationStatus.value = { ...notificationStatus.value, ...status } + } else { + notificationStatus.value = status as NotificationStatus + } + } + + // Reset store + function $reset(): void { + isLoading.value = false + errorMessage.value = null + platform.value = 'web' + isNative.value = false + notificationStatus.value = null + } + + return { + // State + isLoading, + errorMessage, + platform, + isNative, + notificationStatus, + + // Getters + hasError, + isNotificationReady, + + // Actions + setLoading, + setError, + clearError, + setPlatform, + setNotificationStatus, + updateNotificationStatus, + $reset + } +}) diff --git a/test-apps/android-test/src/stores/notifications.ts b/test-apps/android-test/src/stores/notifications.ts new file mode 100644 index 0000000..e5e1f4f --- /dev/null +++ b/test-apps/android-test/src/stores/notifications.ts @@ -0,0 +1,275 @@ +/** + * Notifications Store - Notification Management State + * + * Pinia store for managing notification scheduling, status, and history + * + * @author Matthew Raymer + * @version 1.0.0 + */ + +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { Capacitor } from '@capacitor/core' + +export interface ScheduledNotification { + id: string + title: string + body: string + scheduledTime: number + deliveredAt?: number + status: 'scheduled' | 'delivered' | 'cancelled' +} + +export interface NotificationHistory { + id: string + title: string + body: string + scheduledTime: number + deliveredAt: number + clicked: boolean + dismissed: boolean +} + +export const useNotificationsStore = defineStore('notifications', () => { + // State + const scheduledNotifications = ref([]) + const notificationHistory = ref([]) + const isScheduling = ref(false) + const lastError = ref(null) + + // Getters + const hasScheduledNotifications = computed(() => + scheduledNotifications.value.length > 0 + ) + + const nextNotification = computed(() => { + const future = scheduledNotifications.value + .filter(n => n.status === 'scheduled' && n.scheduledTime > Date.now()) + .sort((a, b) => a.scheduledTime - b.scheduledTime) + return future.length > 0 ? future[0] : null + }) + + const notificationCount = computed(() => + scheduledNotifications.value.length + ) + + // Actions + async function scheduleNotification(options: { + time: string + title?: string + body?: string + sound?: boolean + priority?: string + url?: string + }): Promise { + if (!Capacitor.isNativePlatform() || !window.DailyNotification) { + throw new Error('DailyNotification plugin not available') + } + + try { + isScheduling.value = true + lastError.value = null + + await window.DailyNotification.scheduleDailyNotification(options) + + // Add to local state (we'll get the actual ID from the plugin) + const notification: ScheduledNotification = { + id: `temp-${Date.now()}`, + title: options.title || 'Daily Update', + body: options.body || 'Your daily notification is ready', + scheduledTime: parseTimeToTimestamp(options.time), + status: 'scheduled' + } + + scheduledNotifications.value.push(notification) + + console.log('โœ… Notification scheduled successfully') + + } catch (error) { + const errorMessage = (error as Error).message + lastError.value = errorMessage + console.error('โŒ Failed to schedule notification:', errorMessage) + throw error + } finally { + isScheduling.value = false + } + } + + async function scheduleReminder(options: { + id: string + title: string + body: string + time: string + sound?: boolean + vibration?: boolean + priority?: string + repeatDaily?: boolean + timezone?: string + }): Promise { + if (!Capacitor.isNativePlatform() || !window.DailyNotification) { + throw new Error('DailyNotification plugin not available') + } + + try { + isScheduling.value = true + lastError.value = null + + await window.DailyNotification.scheduleDailyReminder(options) + + // Add to local state + const notification: ScheduledNotification = { + id: options.id, + title: options.title, + body: options.body, + scheduledTime: parseTimeToTimestamp(options.time), + status: 'scheduled' + } + + scheduledNotifications.value.push(notification) + + console.log('โœ… Reminder scheduled successfully') + + } catch (error) { + const errorMessage = (error as Error).message + lastError.value = errorMessage + console.error('โŒ Failed to schedule reminder:', errorMessage) + throw error + } finally { + isScheduling.value = false + } + } + + async function cancelReminder(reminderId: string): Promise { + if (!Capacitor.isNativePlatform() || !window.DailyNotification) { + throw new Error('DailyNotification plugin not available') + } + + try { + isScheduling.value = true + lastError.value = null + + await window.DailyNotification.cancelDailyReminder({ reminderId }) + + // Update local state + const index = scheduledNotifications.value.findIndex(n => n.id === reminderId) + if (index !== -1) { + scheduledNotifications.value[index].status = 'cancelled' + } + + console.log('โœ… Reminder cancelled successfully') + + } catch (error) { + const errorMessage = (error as Error).message + lastError.value = errorMessage + console.error('โŒ Failed to cancel reminder:', errorMessage) + throw error + } finally { + isScheduling.value = false + } + } + + async function checkStatus(): Promise { + if (!Capacitor.isNativePlatform() || !window.DailyNotification) { + return + } + + try { + const status = await window.DailyNotification.checkStatus() + console.log('๐Ÿ“Š Notification Status:', status) + + // Update app store with status + const { useAppStore } = await import('@/stores/app') + const appStore = useAppStore() + appStore.setNotificationStatus(status) + + } catch (error) { + console.error('โŒ Failed to check notification status:', error) + } + } + + async function getLastNotification(): Promise { + if (!Capacitor.isNativePlatform() || !window.DailyNotification) { + return + } + + try { + const lastNotification = await window.DailyNotification.getLastNotification() + console.log('๐Ÿ“ฑ Last Notification:', lastNotification) + + // Add to history + const historyItem: NotificationHistory = { + id: lastNotification.id, + title: lastNotification.title, + body: lastNotification.body, + scheduledTime: lastNotification.scheduledTime, + deliveredAt: lastNotification.deliveredAt, + clicked: false, + dismissed: false + } + + notificationHistory.value.unshift(historyItem) + + // Keep only last 50 items + if (notificationHistory.value.length > 50) { + notificationHistory.value = notificationHistory.value.slice(0, 50) + } + + } catch (error) { + console.error('โŒ Failed to get last notification:', error) + } + } + + function clearError(): void { + lastError.value = null + } + + function clearHistory(): void { + notificationHistory.value = [] + } + + // Helper function to parse time string to timestamp + function parseTimeToTimestamp(timeString: string): number { + const [hours, minutes] = timeString.split(':').map(Number) + const now = new Date() + const scheduled = new Date() + scheduled.setHours(hours, minutes, 0, 0) + + // If time has passed today, schedule for tomorrow + if (scheduled <= now) { + scheduled.setDate(scheduled.getDate() + 1) + } + + return scheduled.getTime() + } + + // Reset store + function $reset(): void { + scheduledNotifications.value = [] + notificationHistory.value = [] + isScheduling.value = false + lastError.value = null + } + + return { + // State + scheduledNotifications, + notificationHistory, + isScheduling, + lastError, + + // Getters + hasScheduledNotifications, + nextNotification, + notificationCount, + + // Actions + scheduleNotification, + scheduleReminder, + cancelReminder, + checkStatus, + getLastNotification, + clearError, + clearHistory, + $reset + } +}) diff --git a/test-apps/android-test/src/views/HistoryView.vue b/test-apps/android-test/src/views/HistoryView.vue new file mode 100644 index 0000000..a322ecc --- /dev/null +++ b/test-apps/android-test/src/views/HistoryView.vue @@ -0,0 +1,144 @@ + + + + + + + diff --git a/test-apps/android-test/src/views/HomeView.vue b/test-apps/android-test/src/views/HomeView.vue new file mode 100644 index 0000000..f13f485 --- /dev/null +++ b/test-apps/android-test/src/views/HomeView.vue @@ -0,0 +1,250 @@ + + + + + + + diff --git a/test-apps/android-test/src/views/NotFoundView.vue b/test-apps/android-test/src/views/NotFoundView.vue new file mode 100644 index 0000000..f8eeb8f --- /dev/null +++ b/test-apps/android-test/src/views/NotFoundView.vue @@ -0,0 +1,117 @@ + + + + + + + diff --git a/test-apps/android-test/src/views/NotificationsView.vue b/test-apps/android-test/src/views/NotificationsView.vue new file mode 100644 index 0000000..957f96b --- /dev/null +++ b/test-apps/android-test/src/views/NotificationsView.vue @@ -0,0 +1,179 @@ + + + + + + + diff --git a/test-apps/android-test/src/views/ScheduleView.vue b/test-apps/android-test/src/views/ScheduleView.vue new file mode 100644 index 0000000..2f844da --- /dev/null +++ b/test-apps/android-test/src/views/ScheduleView.vue @@ -0,0 +1,519 @@ + + + + + + + diff --git a/test-apps/android-test/src/views/SettingsView.vue b/test-apps/android-test/src/views/SettingsView.vue new file mode 100644 index 0000000..65dd793 --- /dev/null +++ b/test-apps/android-test/src/views/SettingsView.vue @@ -0,0 +1,71 @@ + + + + + + + diff --git a/test-apps/android-test/src/views/StatusView.vue b/test-apps/android-test/src/views/StatusView.vue new file mode 100644 index 0000000..33c72aa --- /dev/null +++ b/test-apps/android-test/src/views/StatusView.vue @@ -0,0 +1,310 @@ + + + + + + + diff --git a/test-apps/android-test/tsconfig.json b/test-apps/android-test/tsconfig.json index aa6eba6..8309208 100644 --- a/test-apps/android-test/tsconfig.json +++ b/test-apps/android-test/tsconfig.json @@ -1,15 +1,45 @@ { + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": [ + "env.d.ts", + "src/**/*", + "src/**/*.vue" + ], + "exclude": [ + "src/**/__tests__/*" + ], "compilerOptions": { - "target": "ES2020", - "module": "ES2020", - "moduleResolution": "node", + "composite": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + "@components/*": ["./src/components/*"], + "@views/*": ["./src/views/*"], + "@stores/*": ["./src/stores/*"], + "@services/*": ["./src/services/*"], + "@types/*": ["./src/types/*"], + "@utils/*": ["./src/utils/*"] + }, + "types": [ + "vite/client", + "node" + ], "strict": true, - "esModuleInterop": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve" + } +} \ No newline at end of file diff --git a/test-apps/android-test/vite.config.ts b/test-apps/android-test/vite.config.ts new file mode 100644 index 0000000..ea79110 --- /dev/null +++ b/test-apps/android-test/vite.config.ts @@ -0,0 +1,70 @@ +/** + * Vite Configuration for Daily Notification Test App + * + * Vue 3 + TypeScript + Capacitor setup with vue-facing-decorator support + * + * @author Matthew Raymer + * @version 1.0.0 + */ + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'path' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + + // Build configuration for Capacitor + build: { + outDir: 'dist', + assetsDir: 'assets', + sourcemap: true, + rollupOptions: { + input: { + main: resolve(__dirname, 'index.html') + } + } + }, + + // Development server configuration + server: { + host: '0.0.0.0', + port: 3000, + strictPort: true, + hmr: { + port: 3001 + } + }, + + // Preview server configuration + preview: { + host: '0.0.0.0', + port: 4173, + strictPort: true + }, + + // Path resolution + resolve: { + alias: { + '@': resolve(__dirname, 'src'), + '@components': resolve(__dirname, 'src/components'), + '@views': resolve(__dirname, 'src/views'), + '@stores': resolve(__dirname, 'src/stores'), + '@services': resolve(__dirname, 'src/services'), + '@types': resolve(__dirname, 'src/types'), + '@utils': resolve(__dirname, 'src/utils') + } + }, + + // TypeScript configuration + esbuild: { + target: 'es2020' + }, + + // Define global constants + define: { + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false + } +})