new file: MOBILE_FEATURES.md

This commit is contained in:
MrPiglr 2025-12-26 22:53:51 -07:00
parent ebcef5da2e
commit 24b7e83fd2
123 changed files with 6166 additions and 24 deletions

16
.vscode/launch.json.backup vendored Normal file
View file

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}

7
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,7 @@
{
"builder.serverUrl": "",
"builder.command": "npm run dev",
"builder.runDevServer": true,
"builder.autoDetectDevServer": true,
"builder.launchType": "desktop"
}

199
DESKTOP_MOBILE_SETUP.md Normal file
View file

@ -0,0 +1,199 @@
# AeThex OS - Desktop & Mobile Apps Setup
## Overview
AeThex OS now supports native desktop and mobile applications:
- **Desktop**: Tauri (Windows, macOS, Linux)
- **Mobile**: Capacitor (iOS, Android)
## 🖥️ Desktop App (Tauri)
### Prerequisites
- [Rust](https://www.rust-lang.org/tools/install) installed
- Windows: Microsoft Visual Studio C++ Build Tools
- macOS: Xcode Command Line Tools
- Linux: webkit2gtk, AppIndicator, etc.
### Development
```bash
# Start desktop app in development mode
npm run dev:tauri
```
This will:
1. Start the Vite dev server on port 5000
2. Launch the Tauri desktop window
3. Enable hot-reload for both frontend and Rust changes
### Building for Windows
```bash
# Build Windows executable
npm run build:tauri
```
Output location: `src-tauri/target/release/`
- `.exe` installer in `src-tauri/target/release/bundle/`
### Configuration
Edit [src-tauri/tauri.conf.json](src-tauri/tauri.conf.json) to customize:
- Window size, title, and behavior
- App identifier and version
- Build targets and icons
- Security policies
## 📱 Mobile Apps (Capacitor)
### Prerequisites
#### For Android:
- [Android Studio](https://developer.android.com/studio)
- Android SDK
- Java Development Kit (JDK) 17+
#### For iOS (macOS only):
- [Xcode](https://developer.apple.com/xcode/)
- Xcode Command Line Tools
- CocoaPods: `sudo gem install cocoapods`
### Initial Setup
1. **Build the web app first:**
```bash
npm run build
```
2. **Add mobile platforms:**
```bash
# Add Android platform
npx cap add android
# Add iOS platform (macOS only)
npx cap add ios
```
### Development Workflow
1. **Make changes to your web app** in `client/src/`
2. **Build and sync to mobile:**
```bash
npm run build:mobile
```
This builds the web app and copies it to native projects.
3. **Open in native IDE:**
```bash
# Open Android Studio
npm run android
# Open Xcode (macOS only)
npm run ios
```
4. **Run on device/emulator** from Android Studio or Xcode
### Live Reload (Optional)
For faster development, configure Capacitor to load from local dev server:
```typescript
// capacitor.config.ts
server: {
url: 'http://192.168.1.XXX:5000', // Your local IP
cleartext: true
}
```
Then run `npx cap sync` and use the native IDE to run.
## 🚀 Quick Start Commands
### Desktop Development
```bash
npm run dev:tauri # Launch desktop app (dev mode)
npm run build:tauri # Build production desktop app
```
### Mobile Development
```bash
npm run build # Build web assets
npm run build:mobile # Build and sync to mobile
npm run android # Open Android Studio
npm run ios # Open Xcode (macOS only)
```
## 📦 Project Structure
```
AeThex-OS/
├── src-tauri/ # Tauri desktop app
│ ├── src/
│ │ └── main.rs # Rust main process
│ ├── tauri.conf.json # Tauri configuration
│ └── Cargo.toml # Rust dependencies
├── android/ # Android native project (after cap add)
├── ios/ # iOS native project (after cap add)
├── capacitor.config.ts # Capacitor configuration
├── client/ # React web app (shared by all platforms)
└── dist/client/ # Built web assets
```
## 🔧 Platform-Specific Features
### Tauri (Desktop)
- Native system tray
- File system access
- Custom window controls
- Native menus
- System notifications
- Auto-updates
### Capacitor (Mobile)
- Camera access
- Geolocation
- Push notifications
- Native storage
- Share API
- Haptics
## 🛠️ Troubleshooting
### Tauri won't build
- Ensure Rust is installed: `rustc --version`
- Install C++ build tools on Windows
- Run `cargo clean` in `src-tauri/` directory
### Capacitor sync fails
- Build web app first: `npm run build`
- Check `dist/client/` directory exists
- Verify `capacitor.config.ts` webDir path
### Android Studio errors
- Update Android SDK and build tools
- Sync Gradle files
- Check Java version: `java -version` (needs 17+)
### iOS build fails (macOS)
- Update Xcode to latest version
- Install CocoaPods: `sudo gem install cocoapods`
- Run `npx cap sync ios` and `cd ios/App && pod install`
## 📝 Notes
- Desktop and mobile apps share the same React codebase in `client/src/`
- Server code in `server/` is NOT included in desktop/mobile builds
- For production mobile apps, configure API endpoints in `.env`
- Desktop app uses native webview (not Chromium bundle)
- File size: Tauri ~10-20MB, Capacitor varies by platform
## 🔐 Security
- Desktop: Update `src-tauri/tauri.conf.json` security settings
- Mobile: Configure Content Security Policy in Capacitor config
- Both: Use HTTPS for API connections in production
## 📚 Additional Resources
- [Tauri Documentation](https://tauri.app/v1/guides/)
- [Capacitor Documentation](https://capacitorjs.com/docs)
- [Rust Installation](https://www.rust-lang.org/tools/install)
- [Android Studio Setup](https://developer.android.com/studio/install)
- [Xcode Setup](https://developer.apple.com/xcode/)

234
MOBILE_BUILD_COMPLETE.md Normal file
View file

@ -0,0 +1,234 @@
# Mobile UI Implementation Complete! 🎉
## What's Been Built
### ✅ Platform-Specific Layouts
AeThex OS now has completely different UIs for mobile vs desktop/web:
#### 📱 **Mobile Version** (iOS/Android via Capacitor)
- **App Launcher Screen**: Grid of app icons with touch-friendly 56px targets
- **Full-Screen Windows**: Each app opens in fullscreen with swipe gestures
- **Bottom Navigation**: 4-tab bar (Home, Alerts, Settings, Account)
- **Mobile Status Bar**: Time, battery, WiFi indicators
- **Slide-Up Panels**: Notifications and account menus slide from bottom
- **Quick Stats Widget**: Dashboard cards showing system status
- **Optimized Touch Targets**: All buttons 48px+ for easy tapping
- **Simplified Navigation**: Back button instead of window controls
#### 🖥️ **Desktop/Web Version** (Original)
- **Windowed Interface**: Multi-window desktop environment
- **Taskbar**: Bottom taskbar with app launcher and system tray
- **Desktop Icons**: Grid of launchable apps
- **Window Management**: Minimize, maximize, close, drag, resize
- **Context Menus**: Right-click menus
- **Virtual Desktops**: 4 separate workspaces
### 🔧 Technical Implementation
#### New Files Created:
1. **[use-platform-layout.ts](../client/src/hooks/use-platform-layout.ts)**
- Hook for platform detection and adaptive styling
- Pre-configured classes for mobile/desktop/web
- `PlatformSwitch` component for conditional rendering
2. **[PlatformAdaptiveExample.tsx](../client/src/components/PlatformAdaptiveExample.tsx)**
- Reference implementation showing all patterns
- Demonstrates mobile/desktop navigation differences
3. **[PLATFORM_UI_GUIDE.md](./PLATFORM_UI_GUIDE.md)**
- Complete documentation with code examples
- Best practices and migration guide
#### Modified Files:
1. **[os.tsx](../client/src/pages/os.tsx)**
- Added mobile app launcher layout
- Full-screen window management for mobile
- Mobile-optimized navigation and controls
- Platform detection to switch between layouts
2. **[capacitor.config.ts](../capacitor.config.ts)**
- Configured for Android/iOS builds
- Fixed web assets directory path
3. **[src-tauri/tauri.conf.json](../src-tauri/tauri.conf.json)**
- Desktop app configuration
- Window sizing and branding
## 🎨 Mobile Design Highlights
### Color & Theming
- Maintains Foundation/Corp clearance themes
- Accent colors adapt to theme
- Dark-first design optimized for OLED screens
### Navigation Patterns
- **Home**: Returns to app launcher
- **Alerts**: Slide-up notification panel
- **Settings**: Opens settings app fullscreen
- **Account**: User profile and clearance switcher
### Gestures & Interactions
- **Tap**: Open app or interact
- **Back**: Navigate back or close app
- **Swipe**: (Ready for multi-window switching)
- **Pull-to-refresh**: (Ready for implementation)
### Typography & Spacing
- **Base Font**: 16px (vs 14px desktop) for readability
- **Button Height**: 48px (vs 40px desktop) for touch
- **Padding**: More generous spacing for finger-friendly UI
- **Line Height**: Increased for mobile reading
## 📲 Testing Your Mobile App
### In Android Studio (Now Open):
1. **Wait for Gradle Sync** to complete
2. **Select Device**:
- Click device dropdown in toolbar
- Choose an emulator or connected phone
3. **Run App**: Click green ▶️ play button
4. **See Mobile UI**: The app will show the mobile-optimized layout!
### Expected Mobile Behavior:
```
┌──────────────────────────┐
│ AeThex OS [FOUNDATION]│ ← Status Bar
│ 🔋 🕐 │
├──────────────────────────┤
│ │
│ [📱] [🌐] [📊] [⚙️] │ ← App Grid
│ [💬] [📁] [🎯] [📈] │
│ [⚡] [🔒] [👤] [📝] │
│ │
│ Quick Stats │
│ ┌──────────────────┐ │ ← Widgets
│ │ Open | Alerts |..│ │
│ └──────────────────┘ │
│ │
├──────────────────────────┤
│ [🏠] [🔔] [⚙️] [👤] │ ← Bottom Nav
└──────────────────────────┘
```
When you tap an app:
```
┌──────────────────────────┐
│ [←] Terminal [⚙️]│ ← App Header
├──────────────────────────┤
│ │
│ App content fills │
│ entire screen... │
│ │
│ │
├──────────────────────────┤
│ [🏠] [🔔] [⚙️] [👤] │ ← Nav Stays
└──────────────────────────┘
```
## 🚀 Next Steps
### Immediate:
1. ✅ Android Studio is open - **Run the app now!**
2. Test different apps (Terminal, Settings, etc.)
3. Try the bottom navigation
4. Open notifications panel
### Future Enhancements:
- [ ] Swipe gestures for window switching
- [ ] Pull-to-refresh on launcher
- [ ] Mobile-specific animations
- [ ] Haptic feedback on touch
- [ ] Biometric authentication
- [ ] Push notifications
- [ ] Native camera/file picker integration
- [ ] Offline mode support
### iOS Development (If on macOS):
```bash
npm run ios
```
Then build in Xcode the same way!
## 📝 Key Code Patterns
### Check Platform in Your Code:
```typescript
import { usePlatformLayout } from '@/hooks/use-platform-layout';
function MyComponent() {
const { isMobile, isDesktop, isWeb } = usePlatformLayout();
if (isMobile) {
return <MobileView />;
}
return <DesktopView />;
}
```
### Use Adaptive Styles:
```typescript
import { usePlatformClasses } from '@/hooks/use-platform-layout';
function MyComponent() {
const classes = usePlatformClasses();
return (
<div className={classes.container}>
<Button className={classes.button}>
Auto-sized for platform!
</Button>
</div>
);
}
```
## 🎯 Design Philosophy
### Mobile Principles:
- **One Thing at a Time**: Full-screen focus
- **Touch-First**: 44px+ tap targets
- **Thumb-Friendly**: Bottom navigation
- **Readable**: Larger fonts, generous spacing
- **Fast**: Minimal animations, instant feedback
### Desktop Principles:
- **Multi-Tasking**: Multiple windows
- **Information Density**: More content visible
- **Mouse/Keyboard**: Precise interactions
- **Productivity**: Shortcuts, drag-drop, context menus
## 🐛 Troubleshooting
### If Android Studio fails to build:
1. Check Java version: `java -version` (need 17+)
2. Update Android SDK in Settings → SDK Manager
3. Clean project: Build → Clean Project
4. Sync Gradle: File → Sync Project with Gradle Files
### If UI looks wrong:
1. Check platform detection in browser console: `window.Capacitor`
2. Force mobile view: Chrome DevTools → Device Toolbar
3. Rebuild: `npm run build:mobile`
### If changes don't appear:
1. Kill app completely on device
2. Rebuild: `npm run build:mobile`
3. Re-run from Android Studio
## 🌟 What Makes This Special
This isn't just responsive CSS - it's **completely different UX patterns** per platform:
- Mobile users get a native app experience
- Desktop users get a full windowing system
- Web users get the best of both
- **Same codebase, zero duplication!**
The React app automatically detects Capacitor and renders the mobile layout. No build flags, no separate code paths - just smart detection!
---
**Ready to test? Android Studio should be open - click that green ▶️ button!** 🚀

253
MOBILE_ENHANCEMENTS.md Normal file
View file

@ -0,0 +1,253 @@
# AeThex OS Mobile UI Enhancements 🚀
## Overview
Complete mobile UI overhaul with premium animations, haptic feedback, and native features to create a world-class mobile experience.
## 🎨 Visual Enhancements
### Status Bar
- **Glassmorphism Effect**: `backdrop-blur-2xl` with gradient background
- **Animated AeThex OS Logo**: Subtle scale pulse animation
- **Dynamic Clearance Badge**: Gradient background with border matching clearance color
- **System Icons**: WiFi, Battery, Time display with proper spacing
### App Launcher
- **4-Column Grid Layout**: Optimized for mobile viewing
- **Gradient App Icons**: Each icon has a gradient background using clearance theme accent
- **Stagger Animation**: Icons fade in with 30ms delay between each
- **Hover/Tap Effects**:
- Scale up on hover (1.05x)
- Scale down on tap (0.95x)
- Radial gradient glow effect on active press
- **Icon Wiggle**: Subtle rotation animation on hover
### System Widget
- **Glassmorphism Card**: Backdrop blur with gradient background
- **Animated Metrics**:
- Active windows count (scales with pulse)
- Alerts count
- System online percentage
- **Glow Effect**: Large blurred circle overlay for depth
- **Interactive Stats**: Each stat has hover scale effect
### Window Management
- **Swipe Gestures**:
- Drag right: Close current app
- Drag left: Switch to next app
- Elastic drag constraints
- Opacity fade during drag
- **Spring Animations**: Type 'spring' with stiffness 300, damping 30
- **Header Gradient**: From slate-900 to slate-800 with blur
- **Multi-App Badge**: Shows number of open windows with spring animation
- **Icon Wiggle**: Active window icon rotates gently
### Bottom Navigation
- **Enhanced Buttons**:
- 4 main tabs: Home, Alerts, Settings, Account
- Gradient background on active tab
- Pulsing glow effect on active
- Icon float animation (y-axis bounce)
- Badge pulse on notifications
- **Haptic Feedback**: Medium impact on every tap
- **Stagger Entry**: Buttons fade in with 50ms delays
- **Active State**: Radial gradient pulse behind active icon
## 📳 Haptic Feedback Integration
### Implementation
```typescript
const { impact, notification } = useHaptics();
// Light haptics for subtle interactions
impact('light') // Scrolling, selection changes
// Medium haptics for standard interactions
impact('medium') // Button taps, tab switches
// Heavy haptics for important actions
impact('heavy') // Logout, delete actions
// Success notifications
notification('success') // App launched, clearance switched
// Warning/Error notifications
notification('warning') // Form validation
notification('error') // Action failed
```
### Haptic Mapping
- **App Icon Tap**: Medium impact + Success notification
- **Bottom Nav Tap**: Medium impact
- **Close Window**: Medium impact
- **Swipe to Close**: Medium impact
- **Panel Open/Close**: Light impact
- **Clearance Switch**: Medium impact + Success notification
- **Logout**: Heavy impact + Success notification
- **Widget Tap**: Light impact
- **Notification Card Tap**: Light impact
## 🎭 Animation Details
### Spring Physics
```typescript
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
```
- Used for all major transitions
- Creates natural, bouncy feel
- Enhances perceived responsiveness
### Pulse Animations
```typescript
animate={{ scale: [1, 1.1, 1] }}
transition={{ duration: 2, repeat: Infinity }}
```
- Active tabs pulse continuously
- Notification badges pulse
- Status badges pulse
### Gradient Animations
```typescript
animate={{ opacity: [0.2, 0.4, 0.2] }}
transition={{ duration: 2, repeat: Infinity }}
```
- Background glows fade in/out
- Creates dynamic, alive interface
### Stagger Effects
```typescript
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.03 }}
```
- App icons appear sequentially
- Bottom nav buttons cascade in
- Creates polished loading experience
## 📱 Native Features
### Status Bar
- **Dark Theme**: Matches app aesthetic
- **Background Color**: `#0a0a0a` (dark slate)
- **Auto-configured**: Runs on component mount
### Splash Screen
- **Fade Duration**: 300ms
- **Auto-hide**: After app loads
- **Smooth transition**: To main interface
### Keyboard Management
- **Event Listeners**: Tracks show/hide/resize
- **State Tracking**: `keyboardVisible` boolean
- **Auto-hide Function**: `hideKeyboard()` method
- **UI Adaptation**: Can adjust layout when keyboard shows
## 🎯 Swipe Gestures
### Pull-to-Dismiss Panels
- **Notifications Panel**: Swipe down > 200px to close
- **Account Panel**: Swipe down > 200px to close
- **Visual Feedback**: Swipe handle at top
- **Animated Handle**: Pulses to indicate draggability
- **Haptic Response**: Medium impact on successful dismiss
### App Switching
- **Horizontal Swipe**: Drag left/right on app window
- **Threshold**: 100px offset triggers action
- **Elastic Drag**: Constrained but springy
- **Opacity Fade**: Window fades during drag
- **Spring Back**: Returns to center if threshold not met
## 🎨 Glassmorphism Effects
### Implementation Pattern
```typescript
className="backdrop-blur-2xl bg-gradient-to-b from-slate-900/95 via-slate-900/90 to-slate-950"
```
### Applied To:
- Status bar
- Bottom navigation
- Modal panels (Notifications, Account)
- System widget cards
- Notification cards
- Button active states
### Visual Result:
- Translucent layers
- Depth perception
- Premium aesthetic
- Reads well over any background
## 🔄 Loading States
### Empty States
- **Animated Icons**: Rotate and scale
- **Friendly Messages**: "All caught up!", "No notifications"
- **Consistent Design**: Follows app theme
- **Centered Layout**: Vertically and horizontally
### Skeleton Loaders
- Can be added for async data
- Use Framer Motion's `layoutId` for smooth transitions
- Maintain layout during loading
## 📊 Performance Considerations
### Optimization Strategies
1. **Transform-based Animations**: Use `transform` (scale, translate) for 60fps
2. **Backdrop Filter**: Limited usage to critical areas
3. **Gradient Caching**: CSS gradients are GPU-accelerated
4. **Motion Reduce**: Can add `useReducedMotion` hook for accessibility
5. **Lazy Loading**: Apps can be dynamically imported
6. **Image Optimization**: Icons are SVG for crisp scaling
### Current Bundle Size
- Client JS: 1.62 MB (435 KB gzipped)
- CSS: 178 KB (24.8 KB gzipped)
- APK: ~10 MB
## 🚀 Next Steps
### Potential Enhancements
1. **Pull-to-Refresh**: On app launcher (refresh app list)
2. **Long-Press Menus**: Context menus on app icons
3. **Gesture Tutorial**: First-time user onboarding
4. **Dark/Light Theme**: Toggle in settings
5. **Haptic Patterns**: Custom vibration sequences
6. **3D Touch**: Peek and pop on supported devices
7. **Biometric Auth**: Fingerprint/Face ID
8. **Camera Integration**: For profile pictures
9. **Share Sheet**: Export data to other apps
10. **Push Notifications**: Real-time alerts
### Advanced Animations
1. **Parallax Scrolling**: App launcher background
2. **Physics-based Springs**: Even more natural motion
3. **Morphing Shapes**: Icon transformations
4. **Particle Effects**: On special actions
5. **Lottie Animations**: Complex animated illustrations
## 🎓 Learning Resources
### Framer Motion
- [Spring Animations](https://www.framer.com/motion/transition/#spring)
- [Gestures](https://www.framer.com/motion/gestures/)
- [AnimatePresence](https://www.framer.com/motion/animate-presence/)
### Capacitor
- [Haptics API](https://capacitorjs.com/docs/apis/haptics)
- [Status Bar](https://capacitorjs.com/docs/apis/status-bar)
- [Keyboard](https://capacitorjs.com/docs/apis/keyboard)
### Design Inspiration
- iOS Human Interface Guidelines
- Material Design 3
- Fluent Design System
---
**Built with**: React 19 • Framer Motion 12 • Capacitor 8 • Tailwind CSS 3
**Platform**: Android & iOS
**Tested on**: Samsung Galaxy S23 Ultra
*Created by AeThex Team*

166
MOBILE_FEATURES.md Normal file
View file

@ -0,0 +1,166 @@
# AeThex OS - Mobile Native Features
## 🚀 Implemented Features
### ✅ Core Integrations
1. **Camera & Photos**
- Take photos with device camera
- Pick photos from gallery
- Both support editing before save
- Returns base64 data for easy upload
2. **File System Access**
- Save files to Documents directory
- Read files from storage
- JSON export/import support
- Auto-toast notifications on save
3. **Share API**
- Share text to other apps
- Share URLs to social media
- Native share sheet integration
- Haptic feedback on share
4. **Push & Local Notifications**
- Request notification permissions
- Send local notifications
- Schedule notifications
- Push notification support (requires backend)
5. **Geolocation**
- Get current location (lat/long)
- Watch position changes
- High accuracy mode
- 10s timeout for quick response
6. **Network Status**
- Real-time connection monitoring
- Connection type detection (wifi/cellular)
- Online/offline status
- Auto-updates on network change
7. **Clipboard**
- Copy text to clipboard
- Paste from clipboard
- Auto-toast on copy
- Haptic feedback
8. **Screen Orientation**
- Lock to portrait/landscape
- Unlock orientation
- Useful for video/game apps
9. **In-App Browser**
- Open URLs in native browser
- No need to leave app context
- Auto-returns to app
10. **Native Toasts**
- Short/long duration
- Bottom positioned
- System-native styling
11. **Haptic Feedback**
- Light/medium/heavy vibrations
- Impact on all interactions
- Native-feeling UI
12. **Device Info** (Already integrated)
- Device model & manufacturer
- Unique device ID
- Platform detection
13. **Status Bar Control** (Already integrated)
- Hide/show status bar
- Overlay mode
- Color customization
14. **Keyboard Management** (Already integrated)
- Show/hide detection
- Auto-adjust layouts
- Smooth transitions
## 🎯 Quick Actions FAB
Floating Action Button with instant access to:
- 📷 Camera
- 🔗 Share
- 📍 Location
- 🔔 Notifications
- 📋 Clipboard
- 💾 Save Files
- 🌐 Browser
- 📡 Network Status
## 🔐 Biometric Authentication (Prepared)
Scaffolding ready for:
- Fingerprint authentication
- Face ID support
- Device credential fallback
- Secure session management
**Note:** Requires `@capacitor-community/native-biometric` plugin to fully enable.
## 📦 Future Enhancements
To add later:
- **Contacts API**: Import/share contacts
- **Bluetooth**: Device pairing & communication
- **NFC**: Read/write NFC tags
- **Motion Sensors**: Gyroscope & accelerometer
- **Flashlight**: Toggle device torch
- **App Badge**: Show unread counts
- **Background Sync**: Data sync when app closed
- **Deep Links**: aethex:// URL scheme
## 🛠️ Usage
All features are accessible through hooks:
```tsx
import { useNativeFeatures } from '@/hooks/use-native-features';
function MyComponent() {
const native = useNativeFeatures();
// Take a photo
const photo = await native.takePhoto();
// Share content
await native.shareText('Hello!');
// Get location
const pos = await native.getCurrentLocation();
// Copy to clipboard
await native.copyToClipboard('Some text');
}
```
## 📱 Permissions
App automatically requests permissions for:
- Camera (on first use)
- Location (on first use)
- Notifications (on first request)
- Storage (on first file access)
All permissions are requested just-in-time with clear context.
## 🎨 UI Integration
- Quick Actions FAB on all mobile screens
- Native haptics on all interactions
- Toast notifications for feedback
- Network status indicator
- Location display
## 🔧 Configuration
See `capacitor.config.ts` for plugin configurations:
- Splash screen settings
- Notification icons
- Push notification options
- Deep linking schemes (coming soon)

101
android/.gitignore vendored Normal file
View file

@ -0,0 +1,101 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
# Cordova plugins for Capacitor
capacitor-cordova-android-plugins
# Copied web assets
app/src/main/assets/public
# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml

3
android/.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DIALOG" />
<DialogSelection>
<targets>
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\PCOEM\.android\avd\Medium_Phone.avd" />
</handle>
</Target>
</targets>
</DialogSelection>
</SelectionState>
</selectionStates>
</component>
</project>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

10
android/.idea/misc.xml Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

2
android/app/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/build/*
!/build/.npmkeep

54
android/app/build.gradle Normal file
View file

@ -0,0 +1,54 @@
apply plugin: 'com.android.application'
android {
namespace = "com.aethex.os"
compileSdk = rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.aethex.os"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}
apply from: 'capacitor.build.gradle'
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}

View file

@ -0,0 +1,36 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
}
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-community-privacy-screen')
implementation project(':capacitor-app')
implementation project(':capacitor-browser')
implementation project(':capacitor-camera')
implementation project(':capacitor-clipboard')
implementation project(':capacitor-device')
implementation project(':capacitor-filesystem')
implementation project(':capacitor-geolocation')
implementation project(':capacitor-haptics')
implementation project(':capacitor-keyboard')
implementation project(':capacitor-local-notifications')
implementation project(':capacitor-network')
implementation project(':capacitor-push-notifications')
implementation project(':capacitor-screen-orientation')
implementation project(':capacitor-share')
implementation project(':capacitor-splash-screen')
implementation project(':capacitor-status-bar')
implementation project(':capacitor-toast')
}
if (hasProperty('postBuildExtras')) {
postBuildExtras()
}

21
android/app/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,26 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.getcapacitor.app", appContext.getPackageName());
}
}

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View file

@ -0,0 +1,5 @@
package com.aethex.os;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View file

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">AeThex OS</string>
<string name="title_activity_main">AeThex OS</string>
<string name="package_name">com.aethex.os</string>
<string name="custom_url_scheme">com.aethex.os</string>
</resources>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:background">@null</item>
</style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item>
</style>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
</paths>

View file

@ -0,0 +1,18 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

29
android/build.gradle Normal file
View file

@ -0,0 +1,29 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.2'
classpath 'com.google.gms:google-services:4.4.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply from: "variables.gradle"
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View file

@ -1,3 +1,57 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android' include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-community-privacy-screen'
project(':capacitor-community-privacy-screen').projectDir = new File('../node_modules/@capacitor-community/privacy-screen/android')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
include ':capacitor-browser'
project(':capacitor-browser').projectDir = new File('../node_modules/@capacitor/browser/android')
include ':capacitor-camera'
project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android')
include ':capacitor-clipboard'
project(':capacitor-clipboard').projectDir = new File('../node_modules/@capacitor/clipboard/android')
include ':capacitor-device'
project(':capacitor-device').projectDir = new File('../node_modules/@capacitor/device/android')
include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')
include ':capacitor-geolocation'
project(':capacitor-geolocation').projectDir = new File('../node_modules/@capacitor/geolocation/android')
include ':capacitor-haptics'
project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/haptics/android')
include ':capacitor-keyboard'
project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android')
include ':capacitor-local-notifications'
project(':capacitor-local-notifications').projectDir = new File('../node_modules/@capacitor/local-notifications/android')
include ':capacitor-network'
project(':capacitor-network').projectDir = new File('../node_modules/@capacitor/network/android')
include ':capacitor-push-notifications'
project(':capacitor-push-notifications').projectDir = new File('../node_modules/@capacitor/push-notifications/android')
include ':capacitor-screen-orientation'
project(':capacitor-screen-orientation').projectDir = new File('../node_modules/@capacitor/screen-orientation/android')
include ':capacitor-share'
project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android')
include ':capacitor-splash-screen'
project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capacitor/splash-screen/android')
include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
include ':capacitor-toast'
project(':capacitor-toast').projectDir = new File('../node_modules/@capacitor/toast/android')

22
android/gradle.properties Normal file
View file

@ -0,0 +1,22 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true

Binary file not shown.

View file

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
android/gradlew vendored Normal file
View file

@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
android/gradlew.bat vendored Normal file
View file

@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

5
android/settings.gradle Normal file
View file

@ -0,0 +1,5 @@
include ':app'
include ':capacitor-cordova-android-plugins'
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
apply from: 'capacitor.settings.gradle'

16
android/variables.gradle Normal file
View file

@ -0,0 +1,16 @@
ext {
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
androidxActivityVersion = '1.11.0'
androidxAppCompatVersion = '1.7.1'
androidxCoordinatorLayoutVersion = '1.3.0'
androidxCoreVersion = '1.17.0'
androidxFragmentVersion = '1.8.9'
coreSplashScreenVersion = '1.2.0'
androidxWebkitVersion = '1.14.0'
junitVersion = '4.13.2'
androidxJunitVersion = '1.3.0'
androidxEspressoCoreVersion = '3.7.0'
cordovaAndroidVersion = '14.0.1'
}

36
capacitor.config.ts Normal file
View file

@ -0,0 +1,36 @@
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.aethex.os',
appName: 'AeThex OS',
webDir: 'dist/public',
server: {
androidScheme: 'https',
iosScheme: 'https'
},
plugins: {
SplashScreen: {
launchShowDuration: 0,
launchAutoHide: true,
backgroundColor: '#000000',
androidSplashResourceName: 'splash',
androidScaleType: 'CENTER_CROP',
showSpinner: false,
androidSpinnerStyle: 'large',
iosSpinnerStyle: 'small',
spinnerColor: '#999999',
splashFullScreen: true,
splashImmersive: true
},
PushNotifications: {
presentationOptions: ['badge', 'sound', 'alert']
},
LocalNotifications: {
smallIcon: 'ic_stat_icon_config_sample',
iconColor: '#488AFF',
sound: 'beep.wav'
}
}
};
export default config;

View file

@ -0,0 +1,176 @@
import { useState } from 'react';
import { Camera, Share2, MapPin, Bell, Copy, FileText, Globe, Wifi, WifiOff } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { useNativeFeatures } from '../hooks/use-native-features';
export function MobileQuickActions() {
const [isOpen, setIsOpen] = useState(false);
const native = useNativeFeatures();
const [location, setLocation] = useState<string>('');
const quickActions = [
{
icon: <Camera className="w-5 h-5" />,
label: 'Camera',
color: 'from-blue-500 to-cyan-500',
action: async () => {
const photo = await native.takePhoto();
if (photo) {
native.showToast('Photo captured!');
native.vibrate('light');
}
}
},
{
icon: <Share2 className="w-5 h-5" />,
label: 'Share',
color: 'from-purple-500 to-pink-500',
action: async () => {
await native.shareText('Check out AeThex OS!', 'AeThex OS');
}
},
{
icon: <MapPin className="w-5 h-5" />,
label: 'Location',
color: 'from-green-500 to-emerald-500',
action: async () => {
const position = await native.getCurrentLocation();
if (position) {
const loc = `${position.coords.latitude.toFixed(4)}, ${position.coords.longitude.toFixed(4)}`;
setLocation(loc);
native.showToast(`Location: ${loc}`);
native.vibrate('medium');
}
}
},
{
icon: <Bell className="w-5 h-5" />,
label: 'Notify',
color: 'from-orange-500 to-red-500',
action: async () => {
await native.sendLocalNotification('AeThex OS', 'Test notification from your OS!');
native.vibrate('light');
}
},
{
icon: <Copy className="w-5 h-5" />,
label: 'Clipboard',
color: 'from-yellow-500 to-amber-500',
action: async () => {
await native.copyToClipboard('AeThex OS - The Future is Now');
}
},
{
icon: <FileText className="w-5 h-5" />,
label: 'Save File',
color: 'from-indigo-500 to-blue-500',
action: async () => {
const success = await native.saveFile(
JSON.stringify({ timestamp: Date.now(), app: 'AeThex OS' }),
`aethex-${Date.now()}.json`
);
if (success) native.vibrate('medium');
}
},
{
icon: <Globe className="w-5 h-5" />,
label: 'Browser',
color: 'from-teal-500 to-cyan-500',
action: async () => {
await native.openInBrowser('https://github.com');
native.vibrate('light');
}
},
{
icon: native.networkStatus.connected ? <Wifi className="w-5 h-5" /> : <WifiOff className="w-5 h-5" />,
label: native.networkStatus.connected ? 'Online' : 'Offline',
color: native.networkStatus.connected ? 'from-green-500 to-emerald-500' : 'from-gray-500 to-slate-500',
action: () => {
native.showToast(
`Network: ${native.networkStatus.connectionType} (${native.networkStatus.connected ? 'Connected' : 'Disconnected'})`
);
}
}
];
return (
<>
{/* Floating Action Button */}
<motion.button
onClick={() => setIsOpen(!isOpen)}
whileTap={{ scale: 0.9 }}
className="fixed bottom-24 right-6 w-14 h-14 rounded-full bg-gradient-to-br from-cyan-500 to-purple-600 shadow-lg shadow-cyan-500/50 flex items-center justify-center z-40"
>
<motion.div
animate={{ rotate: isOpen ? 45 : 0 }}
transition={{ duration: 0.2 }}
>
<div className="text-white text-2xl font-bold">+</div>
</motion.div>
</motion.button>
{/* Quick Actions Menu */}
<AnimatePresence>
{isOpen && (
<>
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setIsOpen(false)}
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-30"
/>
{/* Actions Grid */}
<motion.div
initial={{ opacity: 0, scale: 0.8, y: 50 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.8, y: 50 }}
className="fixed bottom-40 right-6 w-72 bg-slate-900/95 backdrop-blur-xl rounded-2xl border border-white/10 shadow-2xl z-40 p-4"
>
<div className="grid grid-cols-4 gap-3">
{quickActions.map((action, i) => (
<motion.button
key={i}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.05 }}
onClick={() => {
action.action();
setIsOpen(false);
}}
className="flex flex-col items-center gap-1 p-2 rounded-xl active:scale-95 transition-transform"
>
<div className={`w-12 h-12 rounded-xl bg-gradient-to-br ${action.color} flex items-center justify-center text-white shadow-lg`}>
{action.icon}
</div>
<span className="text-white text-[10px] font-medium text-center leading-tight">
{action.label}
</span>
</motion.button>
))}
</div>
{location && (
<div className="mt-3 pt-3 border-t border-white/10">
<div className="text-white/50 text-xs">Last Location:</div>
<div className="text-white text-xs font-mono">{location}</div>
</div>
)}
<div className="mt-3 pt-3 border-t border-white/10">
<div className="flex items-center justify-between">
<span className="text-white/50 text-xs">Network</span>
<span className={`text-xs font-medium ${native.networkStatus.connected ? 'text-green-400' : 'text-red-400'}`}>
{native.networkStatus.connectionType}
</span>
</div>
</div>
</motion.div>
</>
)}
</AnimatePresence>
</>
);
}

View file

@ -0,0 +1,163 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { usePlatformLayout, usePlatformClasses, PlatformSwitch } from '@/hooks/use-platform-layout';
import { Home, Users, Settings, Plus } from 'lucide-react';
/**
* Example component showing how to adapt UI for different platforms
*/
export function PlatformAdaptiveExample() {
const layout = usePlatformLayout();
const classes = usePlatformClasses();
return (
<div className={classes.container}>
{/* Platform-specific header */}
<PlatformSwitch
mobile={<MobileHeader />}
desktop={<DesktopHeader />}
web={<WebHeader />}
/>
{/* Content that adapts to platform */}
<div className={classes.spacing}>
<Card className={classes.card}>
<CardHeader>
<CardTitle className={classes.heading}>
Platform: {layout.isMobile ? 'Mobile' : layout.isDesktop ? 'Desktop' : 'Web'}
</CardTitle>
</CardHeader>
<CardContent className={classes.spacing}>
<p className={classes.fontSize}>
This component automatically adapts its layout and styling based on the platform.
</p>
{/* Platform-specific buttons */}
<div className="flex gap-2">
<Button className={classes.button}>
<Plus className="mr-2 h-4 w-4" />
{layout.isMobile ? 'Add' : 'Add New Item'}
</Button>
</div>
</CardContent>
</Card>
{/* Grid that adapts to screen size and platform */}
<div className={`grid gap-4 ${
layout.isMobile ? 'grid-cols-1' :
layout.isDesktop ? 'grid-cols-3' :
'grid-cols-2'
}`}>
<Card className={classes.card}>
<CardContent className="pt-6">
<Home className="h-8 w-8 mb-2" />
<h3 className={classes.subheading}>Dashboard</h3>
</CardContent>
</Card>
<Card className={classes.card}>
<CardContent className="pt-6">
<Users className="h-8 w-8 mb-2" />
<h3 className={classes.subheading}>Team</h3>
</CardContent>
</Card>
<Card className={classes.card}>
<CardContent className="pt-6">
<Settings className="h-8 w-8 mb-2" />
<h3 className={classes.subheading}>Settings</h3>
</CardContent>
</Card>
</div>
</div>
{/* Platform-specific navigation */}
<PlatformSwitch
mobile={<MobileBottomNav />}
desktop={<DesktopTopNav />}
web={<WebStickyNav />}
/>
</div>
);
}
// Mobile: Bottom navigation bar
function MobileBottomNav() {
return (
<nav className="fixed bottom-0 left-0 right-0 bg-background border-t">
<div className="flex justify-around items-center h-16 px-4">
<NavItem icon={<Home />} label="Home" />
<NavItem icon={<Users />} label="Team" />
<NavItem icon={<Settings />} label="Settings" />
</div>
</nav>
);
}
// Desktop: Top navigation bar
function DesktopTopNav() {
return (
<nav className="fixed top-0 left-0 right-0 bg-background border-b">
<div className="flex items-center justify-between h-16 px-8">
<div className="flex items-center gap-8">
<span className="text-xl font-bold">AeThex OS</span>
<NavItem icon={<Home />} label="Dashboard" />
<NavItem icon={<Users />} label="Team" />
</div>
<NavItem icon={<Settings />} label="Settings" />
</div>
</nav>
);
}
// Web: Sticky navigation
function WebStickyNav() {
return (
<nav className="sticky top-0 bg-background/95 backdrop-blur border-b z-50">
<div className="flex items-center justify-between h-14 px-6">
<div className="flex items-center gap-6">
<span className="text-lg font-bold">AeThex OS</span>
<NavItem icon={<Home />} label="Home" />
<NavItem icon={<Users />} label="Team" />
</div>
<NavItem icon={<Settings />} label="Settings" />
</div>
</nav>
);
}
function NavItem({ icon, label }: { icon: React.ReactNode; label: string }) {
return (
<button className="flex flex-col items-center gap-1 text-muted-foreground hover:text-foreground transition-colors">
{icon}
<span className="text-xs">{label}</span>
</button>
);
}
// Mobile-specific header
function MobileHeader() {
return (
<header className="sticky top-0 bg-background border-b z-10 px-4 py-3">
<h1 className="text-xl font-bold">AeThex OS</h1>
</header>
);
}
// Desktop-specific header
function DesktopHeader() {
return (
<header className="mb-6">
<h1 className="text-3xl font-bold mb-2">AeThex OS Desktop</h1>
<p className="text-muted-foreground">Native desktop experience</p>
</header>
);
}
// Web-specific header
function WebHeader() {
return (
<header className="mb-4">
<h1 className="text-2xl font-bold mb-1">AeThex OS</h1>
<p className="text-sm text-muted-foreground">Web desktop platform</p>
</header>
);
}

View file

@ -0,0 +1,66 @@
import { useState, useEffect } from 'react';
import { Capacitor } from '@capacitor/core';
// Note: Biometric auth requires native-auth plugin or similar
// For now we'll create the interface and you can install the plugin later
interface BiometricAuthResult {
isAvailable: boolean;
biometricType: 'fingerprint' | 'face' | 'iris' | 'none';
authenticate: () => Promise<boolean>;
isAuthenticated: boolean;
}
export function useBiometricAuth(): BiometricAuthResult {
const [isAvailable, setIsAvailable] = useState(false);
const [biometricType, setBiometricType] = useState<'fingerprint' | 'face' | 'iris' | 'none'>('none');
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
const checkAvailability = async () => {
if (!Capacitor.isNativePlatform()) {
setIsAvailable(false);
return;
}
// Check if biometrics are available
// This would use @capacitor-community/native-biometric or similar
// For now, we'll assume it's available on mobile
setIsAvailable(true);
setBiometricType('fingerprint'); // Default assumption
};
checkAvailability();
}, []);
const authenticate = async (): Promise<boolean> => {
if (!isAvailable) return false;
try {
// This is where you'd call the actual biometric auth
// For example with @capacitor-community/native-biometric:
// const result = await NativeBiometric.verifyIdentity({
// reason: "Authenticate to access AeThex OS",
// title: "Biometric Authentication",
// subtitle: "Use your fingerprint or face",
// description: "Please authenticate"
// });
// For now, simulate success
console.log('Biometric auth would trigger here');
setIsAuthenticated(true);
return true;
} catch (error) {
console.error('Biometric auth error:', error);
setIsAuthenticated(false);
return false;
}
};
return {
isAvailable,
biometricType,
authenticate,
isAuthenticated
};
}

View file

@ -0,0 +1,87 @@
import React from 'react';
import { Haptics, ImpactStyle, NotificationType } from '@capacitor/haptics';
import { isMobile } from '@/lib/platform';
/**
* Haptic feedback hook for mobile interactions
*/
export function useHaptics() {
const isAvailable = isMobile();
const impact = async (style: 'light' | 'medium' | 'heavy' = 'medium') => {
if (!isAvailable) return;
try {
const styleMap = {
light: ImpactStyle.Light,
medium: ImpactStyle.Medium,
heavy: ImpactStyle.Heavy,
};
await Haptics.impact({ style: styleMap[style] });
} catch (e) {
// Haptics not supported on this device
}
};
const notification = async (type: 'success' | 'warning' | 'error' = 'success') => {
if (!isAvailable) return;
try {
const typeMap = {
success: NotificationType.Success,
warning: NotificationType.Warning,
error: NotificationType.Error,
};
await Haptics.notification({ type: typeMap[type] });
} catch (e) {
// Haptics not supported
}
};
const vibrate = async (duration: number = 50) => {
if (!isAvailable) return;
try {
await Haptics.vibrate({ duration });
} catch (e) {
// Fallback to web vibration API
if (navigator.vibrate) {
navigator.vibrate(duration);
}
}
};
const selectionStart = async () => {
if (!isAvailable) return;
try {
await Haptics.selectionStart();
} catch (e) {
// Not supported
}
};
const selectionChanged = async () => {
if (!isAvailable) return;
try {
await Haptics.selectionChanged();
} catch (e) {
// Not supported
}
};
const selectionEnd = async () => {
if (!isAvailable) return;
try {
await Haptics.selectionEnd();
} catch (e) {
// Not supported
}
};
return {
impact,
notification,
vibrate,
selectionStart,
selectionChanged,
selectionEnd,
isAvailable,
};
}

View file

@ -0,0 +1,86 @@
import { useState, useEffect } from 'react';
import { StatusBar, Style } from '@capacitor/status-bar';
import { SplashScreen } from '@capacitor/splash-screen';
import { Keyboard } from '@capacitor/keyboard';
import { Device } from '@capacitor/device';
import { isMobile } from '@/lib/platform';
/**
* Initialize mobile-specific native features
*/
export function useMobileNative(theme: 'dark' | 'light' = 'dark') {
const [keyboardVisible, setKeyboardVisible] = useState(false);
const [deviceInfo, setDeviceInfo] = useState<any>(null);
useEffect(() => {
if (!isMobile()) return;
const initMobileFeatures = async () => {
// Get device info for recognition
try {
const info = await Device.getInfo();
const id = await Device.getId();
setDeviceInfo({ ...info, ...id });
console.log('[Device Recognition]', info.manufacturer, info.model);
} catch (e) {
console.log('Device info unavailable');
}
// Always hide splash screen immediately
try {
await SplashScreen.hide({ fadeOutDuration: 100 });
} catch (e) {
console.log('Splash screen already hidden');
}
// Configure status bar for immersive mode
setTimeout(async () => {
try {
await StatusBar.setStyle({ style: Style.Dark });
await StatusBar.setBackgroundColor({ color: '#000000' });
await StatusBar.setOverlaysWebView({ overlay: true });
await StatusBar.hide();
} catch (e) {
console.log('Status bar config skipped');
}
}, 0);
// Setup keyboard listeners (non-blocking)
setTimeout(async () => {
try {
Keyboard.addListener('keyboardWillShow', () => {
setKeyboardVisible(true);
});
Keyboard.addListener('keyboardWillHide', () => {
setKeyboardVisible(false);
});
} catch (e) {
console.log('Keyboard listeners skipped');
}
}, 0);
};
initMobileFeatures();
return () => {
if (isMobile()) {
Keyboard.removeAllListeners().catch(() => {});
}
};
}, [theme]);
const hideKeyboard = async () => {
if (!isMobile()) return;
try {
await Keyboard.hide();
} catch (e) {
// Not available
}
};
return {
keyboardVisible,
hideKeyboard,
deviceInfo,
};
}

View file

@ -0,0 +1,334 @@
import { useEffect, useState } from 'react';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import { Share } from '@capacitor/share';
import { PushNotifications } from '@capacitor/push-notifications';
import { Geolocation, Position } from '@capacitor/geolocation';
import { LocalNotifications } from '@capacitor/local-notifications';
import { Network } from '@capacitor/network';
import { Toast } from '@capacitor/toast';
import { Clipboard } from '@capacitor/clipboard';
import { ScreenOrientation } from '@capacitor/screen-orientation';
import { Browser } from '@capacitor/browser';
import { App } from '@capacitor/app';
import { Haptics, ImpactStyle } from '@capacitor/haptics';
interface UseNativeFeaturesReturn {
// Camera
takePhoto: () => Promise<string | null>;
pickPhoto: () => Promise<string | null>;
// Files
saveFile: (data: string, filename: string) => Promise<boolean>;
readFile: (filename: string) => Promise<string | null>;
pickFile: () => Promise<string | null>;
// Share
shareText: (text: string, title?: string) => Promise<void>;
shareUrl: (url: string, title?: string) => Promise<void>;
// Notifications
requestNotificationPermission: () => Promise<boolean>;
sendLocalNotification: (title: string, body: string) => Promise<void>;
// Location
getCurrentLocation: () => Promise<Position | null>;
watchLocation: (callback: (position: Position) => void) => () => void;
// Network
networkStatus: { connected: boolean; connectionType: string };
// Clipboard
copyToClipboard: (text: string) => Promise<void>;
pasteFromClipboard: () => Promise<string>;
// Screen
lockOrientation: (orientation: 'portrait' | 'landscape') => Promise<void>;
unlockOrientation: () => Promise<void>;
// Browser
openInBrowser: (url: string) => Promise<void>;
// Toast
showToast: (text: string, duration?: 'short' | 'long') => Promise<void>;
// Haptics
vibrate: (style?: 'light' | 'medium' | 'heavy') => Promise<void>;
}
export function useNativeFeatures(): UseNativeFeaturesReturn {
const [networkStatus, setNetworkStatus] = useState({ connected: true, connectionType: 'unknown' });
// Initialize network monitoring
useEffect(() => {
const initNetwork = async () => {
const status = await Network.getStatus();
setNetworkStatus({ connected: status.connected, connectionType: status.connectionType });
Network.addListener('networkStatusChange', status => {
setNetworkStatus({ connected: status.connected, connectionType: status.connectionType });
});
};
initNetwork();
return () => {
Network.removeAllListeners();
};
}, []);
// Camera functions
const takePhoto = async (): Promise<string | null> => {
try {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Base64,
source: CameraSource.Camera
});
return image.base64String || null;
} catch (error) {
console.error('Camera error:', error);
return null;
}
};
const pickPhoto = async (): Promise<string | null> => {
try {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Base64,
source: CameraSource.Photos
});
return image.base64String || null;
} catch (error) {
console.error('Photo picker error:', error);
return null;
}
};
// File system functions
const saveFile = async (data: string, filename: string): Promise<boolean> => {
try {
await Filesystem.writeFile({
path: filename,
data: data,
directory: Directory.Documents,
encoding: Encoding.UTF8
});
await showToast(`Saved ${filename}`, 'short');
return true;
} catch (error) {
console.error('File save error:', error);
return false;
}
};
const readFile = async (filename: string): Promise<string | null> => {
try {
const result = await Filesystem.readFile({
path: filename,
directory: Directory.Documents,
encoding: Encoding.UTF8
});
return result.data as string;
} catch (error) {
console.error('File read error:', error);
return null;
}
};
const pickFile = async (): Promise<string | null> => {
// Note: Capacitor doesn't have a built-in file picker
// You'd need to use a plugin like @capacitor-community/file-picker
console.log('File picker not implemented - need @capacitor-community/file-picker');
return null;
};
// Share functions
const shareText = async (text: string, title?: string): Promise<void> => {
try {
await Share.share({
text: text,
title: title || 'Share',
dialogTitle: 'Share via'
});
await Haptics.impact({ style: ImpactStyle.Light });
} catch (error) {
console.error('Share error:', error);
}
};
const shareUrl = async (url: string, title?: string): Promise<void> => {
try {
await Share.share({
url: url,
title: title || 'Share',
dialogTitle: 'Share via'
});
await Haptics.impact({ style: ImpactStyle.Light });
} catch (error) {
console.error('Share error:', error);
}
};
// Notification functions
const requestNotificationPermission = async (): Promise<boolean> => {
try {
const result = await PushNotifications.requestPermissions();
if (result.receive === 'granted') {
await PushNotifications.register();
return true;
}
return false;
} catch (error) {
console.error('Notification permission error:', error);
return false;
}
};
const sendLocalNotification = async (title: string, body: string): Promise<void> => {
try {
await LocalNotifications.schedule({
notifications: [
{
title: title,
body: body,
id: Date.now(),
schedule: { at: new Date(Date.now() + 1000) },
sound: undefined,
attachments: undefined,
actionTypeId: '',
extra: null
}
]
});
} catch (error) {
console.error('Local notification error:', error);
}
};
// Location functions
const getCurrentLocation = async (): Promise<Position | null> => {
try {
const position = await Geolocation.getCurrentPosition({
enableHighAccuracy: true,
timeout: 10000
});
return position;
} catch (error) {
console.error('Location error:', error);
return null;
}
};
const watchLocation = (callback: (position: Position) => void) => {
const watchId = Geolocation.watchPosition(
{ enableHighAccuracy: true },
(position, err) => {
if (position) callback(position);
}
);
return () => {
if (watchId) Geolocation.clearWatch({ id: watchId });
};
};
// Clipboard functions
const copyToClipboard = async (text: string): Promise<void> => {
try {
await Clipboard.write({ string: text });
await showToast('Copied to clipboard', 'short');
await Haptics.impact({ style: ImpactStyle.Light });
} catch (error) {
console.error('Clipboard error:', error);
}
};
const pasteFromClipboard = async (): Promise<string> => {
try {
const result = await Clipboard.read();
return result.value;
} catch (error) {
console.error('Clipboard error:', error);
return '';
}
};
// Screen orientation
const lockOrientation = async (orientation: 'portrait' | 'landscape'): Promise<void> => {
try {
await ScreenOrientation.lock({ orientation: orientation });
} catch (error) {
console.error('Orientation lock error:', error);
}
};
const unlockOrientation = async (): Promise<void> => {
try {
await ScreenOrientation.unlock();
} catch (error) {
console.error('Orientation unlock error:', error);
}
};
// Browser
const openInBrowser = async (url: string): Promise<void> => {
try {
await Browser.open({ url });
} catch (error) {
console.error('Browser error:', error);
}
};
// Toast
const showToast = async (text: string, duration: 'short' | 'long' = 'short'): Promise<void> => {
try {
await Toast.show({
text: text,
duration: duration,
position: 'bottom'
});
} catch (error) {
console.error('Toast error:', error);
}
};
// Haptics
const vibrate = async (style: 'light' | 'medium' | 'heavy' = 'medium'): Promise<void> => {
try {
const styleMap = {
light: ImpactStyle.Light,
medium: ImpactStyle.Medium,
heavy: ImpactStyle.Heavy
};
await Haptics.impact({ style: styleMap[style] });
} catch (error) {
console.error('Haptics error:', error);
}
};
return {
takePhoto,
pickPhoto,
saveFile,
readFile,
pickFile,
shareText,
shareUrl,
requestNotificationPermission,
sendLocalNotification,
getCurrentLocation,
watchLocation,
networkStatus,
copyToClipboard,
pasteFromClipboard,
lockOrientation,
unlockOrientation,
openInBrowser,
showToast,
vibrate
};
}

View file

@ -0,0 +1,105 @@
import { useMemo } from 'react';
import { isMobile, isDesktop, isWeb } from '@/lib/platform';
interface LayoutConfig {
isMobile: boolean;
isDesktop: boolean;
isWeb: boolean;
containerClass: string;
cardClass: string;
navClass: string;
spacing: string;
fontSize: string;
}
/**
* Hook to get platform-specific layout configuration
* Use this to conditionally render or style components based on platform
*/
export function usePlatformLayout(): LayoutConfig {
// Call detection functions once and cache
const platformCheck = useMemo(() => {
const mobile = isMobile();
const desktop = isDesktop();
const web = isWeb();
return { isMobile: mobile, isDesktop: desktop, isWeb: web };
}, []); // Empty array - only check once on mount
const config = useMemo((): LayoutConfig => {
if (platformCheck.isMobile) {
return {
...platformCheck,
// Mobile-first container styling
containerClass: 'px-4 py-3 max-w-full',
cardClass: 'rounded-lg shadow-sm border p-4',
navClass: 'fixed bottom-0 left-0 right-0 bg-background border-t',
spacing: 'space-y-3',
fontSize: 'text-base',
};
}
if (platformCheck.isDesktop) {
return {
...platformCheck,
// Desktop app styling
containerClass: 'px-8 py-6 max-w-7xl mx-auto',
cardClass: 'rounded-xl shadow-lg border p-6',
navClass: 'fixed top-0 left-0 right-0 bg-background border-b',
spacing: 'space-y-6',
fontSize: 'text-sm',
};
}
// Web browser styling (default)
return {
...platformCheck,
containerClass: 'px-6 py-4 max-w-6xl mx-auto',
cardClass: 'rounded-xl shadow-md border p-6',
navClass: 'sticky top-0 bg-background/95 backdrop-blur border-b z-50',
spacing: 'space-y-4',
fontSize: 'text-sm',
};
}, [platformCheck]);
return config;
}
/**
* Get platform-specific class names
*/
export function usePlatformClasses() {
const layout = usePlatformLayout();
return {
container: layout.containerClass,
card: layout.cardClass,
nav: layout.navClass,
spacing: layout.spacing,
fontSize: layout.fontSize,
// Additional utility classes
button: layout.isMobile ? 'h-12 text-base' : 'h-10 text-sm',
input: layout.isMobile ? 'h-12 text-base' : 'h-10 text-sm',
heading: layout.isMobile ? 'text-2xl' : 'text-3xl',
subheading: layout.isMobile ? 'text-lg' : 'text-xl',
};
}
/**
* Conditional rendering based on platform
*/
export function PlatformSwitch({
mobile,
desktop,
web,
fallback
}: {
mobile?: React.ReactNode;
desktop?: React.ReactNode;
web?: React.ReactNode;
fallback?: React.ReactNode;
}) {
if (isMobile() && mobile) return mobile as React.ReactElement;
if (isDesktop() && desktop) return desktop as React.ReactElement;
if (isWeb() && web) return web as React.ReactElement;
return (fallback || null) as React.ReactElement;
}

View file

@ -1,5 +1,7 @@
import { createContext, useContext, ReactNode } from "react"; import { createContext, useContext, ReactNode } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { supabase } from "./supabase";
import { isMobile } from "./platform";
interface User { interface User {
id: string; id: string;
@ -26,6 +28,24 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const { data: session, isLoading } = useQuery({ const { data: session, isLoading } = useQuery({
queryKey: ["session"], queryKey: ["session"],
queryFn: async () => { queryFn: async () => {
// On mobile, use Supabase directly
if (isMobile()) {
const { data, error } = await supabase.auth.getSession();
if (error) throw error;
if (!data.session) return { authenticated: false };
return {
authenticated: true,
user: {
id: data.session.user.id,
email: data.session.user.email,
username: data.session.user.email?.split('@')[0] || 'user',
isAdmin: false
}
};
}
// On web/desktop, use API server
const res = await fetch("/api/auth/session", { credentials: "include" }); const res = await fetch("/api/auth/session", { credentials: "include" });
return res.json(); return res.json();
}, },
@ -38,6 +58,17 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const loginMutation = useMutation({ const loginMutation = useMutation({
mutationFn: async ({ email, password }: { email: string; password: string }) => { mutationFn: async ({ email, password }: { email: string; password: string }) => {
// On mobile, use Supabase directly
if (isMobile()) {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) throw new Error(error.message);
return { success: true, user: data.user };
}
// On web/desktop, use API server
const res = await fetch("/api/auth/login", { const res = await fetch("/api/auth/login", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@ -73,6 +104,14 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const logoutMutation = useMutation({ const logoutMutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
// On mobile, use Supabase directly
if (isMobile()) {
const { error } = await supabase.auth.signOut();
if (error) throw error;
return;
}
// On web/desktop, use API server
await fetch("/api/auth/logout", { method: "POST", credentials: "include" }); await fetch("/api/auth/logout", { method: "POST", credentials: "include" });
}, },
onSuccess: () => { onSuccess: () => {

View file

@ -22,32 +22,42 @@ function detectPlatform(): PlatformType {
if (cachedPlatform !== null) return cachedPlatform; if (cachedPlatform !== null) return cachedPlatform;
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
console.log('[Platform] Detected: web (no window)');
cachedPlatform = 'web'; cachedPlatform = 'web';
return cachedPlatform; return cachedPlatform;
} }
console.log('[Platform] Checking window.Capacitor:', window.Capacitor);
console.log('[Platform] Checking window.__TAURI__:', window.__TAURI__);
if (window.__TAURI__ !== undefined) { if (window.__TAURI__ !== undefined) {
console.log('[Platform] Detected: desktop (Tauri)');
cachedPlatform = 'desktop'; cachedPlatform = 'desktop';
return cachedPlatform; return cachedPlatform;
} }
if (window.flutter_inappwebview !== undefined || window.Capacitor !== undefined) { if (window.flutter_inappwebview !== undefined || window.Capacitor !== undefined) {
console.log('[Platform] Detected: mobile (Capacitor or Flutter)');
cachedPlatform = 'mobile'; cachedPlatform = 'mobile';
return cachedPlatform; return cachedPlatform;
} }
const userAgent = navigator.userAgent.toLowerCase(); const userAgent = navigator.userAgent.toLowerCase();
console.log('[Platform] User agent:', userAgent);
if (userAgent.includes('electron')) { if (userAgent.includes('electron')) {
console.log('[Platform] Detected: desktop (Electron)');
cachedPlatform = 'desktop'; cachedPlatform = 'desktop';
return cachedPlatform; return cachedPlatform;
} }
if (userAgent.includes('cordova')) { if (userAgent.includes('cordova')) {
console.log('[Platform] Detected: mobile (Cordova)');
cachedPlatform = 'mobile'; cachedPlatform = 'mobile';
return cachedPlatform; return cachedPlatform;
} }
console.log('[Platform] Detected: web (default)');
cachedPlatform = 'web'; cachedPlatform = 'web';
return cachedPlatform; return cachedPlatform;
} }

View file

@ -3,16 +3,37 @@ import { createClient } from '@supabase/supabase-js';
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
console.log('[Supabase] URL env var:', supabaseUrl ? '✓ Present' : '✗ Missing'); // Only log in development
console.log('[Supabase] Key env var:', supabaseAnonKey ? '✓ Present' : '✗ Missing'); if (import.meta.env.DEV) {
console.log('[Supabase] URL env var:', supabaseUrl ? '✓ Present' : '✗ Missing');
console.log('[Supabase] Key env var:', supabaseAnonKey ? '✓ Present' : '✗ Missing');
}
if (!supabaseUrl || !supabaseAnonKey) { if (!supabaseUrl || !supabaseAnonKey) {
console.warn('Supabase credentials not found. Auth features may not work.'); if (import.meta.env.DEV) {
console.warn('[Supabase] VITE_SUPABASE_URL:', supabaseUrl); console.warn('Supabase credentials not found. Using fallback credentials.');
console.warn('[Supabase] VITE_SUPABASE_ANON_KEY:', supabaseAnonKey ? supabaseAnonKey.substring(0, 20) + '...' : undefined); }
} }
export const supabase = createClient( export const supabase = createClient(
supabaseUrl || 'https://kmdeisowhtsalsekkzqd.supabase.co', supabaseUrl || 'https://kmdeisowhtsalsekkzqd.supabase.co',
supabaseAnonKey || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImttZGVpc293aHRzYWxzZWtrenFkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTM3Mzc2NTIsImV4cCI6MjA2OTMxMzY1Mn0.2mvk-rDZnHOzdx6Cgcysh51a3cflOlRWO6OA1Z5YWuQ' supabaseAnonKey || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImttZGVpc293aHRzYWxzZWtrenFkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTM3Mzc2NTIsImV4cCI6MjA2OTMxMzY1Mn0.2mvk-rDZnHOzdx6Cgcysh51a3cflOlRWO6OA1Z5YWuQ'
); );
// Verify supabase client is properly initialized
if (!supabase || typeof supabase.from !== 'function') {
console.error('[Supabase] Client initialization failed - supabase.from is not available');
}
// Suppress noisy console errors in production
if (!import.meta.env.DEV) {
const originalError = console.error;
console.error = (...args) => {
const message = args[0]?.toString() || '';
// Filter out known non-critical errors
if (message.includes('Reaction XP') || message.includes('tracking error')) {
return;
}
originalError.apply(console, args);
};
}

View file

@ -1,10 +1,16 @@
import { useState, useRef, useCallback, useEffect } from "react"; import { useState, useRef, useCallback, useEffect } from "react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence, useMotionValue, useTransform, PanInfo } from "framer-motion";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import { useAuth } from "@/lib/auth"; import { useAuth } from "@/lib/auth";
import { useWebSocket } from "@/hooks/use-websocket"; import { useWebSocket } from "@/hooks/use-websocket";
import { getIcon } from "@/lib/iconMap"; import { getIcon } from "@/lib/iconMap";
import { usePlatformLayout, PlatformSwitch } from "@/hooks/use-platform-layout";
import { useHaptics } from "@/hooks/use-haptics";
import { useMobileNative } from "@/hooks/use-mobile-native";
import { useNativeFeatures } from "@/hooks/use-native-features";
import { useBiometricAuth } from "@/hooks/use-biometric-auth";
import { MobileQuickActions } from "@/components/MobileQuickActions";
import { import {
Terminal, FileText, IdCard, Music, Settings, Globe, Terminal, FileText, IdCard, Music, Settings, Globe,
X, Minus, Square, Maximize2, Volume2, Wifi, Battery, X, Minus, Square, Maximize2, Volume2, Wifi, Battery,
@ -16,7 +22,7 @@ import {
Eye, Shield, Zap, Skull, Lock, Unlock, Server, Database, Eye, Shield, Zap, Skull, Lock, Unlock, Server, Database,
TrendingUp, ArrowUp, ArrowDown, Hash, Key, HardDrive, FolderSearch, TrendingUp, ArrowUp, ArrowDown, Hash, Key, HardDrive, FolderSearch,
AlertTriangle, Briefcase, CalendarDays, FolderGit2, MessageSquare, AlertTriangle, Briefcase, CalendarDays, FolderGit2, MessageSquare,
ShoppingCart, Folder, Code ShoppingCart, Folder, Code, Home
} from "lucide-react"; } from "lucide-react";
interface WindowState { interface WindowState {
@ -152,7 +158,14 @@ const WALLPAPERS = [
const KONAMI_CODE = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a']; const KONAMI_CODE = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
export default function AeThexOS() { export default function AeThexOS() {
const [isBooting, setIsBooting] = useState(true); const layout = usePlatformLayout();
const { impact, notification } = useHaptics();
const { keyboardVisible, deviceInfo } = useMobileNative('dark');
const native = useNativeFeatures();
const biometric = useBiometricAuth();
// Skip boot sequence on mobile
const [isBooting, setIsBooting] = useState(!layout.isMobile);
const [bootProgress, setBootProgress] = useState(0); const [bootProgress, setBootProgress] = useState(0);
const [bootStep, setBootStep] = useState(''); const [bootStep, setBootStep] = useState('');
const [windows, setWindows] = useState<WindowState[]>([]); const [windows, setWindows] = useState<WindowState[]>([]);
@ -1130,6 +1143,309 @@ export default function AeThexOS() {
const parallaxX = (mousePosition.x / window.innerWidth - 0.5) * 10; const parallaxX = (mousePosition.x / window.innerWidth - 0.5) * 10;
const parallaxY = (mousePosition.y / window.innerHeight - 0.5) * 10; const parallaxY = (mousePosition.y / window.innerHeight - 0.5) * 10;
// Motion values for mobile gestures (must be outside conditional)
const dragX = useMotionValue(0);
const dragOpacity = useTransform(dragX, [-200, 0, 200], [0.5, 1, 0.5]);
// Mobile-specific layout
if (layout.isMobile) {
const activeWindows = windows.filter(w => !w.minimized);
const currentWindow = activeWindows[activeWindows.length - 1];
return (
<div className="h-screen w-screen bg-gradient-to-br from-gray-900 via-blue-900 to-purple-900 overflow-hidden flex flex-col">
{/* Status Bar */}
<div className="h-12 bg-black/40 backdrop-blur-md flex items-center justify-between px-4 shrink-0">
<div className="flex items-center gap-2">
<span className="text-white text-sm font-medium">{deviceInfo?.model || 'AeThex OS'}</span>
<span className="text-xs px-2 py-0.5 rounded-full bg-cyan-500/30 text-cyan-300 border border-cyan-500/50">
{clearanceTheme.name}
</span>
</div>
<div className="text-white text-xs font-mono">
{time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</div>
</div>
{/* Main Content Area */}
<div className="flex-1 overflow-hidden relative">
{currentWindow ? (
// App Window View
<div className="h-full w-full bg-slate-900 flex flex-col">
{/* App Header */}
<div className="h-14 bg-slate-800/90 border-b border-white/10 flex items-center justify-between px-4 shrink-0">
<button
onClick={() => {
impact('light');
closeWindow(currentWindow.id);
}}
className="w-9 h-9 rounded-lg bg-red-500/20 text-red-400 flex items-center justify-center active:scale-95 transition-transform"
>
<X className="w-5 h-5" />
</button>
<h2 className="text-white font-semibold text-sm">{currentWindow.title}</h2>
<button
onClick={() => {
impact('light');
minimizeWindow(currentWindow.id);
}}
className="w-9 h-9 rounded-lg bg-cyan-500/20 text-cyan-400 flex items-center justify-center active:scale-95 transition-transform"
>
<Minus className="w-5 h-5" />
</button>
</div>
{/* App Content */}
<div className="flex-1 overflow-auto bg-white">
{renderAppContent(currentWindow.component)}
</div>
</div>
) : (
// Home Screen
<div className="h-full overflow-auto px-4 py-6">
<h1 className="text-white text-2xl font-bold mb-6">Applications</h1>
<div className="grid grid-cols-4 gap-4">
{apps.map((app) => (
<button
key={app.id}
onClick={() => {
impact('medium');
openWindow({
id: `${app.id}-${Date.now()}`,
title: app.title,
icon: app.icon,
component: app.component,
x: 0,
y: 0,
width: window.innerWidth,
height: window.innerHeight,
zIndex: maxZIndex + 1,
minimized: false,
maximized: true,
content: renderAppContent(app.component)
});
setMaxZIndex(prev => prev + 1);
}}
className="flex flex-col items-center gap-2 p-3 active:scale-95 transition-transform"
>
<div className="w-14 h-14 bg-gradient-to-br from-cyan-500 to-purple-600 rounded-2xl flex items-center justify-center shadow-lg text-white">
{app.icon}
</div>
<span className="text-white text-xs font-medium text-center line-clamp-2">
{app.title}
</span>
</button>
))}
</div>
</div>
)}
</div>
{/* Bottom Navigation */}
<div className="h-20 bg-black/60 backdrop-blur-xl border-t border-white/10 shrink-0">
<div className="h-full flex items-center justify-around px-4">
<button
onClick={() => {
impact('medium');
windows.forEach(w => closeWindow(w.id));
setShowNotifications(false);
setShowStartMenu(false);
}}
className="flex flex-col items-center gap-1"
>
<Home className="w-6 h-6 text-white" />
<span className="text-white text-[10px]">Home</span>
</button>
<button
onClick={() => {
impact('light');
setShowNotifications(!showNotifications);
setShowStartMenu(false);
}}
className="flex flex-col items-center gap-1 relative"
>
<Bell className="w-6 h-6 text-white" />
{notifications.length > 0 && (
<span className="absolute -top-1 -right-1 w-4 h-4 bg-red-500 text-white text-[10px] rounded-full flex items-center justify-center">
{notifications.length}
</span>
)}
<span className="text-white text-[10px]">Alerts</span>
</button>
<button
onClick={() => {
impact('light');
const settingsApp = apps.find(a => a.id === 'settings');
if (settingsApp) openApp(settingsApp);
}}
className="flex flex-col items-center gap-1"
>
<Settings className="w-6 h-6 text-white" />
<span className="text-white text-[10px]">Settings</span>
</button>
<button
onClick={() => {
impact('light');
setShowStartMenu(!showStartMenu);
setShowNotifications(false);
}}
className="flex flex-col items-center gap-1"
>
<User className="w-6 h-6 text-white" />
<span className="text-white text-[10px]">Profile</span>
</button>
</div>
</div>
{/* Notifications Panel Overlay */}
<AnimatePresence>
{showNotifications && (
<motion.div
initial={{ y: "-100%" }}
animate={{ y: 0 }}
exit={{ y: "-100%" }}
transition={{ type: "spring", damping: 25, stiffness: 250 }}
className="absolute inset-0 z-50 bg-slate-950/98 backdrop-blur-xl flex flex-col"
>
<div className="h-14 px-4 flex items-center justify-between border-b border-white/10 shrink-0">
<h2 className="text-white text-lg font-bold">Notifications</h2>
<button
onClick={() => {
impact('light');
setShowNotifications(false);
}}
className="w-9 h-9 rounded-full bg-white/10 text-white flex items-center justify-center"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="flex-1 overflow-auto p-4">
{notifications.length === 0 ? (
<div className="text-center text-white/50 mt-20">No notifications</div>
) : (
<div className="space-y-3">
{notifications.map((notif, i) => (
<div key={i} className="p-4 rounded-xl bg-slate-800/50 border border-white/10">
<div className="text-white">{notif}</div>
</div>
))}
</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
{/* Profile Panel Overlay */}
<AnimatePresence>
{showStartMenu && (
<motion.div
initial={{ y: "100%" }}
animate={{ y: 0 }}
exit={{ y: "100%" }}
transition={{ type: "spring", damping: 25, stiffness: 250 }}
className="absolute inset-0 z-50 bg-slate-950/98 backdrop-blur-xl flex flex-col"
>
<div className="h-14 px-4 flex items-center justify-between border-b border-white/10 shrink-0">
<h2 className="text-white text-lg font-bold">Profile</h2>
<button
onClick={() => {
impact('light');
setShowStartMenu(false);
}}
className="w-9 h-9 rounded-full bg-white/10 text-white flex items-center justify-center"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="flex-1 overflow-auto p-6">
{user ? (
<div className="space-y-6">
<div className="flex items-center gap-4 p-4 rounded-xl bg-slate-800/50 border border-white/10">
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-cyan-500 to-cyan-400 flex items-center justify-center text-white text-2xl font-bold">
{user.username?.[0]?.toUpperCase() || 'U'}
</div>
<div>
<div className="text-white font-bold text-lg">{user.username}</div>
<div className="text-white/70 text-sm">{user.email}</div>
</div>
</div>
<div className="space-y-2">
<button
onClick={() => {
impact('medium');
const achievementsApp = apps.find(a => a.id === 'achievements');
if (achievementsApp) {
openApp(achievementsApp);
setShowStartMenu(false);
}
}}
className="w-full p-4 rounded-xl bg-slate-800/50 border border-white/10 flex items-center gap-3 text-white active:bg-slate-800/70"
>
<Award className="w-5 h-5 text-cyan-400" />
<span>Achievements</span>
</button>
<button
onClick={() => {
impact('medium');
const projectsApp = apps.find(a => a.id === 'projects');
if (projectsApp) {
openApp(projectsApp);
setShowStartMenu(false);
}
}}
className="w-full p-4 rounded-xl bg-slate-800/50 border border-white/10 flex items-center gap-3 text-white active:bg-slate-800/70"
>
<Briefcase className="w-5 h-5 text-cyan-400" />
<span>Projects</span>
</button>
<button
onClick={() => {
impact('medium');
handleLogout();
}}
className="w-full p-4 rounded-xl bg-red-500/20 border border-red-500/40 flex items-center gap-3 text-red-400 active:bg-red-500/30"
>
<LogOut className="w-5 h-5" />
<span>Logout</span>
</button>
</div>
{deviceInfo && (
<div className="p-4 rounded-xl bg-slate-800/50 border border-white/10">
<div className="text-white/70 text-xs mb-2">Device</div>
<div className="text-white text-sm">
{deviceInfo.manufacturer} {deviceInfo.model}
</div>
<div className="text-white/50 text-xs font-mono mt-1">
{deviceInfo.uuid?.slice(0, 16)}...
</div>
</div>
)}
</div>
) : (
<div className="text-center text-white/50 mt-20">Not logged in</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
{/* Mobile Quick Actions FAB */}
<MobileQuickActions />
</div>
);
}
console.log('🖥️ [OS] Rendering DESKTOP layout (isMobile=false)');
// Desktop/Web layout
return ( return (
<div <div
className="h-screen w-screen overflow-hidden select-none relative transition-all duration-700" className="h-screen w-screen overflow-hidden select-none relative transition-all duration-700"

296
docs/PLATFORM_UI_GUIDE.md Normal file
View file

@ -0,0 +1,296 @@
# Platform-Specific UI Guide
## Overview
AeThex OS now supports platform-adaptive UI that automatically adjusts for mobile, desktop, and web environments while maintaining the same core functionality.
## Quick Start
### 1. Use the Platform Layout Hook
```typescript
import { usePlatformLayout, usePlatformClasses } from '@/hooks/use-platform-layout';
function MyComponent() {
const layout = usePlatformLayout();
const classes = usePlatformClasses();
return (
<div className={classes.container}>
{layout.isMobile ? (
<MobileView />
) : (
<DesktopView />
)}
</div>
);
}
```
### 2. Platform-Specific Rendering
```typescript
import { PlatformSwitch } from '@/hooks/use-platform-layout';
<PlatformSwitch
mobile={<MobileNavigation />}
desktop={<DesktopNavigation />}
web={<WebNavigation />}
fallback={<DefaultNavigation />}
/>
```
### 3. Adaptive Styling
```typescript
const classes = usePlatformClasses();
<Button className={classes.button}>
{/* Automatically h-12 on mobile, h-10 on desktop/web */}
Click Me
</Button>
```
## Platform Detection
The app automatically detects:
- **Mobile**: Capacitor native apps (iOS/Android)
- **Desktop**: Tauri desktop apps (Windows/macOS/Linux)
- **Web**: Browser-based access
Detection is done via the existing [lib/platform.ts](../client/src/lib/platform.ts).
## Layout Patterns
### Mobile-First Approach
```typescript
// Mobile: Bottom navigation, full-width cards, larger touch targets
<div className="grid grid-cols-1 gap-3 px-4">
<Card className="rounded-lg p-4">
<Button className="h-12 text-base w-full">
Large Touch Target
</Button>
</Card>
</div>
```
### Desktop Optimization
```typescript
// Desktop: Top navigation, multi-column layout, compact controls
<div className="grid grid-cols-3 gap-6 px-8">
<Card className="rounded-xl p-6">
<Button className="h-10 text-sm">
Compact Button
</Button>
</Card>
</div>
```
## Responsive Design Strategy
### 1. Layout Changes
- **Mobile**: Single column, bottom navigation, full-screen modals
- **Desktop**: Multi-column, top/side navigation, floating dialogs
- **Web**: Adaptive columns, sticky navigation, responsive dialogs
### 2. Typography
- **Mobile**: Larger base font (16px+) for readability
- **Desktop**: Standard font (14px) for information density
- **Web**: Medium font (15px) for balance
### 3. Spacing
- **Mobile**: Tighter spacing (12px-16px) to maximize screen space
- **Desktop**: Generous spacing (24px-32px) for clarity
- **Web**: Balanced spacing (16px-24px)
### 4. Touch Targets
- **Mobile**: Minimum 44px height for buttons and interactive elements
- **Desktop**: Standard 40px height
- **Web**: 40px height
## Example Implementation
See [PlatformAdaptiveExample.tsx](../client/src/components/PlatformAdaptiveExample.tsx) for a complete example showing:
- Platform-specific headers
- Adaptive grids
- Conditional navigation styles
- Touch-optimized controls
## Common Patterns
### Adaptive Navigation
```typescript
function Navigation() {
const { isMobile } = usePlatformLayout();
if (isMobile) {
return (
<nav className="fixed bottom-0 left-0 right-0 border-t">
<BottomTabBar />
</nav>
);
}
return (
<nav className="fixed top-0 left-0 right-0 border-b">
<TopMenuBar />
</nav>
);
}
```
### Responsive Cards
```typescript
function CardGrid() {
const { isMobile, isDesktop } = usePlatformLayout();
const columns = isMobile ? 'grid-cols-1' :
isDesktop ? 'grid-cols-4' :
'grid-cols-2';
return <div className={`grid ${columns} gap-4`}>
{/* Cards */}
</div>;
}
```
### Adaptive Forms
```typescript
function Form() {
const classes = usePlatformClasses();
return (
<form className={classes.spacing}>
<Input className={classes.input} placeholder="Name" />
<Button className={classes.button} type="submit">
Submit
</Button>
</form>
);
}
```
## Mobile-Specific Features
### Safe Area Support (iOS)
```css
/* In your CSS */
.mobile-container {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}
```
### Pull-to-Refresh
```typescript
import { isMobile } from '@/lib/platform';
if (isMobile()) {
// Enable pull-to-refresh gesture
window.addEventListener('touchstart', handlePullToRefresh);
}
```
### Native Gestures
```typescript
// Swipe navigation on mobile
if (isMobile()) {
<SwipeableViews onChangeIndex={handleSwipe}>
<Page1 />
<Page2 />
</SwipeableViews>
}
```
## Desktop-Specific Features
### Window Controls
```typescript
import { isDesktop } from '@/lib/platform';
if (isDesktop()) {
// Custom title bar, minimize/maximize/close
<TauriTitleBar />
}
```
### Keyboard Shortcuts
```typescript
if (isDesktop()) {
useEffect(() => {
// Ctrl+N for new item, etc.
const handler = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 'n') {
createNew();
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, []);
}
```
## Testing Platform Variants
### In Development
```typescript
// Force platform detection for testing
if (import.meta.env.DEV) {
window.Capacitor = {}; // Test as mobile
// or
window.__TAURI__ = {}; // Test as desktop
}
```
### Chrome DevTools
- Open DevTools → Device Toolbar
- Select mobile device
- The app will detect as web but use mobile viewport
## Best Practices
1. **Design Mobile-First**: Start with mobile constraints, enhance for larger screens
2. **Use Touch-Friendly Sizes**: Minimum 44px tap targets on mobile
3. **Optimize Navigation**: Bottom tabs on mobile, top/side on desktop
4. **Adapt Typography**: Larger on mobile, more compact on desktop
5. **Test on Real Devices**: Emulators are good, but test on actual hardware
6. **Progressive Enhancement**: Core functionality works everywhere, enhancements per platform
## Migration Guide
To update existing components:
1. Import the hooks:
```typescript
import { usePlatformClasses } from '@/hooks/use-platform-layout';
```
2. Replace hardcoded classes:
```typescript
// Before
<div className="px-6 py-4">
// After
const classes = usePlatformClasses();
<div className={classes.container}>
```
3. Add platform switches where needed:
```typescript
<PlatformSwitch
mobile={<MobileComponent />}
fallback={<DesktopComponent />}
/>
```
## Resources
- [Platform Detection](../client/src/lib/platform.ts)
- [Layout Hook](../client/src/hooks/use-platform-layout.ts)
- [Example Component](../client/src/components/PlatformAdaptiveExample.tsx)
- [Capacitor Docs](https://capacitorjs.com/docs)
- [Tauri Docs](https://tauri.app/v1/guides/)

13
ios/.gitignore vendored Normal file
View file

@ -0,0 +1,13 @@
App/build
App/Pods
App/output
App/App/public
DerivedData
xcuserdata
# Cordova plugins for Capacitor
capacitor-cordova-ios-plugins
# Generated Config files
App/App/capacitor.config.json
App/App/config.xml

View file

@ -0,0 +1,376 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
4D22ABE92AF431CB00220026 /* CapApp-SPM in Frameworks */ = {isa = PBXBuildFile; productRef = 4D22ABE82AF431CB00220026 /* CapApp-SPM */; };
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; };
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
504EC3011FED79650016851F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4D22ABE92AF431CB00220026 /* CapApp-SPM in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
504EC2FB1FED79650016851F = {
isa = PBXGroup;
children = (
958DCC722DB07C7200EA8C5F /* debug.xcconfig */,
504EC3061FED79650016851F /* App */,
504EC3051FED79650016851F /* Products */,
);
sourceTree = "<group>";
};
504EC3051FED79650016851F /* Products */ = {
isa = PBXGroup;
children = (
504EC3041FED79650016851F /* App.app */,
);
name = Products;
sourceTree = "<group>";
};
504EC3061FED79650016851F /* App */ = {
isa = PBXGroup;
children = (
50379B222058CBB4000EE86E /* capacitor.config.json */,
504EC3071FED79650016851F /* AppDelegate.swift */,
504EC30B1FED79650016851F /* Main.storyboard */,
504EC30E1FED79650016851F /* Assets.xcassets */,
504EC3101FED79650016851F /* LaunchScreen.storyboard */,
504EC3131FED79650016851F /* Info.plist */,
2FAD9762203C412B000D30F8 /* config.xml */,
50B271D01FEDC1A000F3C39B /* public */,
);
path = App;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
504EC3031FED79650016851F /* App */ = {
isa = PBXNativeTarget;
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */;
buildPhases = (
504EC3001FED79650016851F /* Sources */,
504EC3011FED79650016851F /* Frameworks */,
504EC3021FED79650016851F /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = App;
packageProductDependencies = (
4D22ABE82AF431CB00220026 /* CapApp-SPM */,
);
productName = App;
productReference = 504EC3041FED79650016851F /* App.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
504EC2FC1FED79650016851F /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0920;
TargetAttributes = {
504EC3031FED79650016851F = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 504EC2FB1FED79650016851F;
packageReferences = (
D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */,
);
productRefGroup = 504EC3051FED79650016851F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
504EC3031FED79650016851F /* App */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
504EC3021FED79650016851F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */,
50B271D11FEDC1A000F3C39B /* public in Resources */,
504EC30F1FED79650016851F /* Assets.xcassets in Resources */,
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */,
504EC30D1FED79650016851F /* Main.storyboard in Resources */,
2FAD9763203C412B000D30F8 /* config.xml in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
504EC3001FED79650016851F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
504EC30B1FED79650016851F /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
504EC30C1FED79650016851F /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
504EC3101FED79650016851F /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
504EC3111FED79650016851F /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
504EC3141FED79650016851F /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
504EC3151FED79650016851F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
504EC3171FED79650016851F /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = com.aethex.os;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
504EC3181FED79650016851F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.aethex.os;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
504EC3141FED79650016851F /* Debug */,
504EC3151FED79650016851F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
504EC3171FED79650016851F /* Debug */,
504EC3181FED79650016851F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = "CapApp-SPM";
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
4D22ABE82AF431CB00220026 /* CapApp-SPM */ = {
isa = XCSwiftPackageProductDependency;
package = D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */;
productName = "CapApp-SPM";
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 504EC2FC1FED79650016851F /* Project object */;
}

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,49 @@
import UIKit
import Capacitor
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
// Called when the app was launched with a url. Feel free to add additional processing here,
// but if you want the App API to support tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Called when the app was launched with an activity, including Universal Links.
// Feel free to add additional processing here, but if you want the App API to support
// tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View file

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "AppIcon-512@2x.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "splash-2732x2732-2.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "splash-2732x2732-1.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "splash-2732x2732.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17105"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<imageView key="view" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" id="snD-IY-ifK">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</imageView>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="Splash" width="1366" height="1366"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14111" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
</dependencies>
<scenes>
<!--Bridge View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="CAPBridgeViewController" customModule="Capacitor" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

51
ios/App/App/Info.plist Normal file
View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CAPACITOR_DEBUG</key>
<string>$(CAPACITOR_DEBUG)</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>AeThex OS</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
</plist>

9
ios/App/CapApp-SPM/.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View file

@ -0,0 +1,59 @@
// swift-tools-version: 5.9
import PackageDescription
// DO NOT MODIFY THIS FILE - managed by Capacitor CLI commands
let package = Package(
name: "CapApp-SPM",
platforms: [.iOS(.v15)],
products: [
.library(
name: "CapApp-SPM",
targets: ["CapApp-SPM"])
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.0.0"),
.package(name: "CapacitorApp", path: "..\..\..\node_modules\@capacitor\app"),
.package(name: "CapacitorBrowser", path: "..\..\..\node_modules\@capacitor\browser"),
.package(name: "CapacitorCamera", path: "..\..\..\node_modules\@capacitor\camera"),
.package(name: "CapacitorClipboard", path: "..\..\..\node_modules\@capacitor\clipboard"),
.package(name: "CapacitorDevice", path: "..\..\..\node_modules\@capacitor\device"),
.package(name: "CapacitorFilesystem", path: "..\..\..\node_modules\@capacitor\filesystem"),
.package(name: "CapacitorGeolocation", path: "..\..\..\node_modules\@capacitor\geolocation"),
.package(name: "CapacitorHaptics", path: "..\..\..\node_modules\@capacitor\haptics"),
.package(name: "CapacitorKeyboard", path: "..\..\..\node_modules\@capacitor\keyboard"),
.package(name: "CapacitorLocalNotifications", path: "..\..\..\node_modules\@capacitor\local-notifications"),
.package(name: "CapacitorNetwork", path: "..\..\..\node_modules\@capacitor\network"),
.package(name: "CapacitorPushNotifications", path: "..\..\..\node_modules\@capacitor\push-notifications"),
.package(name: "CapacitorScreenOrientation", path: "..\..\..\node_modules\@capacitor\screen-orientation"),
.package(name: "CapacitorShare", path: "..\..\..\node_modules\@capacitor\share"),
.package(name: "CapacitorSplashScreen", path: "..\..\..\node_modules\@capacitor\splash-screen"),
.package(name: "CapacitorStatusBar", path: "..\..\..\node_modules\@capacitor\status-bar"),
.package(name: "CapacitorToast", path: "..\..\..\node_modules\@capacitor\toast")
],
targets: [
.target(
name: "CapApp-SPM",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm"),
.product(name: "CapacitorApp", package: "CapacitorApp"),
.product(name: "CapacitorBrowser", package: "CapacitorBrowser"),
.product(name: "CapacitorCamera", package: "CapacitorCamera"),
.product(name: "CapacitorClipboard", package: "CapacitorClipboard"),
.product(name: "CapacitorDevice", package: "CapacitorDevice"),
.product(name: "CapacitorFilesystem", package: "CapacitorFilesystem"),
.product(name: "CapacitorGeolocation", package: "CapacitorGeolocation"),
.product(name: "CapacitorHaptics", package: "CapacitorHaptics"),
.product(name: "CapacitorKeyboard", package: "CapacitorKeyboard"),
.product(name: "CapacitorLocalNotifications", package: "CapacitorLocalNotifications"),
.product(name: "CapacitorNetwork", package: "CapacitorNetwork"),
.product(name: "CapacitorPushNotifications", package: "CapacitorPushNotifications"),
.product(name: "CapacitorScreenOrientation", package: "CapacitorScreenOrientation"),
.product(name: "CapacitorShare", package: "CapacitorShare"),
.product(name: "CapacitorSplashScreen", package: "CapacitorSplashScreen"),
.product(name: "CapacitorStatusBar", package: "CapacitorStatusBar"),
.product(name: "CapacitorToast", package: "CapacitorToast")
]
)
]
)

View file

@ -0,0 +1,5 @@
# CapApp-SPM
This SPM is used to host SPM dependencies for you Capacitor project
Do not modify the contents of it or there may be unintended consequences.

View file

@ -0,0 +1 @@
public let isCapacitorApp = true

1
ios/debug.xcconfig Normal file
View file

@ -0,0 +1 @@
CAPACITOR_DEBUG = true

1647
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,12 +6,39 @@
"scripts": { "scripts": {
"dev:client": "vite dev --port 5000", "dev:client": "vite dev --port 5000",
"dev": "NODE_ENV=development tsx server/index.ts", "dev": "NODE_ENV=development tsx server/index.ts",
"dev:tauri": "tauri dev",
"build": "tsx script/build.ts", "build": "tsx script/build.ts",
"build:tauri": "tauri build",
"build:mobile": "npm run build && npx cap sync",
"android": "npx cap open android",
"ios": "npx cap open ios",
"start": "NODE_ENV=production node dist/index.js", "start": "NODE_ENV=production node dist/index.js",
"check": "tsc", "check": "tsc",
"db:push": "drizzle-kit push" "db:push": "drizzle-kit push"
}, },
"dependencies": { "dependencies": {
"@capacitor-community/privacy-screen": "^6.0.0",
"@capacitor/android": "^8.0.0",
"@capacitor/app": "^8.0.0",
"@capacitor/browser": "^8.0.0",
"@capacitor/camera": "^8.0.0",
"@capacitor/cli": "^8.0.0",
"@capacitor/clipboard": "^8.0.0",
"@capacitor/core": "^8.0.0",
"@capacitor/device": "^8.0.0",
"@capacitor/filesystem": "^8.0.0",
"@capacitor/geolocation": "^8.0.0",
"@capacitor/haptics": "^8.0.0",
"@capacitor/ios": "^8.0.0",
"@capacitor/keyboard": "^8.0.0",
"@capacitor/local-notifications": "^8.0.0",
"@capacitor/network": "^8.0.0",
"@capacitor/push-notifications": "^8.0.0",
"@capacitor/screen-orientation": "^8.0.0",
"@capacitor/share": "^8.0.0",
"@capacitor/splash-screen": "^8.0.0",
"@capacitor/status-bar": "^8.0.0",
"@capacitor/toast": "^8.0.0",
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",
"@jridgewell/trace-mapping": "^0.3.25", "@jridgewell/trace-mapping": "^0.3.25",
"@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-accordion": "^1.2.12",
@ -90,6 +117,7 @@
"@replit/vite-plugin-dev-banner": "^0.1.1", "@replit/vite-plugin-dev-banner": "^0.1.1",
"@replit/vite-plugin-runtime-error-modal": "^0.0.4", "@replit/vite-plugin-runtime-error-modal": "^0.0.4",
"@tailwindcss/vite": "^4.1.14", "@tailwindcss/vite": "^4.1.14",
"@tauri-apps/cli": "^2.9.6",
"@types/connect-pg-simple": "^7.0.3", "@types/connect-pg-simple": "^7.0.3",
"@types/express": "4.17.21", "@types/express": "4.17.21",
"@types/express-session": "^1.18.0", "@types/express-session": "^1.18.0",
@ -101,6 +129,7 @@
"@types/ws": "^8.5.13", "@types/ws": "^8.5.13",
"@vitejs/plugin-react": "^5.0.4", "@vitejs/plugin-react": "^5.0.4",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"concurrently": "^9.2.1",
"drizzle-kit": "^0.31.4", "drizzle-kit": "^0.31.4",
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"postcss": "^8.5.6", "postcss": "^8.5.6",

Some files were not shown because too many files have changed in this diff Show more