14 Commits

Author SHA1 Message Date
e82255d8a1 Disable cache
All checks were successful
Flutter Release / get-version (push) Successful in 10s
Flutter Release / build-windows (push) Successful in 1m58s
Flutter Release / build-android (push) Successful in 11m36s
Flutter Release / create-release (push) Successful in 30s
2025-12-15 13:21:50 -05:00
dae47fdeb9 Change upload artifcat 2025-12-15 12:47:08 -05:00
0bf330ac5b no more github actions for windows 2025-12-15 12:43:11 -05:00
435f41133a Go to stable 2025-12-15 12:41:24 -05:00
2f972536b5 FLutter v 2025-12-15 12:40:17 -05:00
7453943c37 Update app icons
Some checks failed
Flutter Release / get-version (push) Successful in 6s
Flutter Release / build-windows (push) Failing after 9s
Flutter Release / create-release (push) Has been cancelled
Flutter Release / build-android (push) Has been cancelled
2025-12-15 12:34:05 -05:00
f4bf073d52 Update SDK 2025-12-15 00:25:01 -05:00
110c5d99a1 Signing, Installer, New Workflows
Some checks failed
Flutter Release / get-version (push) Successful in 7s
Flutter Release / build-windows (push) Failing after 9s
Flutter Release / create-release (push) Has been cancelled
Flutter Release / build-android (push) Has been cancelled
2025-12-15 00:05:29 -05:00
9ff0d62651 check update
Some checks failed
Flutter Release / get-version (push) Successful in 8s
Flutter Release / build-windows (push) Failing after 9s
Flutter Release / build-android (push) Failing after 1m12s
Flutter Release / create-release (push) Has been skipped
2025-12-14 23:11:50 -05:00
e5fdbae3b2 update andriod sdk 2025-12-14 22:20:02 -05:00
2c557cdeac update dependencies
Some checks failed
Flutter Release / get-version (push) Successful in 8s
Flutter Release / build-android (push) Failing after 53s
Flutter Release / build-windows (push) Failing after 9s
Flutter Release / create-release (push) Has been skipped
2025-12-14 22:05:58 -05:00
57ccaad08a update workflows
Some checks failed
Flutter Release / get-version (push) Successful in 8s
Flutter Release / build-android (push) Failing after 1m59s
Flutter Release / build-windows (push) Failing after 10s
Flutter Release / create-release (push) Has been skipped
2025-12-14 22:02:24 -05:00
86040cdd4f Merge branch 'main' of https://git.hudsonriggs.systems/LambdaBankingConglomerate/rmtPocketWatcher
Some checks failed
Flutter Release / get-version (push) Successful in 11s
Flutter Release / build-android (push) Failing after 5m25s
Flutter Release / create-release (push) Has been cancelled
Flutter Release / build-windows (push) Has been cancelled
2025-12-14 21:53:47 -05:00
7ed7a2470d Flutter App 2025-12-14 21:53:46 -05:00
142 changed files with 10135 additions and 322 deletions

View File

@@ -1,49 +1,89 @@
# Gitea Actions - Release Workflow # Gitea Actions - Flutter Release Workflow
This workflow automatically builds and releases rmtPocketWatcher for Windows and Linux when you push a version tag. This workflow automatically builds and releases rmtPocketWatcher Flutter app for Windows and Android when you push a version tag.
## 🚀 Current Build Targets
- **Windows**: Native desktop application (.zip)
- **Android**: APK package (.apk)
## 📱 Migration from Electron
**⚠️ ELECTRON VERSION DEPRECATED**: The Electron version has been replaced with Flutter for better cross-platform support, native performance, and mobile compatibility.
## How to Trigger a Release ## How to Trigger a Release
1. Update version in `electron-app/package.json`: 1. Update version in `flutter_app/pubspec.yaml`:
```bash ```yaml
cd electron-app version: 1.2.3+4 # Update this line
npm version patch # or minor, or major
``` ```
2. Push the tag to Gitea: 2. Push changes to main branch:
```bash ```bash
git add flutter_app/pubspec.yaml
git commit -m "Bump version to 1.2.3"
git push origin main git push origin main
git push origin --tags
``` ```
3. The workflow will automatically: 3. The workflow will automatically:
- Build Windows installer (.exe) - Build Windows desktop application
- Build Linux AppImage and .deb package - Build Android APK
- Create a GitHub/Gitea release - Create a GitHub/Gitea release with both binaries
- Upload all binaries to the release - Include release notes with download instructions
## Requirements ## 🔧 Manual Release Build
- Gitea Actions must be enabled on your repository To trigger a manual release build:
- Runners must be configured for `windows-latest` and `ubuntu-latest`
- Repository must have write permissions for releases
## Manual Build 1. Go to Actions tab in your repository
2. Select "Flutter Release" workflow
3. Click "Run workflow"
To build locally without releasing: This will create production builds for both Windows and Android and publish a release.
## 🏗️ Local Development
### Windows
```bash ```bash
cd electron-app cd flutter_app
npm run electron:build -- --win # Windows flutter pub get
npm run electron:build -- --linux # Linux flutter run -d windows
``` ```
Outputs will be in `electron-app/release/` ### Android
```bash
cd flutter_app
flutter pub get
flutter run -d android # Requires connected device/emulator
```
## Troubleshooting ### Build Release Locally
```bash
cd flutter_app
flutter build windows --release # Windows
flutter build apk --release # Android
```
## 📋 Requirements
- Gitea Actions enabled on repository
- Runners configured for `windows-latest` and `ubuntu-latest`
- Repository write permissions for releases
- Flutter 3.24.0+ installed on runners
- Java 17 for Android builds
## 🔍 Troubleshooting
If the workflow fails: If the workflow fails:
- Check that Node.js 20 is available on runners - Check Flutter version compatibility
- Verify all dependencies install correctly - Verify all dependencies in `pubspec.yaml`
- Ensure Android SDK is properly configured
- Check Gitea Actions logs for specific errors - Check Gitea Actions logs for specific errors
- Ensure GITHUB_TOKEN has proper permissions - Verify GITHUB_TOKEN permissions
## 📦 Release Assets
Each release includes:
- `rmtPocketWatcher-Windows-v{version}.zip` - Windows desktop app
- `rmtPocketWatcher-Android-v{version}.apk` - Android mobile app
- Detailed release notes with installation instructions

View File

@@ -1,55 +0,0 @@
name: Manual Dev Build
on:
workflow_dispatch:
jobs:
build-dev:
runs-on: windows
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Verify Node.js
run: node -v
- name: Install electron-app dependencies
working-directory: electron-app
run: npm ci
- name: Build TypeScript (main + preload)
working-directory: electron-app
run: npm run build:main
- name: Build Renderer (Vite)
working-directory: electron-app
run: npm run build:renderer
- name: List dist directory
working-directory: electron-app
run: |
Write-Host "=== Dist Directory Structure ==="
Get-ChildItem -Recurse dist | Select-Object FullName
- name: Package with electron-builder (unpacked only)
working-directory: electron-app
env:
CSC_IDENTITY_AUTO_DISCOVERY: false
run: npx electron-builder --win --dir
- name: List release directory
working-directory: electron-app
run: |
Write-Host "=== Release Directory ==="
if (Test-Path "release") {
Get-ChildItem -Recurse release | Select-Object FullName, Length
} else {
Write-Host "Release directory not found"
}
- name: Upload unpacked build
uses: actions/upload-artifact@v4
with:
name: rmtPocketWatcher-Windows-Unpacked
path: electron-app/release/win-unpacked/
retention-days: 7

View File

@@ -1,61 +1,304 @@
name: Windows Release name: Flutter Release
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
paths: paths:
- 'electron-app/package.json' - 'flutter_app/pubspec.yaml'
branches: branches:
- main - main
jobs: jobs:
get-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.VERSION }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Get version from pubspec.yaml
id: version
working-directory: flutter_app
shell: bash
run: |
VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //' | sed 's/+.*//')
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
build-windows: build-windows:
runs-on: windows runs-on: windows-latest
needs: get-version
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
# Node 20 should be preinstalled on the Windows host runner; skipping setup-node avoids 7zip download issues. - name: Verify Flutter setup
- name: Verify Node.js shell: powershell
run: node -v run: |
flutter --version
- name: Install electron-app dependencies flutter doctor -v
working-directory: electron-app
run: npm ci
- name: Create production .env file - name: Create production .env file
working-directory: electron-app working-directory: flutter_app
shell: powershell
env: env:
WS_URL: ${{ secrets.WS_URL }} WS_URL: ${{ secrets.WS_URL }}
API_URL: ${{ secrets.API_URL }} API_URL: ${{ secrets.API_URL }}
run: node scripts/create-env.cjs run: |
"WS_URL=$env:WS_URL" | Out-File -FilePath .env -Encoding utf8
"API_URL=$env:API_URL" | Out-File -FilePath .env -Append -Encoding utf8
- name: Verify .env file - name: Install dependencies
working-directory: electron-app working-directory: flutter_app
run: type .env shell: powershell
run: flutter pub get
- name: Build TypeScript - name: Setup Certificate for Signing
working-directory: electron-app working-directory: flutter_app
run: npm run build shell: powershell
- name: Build Windows portable executable (skip signing)
working-directory: electron-app
env: env:
CSC_IDENTITY_AUTO_DISCOVERY: false CERT_BASE64: ${{ secrets.CERT_BASE64 }}
run: npx electron-builder --win portable --config electron-builder.yml CERT_PASSWORD: ${{ secrets.CERT_PASSWORD }}
run: |
if ($env:CERT_BASE64) {
Write-Host "Setting up certificate for code signing..." -ForegroundColor Green
# Create certificates directory if it doesn't exist
if (-not (Test-Path "certificates")) {
New-Item -ItemType Directory -Path "certificates" -Force | Out-Null
}
# Decode base64 certificate and save as PFX
$certBytes = [System.Convert]::FromBase64String($env:CERT_BASE64)
[System.IO.File]::WriteAllBytes("certificates\rmtPocketWatcher.pfx", $certBytes)
Write-Host "✅ Certificate installed successfully" -ForegroundColor Green
} else {
Write-Host "⚠️ No certificate provided - building unsigned" -ForegroundColor Yellow
}
- name: Get version from package.json - name: Build Windows release with installer
id: version working-directory: flutter_app
working-directory: electron-app shell: powershell
run: node scripts/get-version.cjs env:
CERT_PASSWORD: ${{ secrets.CERT_PASSWORD }}
run: |
# Set certificate password environment variable for build script
if ($env:CERT_PASSWORD) {
$env:MSIX_CERTIFICATE_PASSWORD = $env:CERT_PASSWORD
}
# Run our custom build script
.\build_windows.ps1 -Release
# The build script creates: build\rmtPocketWatcher-Windows-v{version}-release.zip
# Rename to simpler format for release
$version = "${{ needs.get-version.outputs.version }}"
# Find the generated zip and rename it
$sourceZip = "build\rmtPocketWatcher-Windows-v$version-release.zip"
if (Test-Path $sourceZip) {
Move-Item $sourceZip "rmtPocketWatcher-Windows-v$version.zip" -Force
Write-Host "Created rmtPocketWatcher-Windows-v$version.zip"
} else {
# Fallback: find any matching zip
$zipFiles = Get-ChildItem -Path "build" -Filter "rmtPocketWatcher-Windows-*.zip" -ErrorAction SilentlyContinue
if ($zipFiles) {
Move-Item $zipFiles[0].FullName "rmtPocketWatcher-Windows-v$version.zip" -Force
Write-Host "Created rmtPocketWatcher-Windows-v$version.zip from $($zipFiles[0].Name)"
}
}
# Build self-extracting portable exe (single file distribution)
Write-Host "Building self-extracting portable executable..."
.\build_sfx.ps1
# Copy SFX exe to root for upload
if (Test-Path "build\windows\sfx\rmtPocketWatcher-v$version-Portable.exe") {
Copy-Item "build\windows\sfx\rmtPocketWatcher-v$version-Portable.exe" "rmtPocketWatcher-Windows-Portable-v$version.exe" -Force
Write-Host "Created rmtPocketWatcher-Windows-Portable-v$version.exe"
}
# Copy MSIX to root for easier upload
$msixFile = Get-ChildItem -Path "build\windows\x64\runner\Release" -Filter "*.msix" -ErrorAction SilentlyContinue | Select-Object -First 1
if ($msixFile) {
Copy-Item $msixFile.FullName "rmtPocketWatcher-Windows-v$version.msix" -Force
Write-Host "Created rmtPocketWatcher-Windows-v$version.msix"
}
# Export certificate for user installation (if certificate exists)
if (Test-Path "certificates\rmtPocketWatcher.pfx") {
Write-Host "Exporting certificate for user installation..."
try {
# Use PowerShell to export the certificate
$pfxPath = "certificates\rmtPocketWatcher.pfx"
$cerPath = "rmtPocketWatcher-Certificate.cer"
$certPassword = if ($env:CERT_PASSWORD) { $env:CERT_PASSWORD } else { "rmtPocketWatcher2024!" }
# Load PFX and export public certificate
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxPath, $certPassword)
$certBytes = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
[System.IO.File]::WriteAllBytes($cerPath, $certBytes)
Write-Host "✅ Certificate exported: $cerPath" -ForegroundColor Green
} catch {
Write-Warning "Failed to export certificate: $($_.Exception.Message)"
}
}
# List created artifacts
Write-Host "Artifacts created:"
Get-ChildItem -Filter "*.zip" | ForEach-Object { Write-Host " - $($_.Name)" }
Get-ChildItem -Filter "*Portable*.exe" | ForEach-Object { Write-Host " - $($_.Name)" }
Get-ChildItem -Filter "*.msix" | ForEach-Object { Write-Host " - $($_.Name)" }
Get-ChildItem -Filter "*.cer" | ForEach-Object { Write-Host " - $($_.Name)" }
- name: Create Release and Upload exe - name: Upload Windows artifacts
uses: actions/upload-artifact@v3
with:
name: rmtPocketWatcher-Windows
path: |
flutter_app/rmtPocketWatcher-Windows-v${{ needs.get-version.outputs.version }}.zip
flutter_app/rmtPocketWatcher-Windows-Portable-v${{ needs.get-version.outputs.version }}.exe
flutter_app/rmtPocketWatcher-Windows-v${{ needs.get-version.outputs.version }}.msix
flutter_app/rmtPocketWatcher-Certificate.cer
retention-days: 30
build-android:
runs-on: ubuntu-latest
needs: get-version
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
cache: false
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Accept Android licenses
shell: bash
run: yes | sdkmanager --licenses || true
- name: Verify Flutter setup
shell: bash
run: flutter doctor -v
- name: Create production .env file
working-directory: flutter_app
shell: bash
env:
WS_URL: ${{ secrets.WS_URL }}
API_URL: ${{ secrets.API_URL }}
run: |
echo "WS_URL=$WS_URL" > .env
echo "API_URL=$API_URL" >> .env
echo "Created .env file:"
cat .env | sed 's/=.*/=***/' # Show keys but mask values
- name: Install dependencies
working-directory: flutter_app
shell: bash
run: flutter pub get
- name: Build Android APK
working-directory: flutter_app
shell: bash
run: flutter build apk --release --verbose
- name: Rename APK
working-directory: flutter_app
shell: bash
run: |
cp build/app/outputs/flutter-apk/app-release.apk rmtPocketWatcher-Android-v${{ needs.get-version.outputs.version }}.apk
- name: Upload Android artifact
uses: actions/upload-artifact@v3
with:
name: rmtPocketWatcher-Android
path: flutter_app/rmtPocketWatcher-Android-v${{ needs.get-version.outputs.version }}.apk
retention-days: 30
create-release:
runs-on: ubuntu-latest
needs: [get-version, build-windows, build-android]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download Windows artifact
uses: actions/download-artifact@v3
with:
name: rmtPocketWatcher-Windows
path: ./artifacts
- name: Download Android artifact
uses: actions/download-artifact@v3
with:
name: rmtPocketWatcher-Android
path: ./artifacts
- name: Create Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
tag_name: v${{ steps.version.outputs.VERSION }} tag_name: v${{ needs.get-version.outputs.version }}
name: rmtPocketWatcher v${{ steps.version.outputs.VERSION }} name: rmtPocketWatcher v${{ needs.get-version.outputs.version }}
draft: false draft: false
prerelease: false prerelease: false
files: electron-app/release/rmtPocketWatcher-*.exe body: |
## rmtPocketWatcher v${{ needs.get-version.outputs.version }}
**Lambda Banking Conglomerate** - Star Citizen AUEC Price Tracker
### Downloads
- **Windows (Full)**: `rmtPocketWatcher-Windows-v${{ needs.get-version.outputs.version }}.zip` - Complete standalone package
- **Windows (Portable)**: `rmtPocketWatcher-Windows-Portable-v${{ needs.get-version.outputs.version }}.exe` - Single self-extracting executable
- **Windows (Installer)**: `rmtPocketWatcher-Windows-v${{ needs.get-version.outputs.version }}.msix` - Windows Store-style installer
- **Certificate**: `rmtPocketWatcher-Certificate.cer` - Required for signed executables (see installation notes)
- **Android**: `rmtPocketWatcher-Android-v${{ needs.get-version.outputs.version }}.apk`
### Features
- Real-time AUEC price tracking from multiple vendors
- Bloomberg-style terminal interface
- Cross-platform native notifications with custom sound
- Historical price charts and trend analysis
- Client-side price alerts
- Vendor comparison tables
### Installation
#### Certificate Installation (Required for signed executables)
If Windows shows "Unknown publisher" warnings, install the certificate first:
1. Download `rmtPocketWatcher-Certificate.cer`
2. Right-click → "Install Certificate"
3. Choose "Local Machine" → "Place all certificates in the following store"
4. Browse → Select "Trusted Root Certification Authorities" → OK
5. Complete the installation
#### Application Installation
**Windows (Full)**: Extract the ZIP file and run `rmtpocketwatcher.exe` - includes all dependencies
**Windows (Portable)**: Just run the .exe - auto-extracts to AppData and launches
**Windows (Installer)**: Double-click the MSIX file for Windows Store-style installation
**Android**: Install the APK file (enable "Install from unknown sources")
---
*Built with Flutter for cross-platform compatibility*
files: |
./artifacts/rmtPocketWatcher-Windows-v${{ needs.get-version.outputs.version }}.zip
./artifacts/rmtPocketWatcher-Windows-Portable-v${{ needs.get-version.outputs.version }}.exe
./artifacts/rmtPocketWatcher-Windows-v${{ needs.get-version.outputs.version }}.msix
./artifacts/rmtPocketWatcher-Certificate.cer
./artifacts/rmtPocketWatcher-Android-v${{ needs.get-version.outputs.version }}.apk
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -18,17 +18,20 @@ This is a monorepo-style project with separate backend and frontend applications
│ ├── Dockerfile │ ├── Dockerfile
│ └── package.json │ └── package.json
├── electron-app/ # Electron + React frontend ├── flutter_app/ # Flutter cross-platform app
│ ├── src/ │ ├── lib/
│ │ ├── main/ # Electron main process │ │ ├── models/ # Data models (PriceData, PriceAlert)
│ │ ├── renderer/ # React UI components │ │ ├── providers/ # State management (Provider)
│ │ │ ├── components/ │ │ ├── services/ # API, WebSocket, Storage services
│ │ │ ├── pages/ │ │ ├── screens/ # UI screens (HomeScreen)
│ │ │ ├── hooks/ │ │ ├── widgets/ # Reusable UI components
│ │ │ └── store/ # Zustand/Recoil state │ │ └── main.dart # App entry point
│ └── shared/ # IPC types & shared code ├── assets/ # Images, fonts, etc.
│ ├── tests/ │ ├── .env # Environment configuration
│ └── package.json │ └── pubspec.yaml # Dependencies
├── electron-app/ # Legacy Electron app (deprecated)
│ └── ... # Kept for reference
├── shared/ # Shared TypeScript types/interfaces ├── shared/ # Shared TypeScript types/interfaces
│ └── types/ │ └── types/
@@ -47,10 +50,11 @@ This is a monorepo-style project with separate backend and frontend applications
- API layer is stateless for horizontal scaling - API layer is stateless for horizontal scaling
- TimescaleDB handles time-series data efficiently - TimescaleDB handles time-series data efficiently
- **Frontend**: Component-based React architecture - **Frontend**: Flutter widget-based architecture
- Sandboxed renderer process for security - Provider pattern for state management
- Secure IPC messaging between main and renderer - Service layer for API/WebSocket communication
- Client-side alert evaluation logic - Client-side alert evaluation logic
- Cross-platform: Windows, macOS, Linux, Android, iOS
- **Database Schema**: Three main tables - **Database Schema**: Three main tables
- `raw_vendor_prices`: Individual vendor listings - `raw_vendor_prices`: Individual vendor listings
@@ -59,8 +63,9 @@ This is a monorepo-style project with separate backend and frontend applications
## Key Conventions ## Key Conventions
- All code in TypeScript with strict type checking - Backend: TypeScript with strict type checking
- Frontend: Dart with null safety
- Scrapers include retry logic (3 attempts) and error handling - Scrapers include retry logic (3 attempts) and error handling
- WebSocket auto-reconnect logic in Electron app - WebSocket auto-reconnect logic in Flutter app
- Signed binaries for all platform distributions - Signed binaries for all platform distributions
- No remote code evaluation in Electron (security) - Flutter: No eval or dynamic code execution (security)

View File

@@ -11,15 +11,18 @@
- **WebSockets**: Native `ws` or Fastify WS plugin - **WebSockets**: Native `ws` or Fastify WS plugin
- **Deployment**: Docker containers - **Deployment**: Docker containers
## Frontend (Electron Desktop App) ## Frontend (Flutter Cross-Platform App)
- **Framework**: Electron 30+ - **Framework**: Flutter 3.38+
- **UI Library**: NextJS? + TypeScript/TSX - **Language**: Dart 3.10+
- **Build Tool**: Vite - **State Management**: Provider
- **Styling**: TailwindCSS - **Charts**: fl_chart
- **Charts**: Recharts, ECharts, or TradingView Charting Library - **Styling**: Material Design 3
- **State Management**: Zustand or Recoil - **WebSocket**: web_socket_channel
- **Auto-updates**: electron-updater - **Storage**: shared_preferences + sqflite
- **Notifications**: flutter_local_notifications
- **Window Management**: window_manager (desktop)
- **Platforms**: Windows, macOS, Linux, Android, iOS
## Testing ## Testing
@@ -36,11 +39,14 @@ npm run build # Build TypeScript
npm run test # Run Jest tests npm run test # Run Jest tests
npm run scrape # Manual scrape trigger npm run scrape # Manual scrape trigger
# Frontend (Electron) # Frontend (Flutter)
npm run dev # Start Electron in dev mode flutter run -d windows # Run on Windows
npm run build # Build production app flutter run -d android # Run on Android
npm run package # Package for distribution flutter run -d ios # Run on iOS
npm run test # Run tests flutter build windows # Build Windows release
flutter build apk # Build Android APK
flutter pub get # Install dependencies
flutter doctor # Check setup
# Database # Database
npm run migrate # Run database migrations npm run migrate # Run database migrations

View File

@@ -16,14 +16,14 @@ Developed by Lambda Banking Conglomerate - A Star Citizen Organization
## Quick Start ## Quick Start
### Using Docker Compose (Recommended) ### 1. Start Backend (Using Docker Compose - Recommended)
```bash ```bash
# Clone the repository # Clone the repository
git clone <repository-url> git clone <repository-url>
cd rmtPocketWatcher cd rmtPocketWatcher
# Start everything # Start backend and database
docker-compose up -d docker-compose up -d
# View logs # View logs
@@ -35,7 +35,29 @@ curl http://localhost:3000/health
The backend will be available at `http://localhost:3000` The backend will be available at `http://localhost:3000`
### Local Development ### 2. Run Flutter App
```bash
cd flutter_app
# Install dependencies
flutter pub get
# Configure environment
cp .env.example .env
# Edit .env if needed (default connects to localhost:3000)
# Run on Windows
flutter run -d windows
# Run on Android
flutter run -d android
# Run on iOS
flutter run -d ios
```
### Local Backend Development
```bash ```bash
# Start database only # Start database only
@@ -67,7 +89,15 @@ rmtPocketWatcher/
│ │ └── index.ts # Main server entry point │ │ └── index.ts # Main server entry point
│ ├── prisma/ # Database schema and migrations │ ├── prisma/ # Database schema and migrations
│ └── Dockerfile │ └── Dockerfile
├── electron-app/ # Electron desktop app (coming soon) ├── flutter_app/ # Flutter cross-platform app
│ ├── lib/
│ │ ├── models/ # Data models
│ │ ├── providers/ # State management
│ │ ├── services/ # API, WebSocket, Storage
│ │ ├── screens/ # UI screens
│ │ └── widgets/ # Reusable components
│ └── pubspec.yaml
├── electron-app/ # Legacy Electron app (deprecated)
├── docker-compose.yml # Docker orchestration ├── docker-compose.yml # Docker orchestration
└── README.md └── README.md
``` ```
@@ -109,11 +139,12 @@ NODE_ENV=production
- PostgreSQL + Prisma ORM - PostgreSQL + Prisma ORM
- Node Scheduler (cron jobs) - Node Scheduler (cron jobs)
**Frontend (Coming Soon):** **Frontend:**
- Electron 30+ - Flutter 3.38+ (cross-platform: Windows, macOS, Linux, Android, iOS)
- React + TypeScript - Dart 3.10+
- TailwindCSS - Provider (state management)
- Recharts/ECharts - fl_chart (charting)
- Material Design 3
## Development ## Development

171
SETUP.md
View File

@@ -1,171 +0,0 @@
# rmtPocketWatcher Setup Guide
## What's Been Created
**Backend Scraper Service**
- Playwright-based scrapers for Eldorado and PlayerAuctions
- Automatic retry logic and error handling
- Scheduled scraping every 5 minutes
- Tracks all seller listings with platform, price, and delivery time
**Database Layer**
- PostgreSQL with Prisma ORM
- Three tables: VendorPrice, PriceIndex, ScrapeLog
- Stores all historical listings for trend analysis
- Indexed for fast queries
**API Layer**
- Fastify REST API with 6 endpoints
- WebSocket for real-time updates
- Filter by seller, platform, date range
- Historical price data
**Docker Setup**
- Docker Compose orchestration
- PostgreSQL container with health checks
- Backend container with auto-migration
- Volume persistence for database
## Current Status
The Docker Compose stack is building. This will:
1. Pull PostgreSQL 16 Alpine image
2. Build the backend Node.js container
3. Install Playwright and dependencies
4. Generate Prisma client
5. Start both services
## Once Build Completes
### Check Status
```bash
# View logs
docker-compose logs -f backend
# Check if services are running
docker ps
```
### Test the API
```bash
# Health check
curl http://localhost:3000/health
# Get latest prices (after first scrape)
curl http://localhost:3000/api/prices/latest
# Get lowest price
curl http://localhost:3000/api/prices/lowest
# Get price history
curl "http://localhost:3000/api/index/history?range=7d"
```
### Monitor Scraping
The backend will automatically:
- Scrape Eldorado and PlayerAuctions every 5 minutes
- Save all listings to the database
- Calculate and store the lowest price
- Log all scrape attempts
Check logs to see scraping activity:
```bash
docker-compose logs -f backend | grep "scraping"
```
## Database Access
### Using Prisma Studio
```bash
cd backend
npm run db:studio
```
### Using psql
```bash
docker exec -it rmtpw-postgres psql -U rmtpw -d rmtpocketwatcher
```
## Stopping and Restarting
```bash
# Stop services
docker-compose down
# Start services
docker-compose up -d
# Rebuild after code changes
docker-compose up --build -d
# View logs
docker-compose logs -f
# Remove everything including data
docker-compose down -v
```
## Environment Variables
Edit `.env` or `docker-compose.yml` to configure:
- `SCRAPE_INTERVAL_MINUTES` - How often to scrape (default: 5)
- `SCRAPER_HEADLESS` - Run browser in headless mode (default: true)
- `PORT` - API server port (default: 3000)
- `DATABASE_URL` - PostgreSQL connection string
## Next Steps
1. **Wait for build to complete** (~2-5 minutes)
2. **Verify services are running**: `docker ps`
3. **Check first scrape**: `docker-compose logs -f backend`
4. **Test API endpoints**: See examples above
5. **Build Electron frontend** (coming next)
## Troubleshooting
### Backend won't start
```bash
# Check logs
docker-compose logs backend
# Restart backend
docker-compose restart backend
```
### Database connection issues
```bash
# Check postgres is healthy
docker-compose ps
# Restart postgres
docker-compose restart postgres
```
### Scraper errors
```bash
# View detailed logs
docker-compose logs -f backend | grep -A 5 "error"
# Check scrape log in database
docker exec -it rmtpw-postgres psql -U rmtpw -d rmtpocketwatcher -c "SELECT * FROM scrape_log ORDER BY timestamp DESC LIMIT 10;"
```
## File Structure
```
rmtPocketWatcher/
├── docker-compose.yml # Docker orchestration
├── .env # Environment variables
├── backend/
│ ├── Dockerfile # Backend container definition
│ ├── prisma/
│ │ └── schema.prisma # Database schema
│ ├── src/
│ │ ├── scrapers/ # Scraping logic
│ │ ├── api/ # REST + WebSocket API
│ │ ├── database/ # Prisma client & repository
│ │ └── index.ts # Main server
│ └── package.json
└── README.md
```

View File

@@ -0,0 +1,62 @@
# ⚠️ DEPRECATED: Electron Version
## Migration Notice
This Electron version of rmtPocketWatcher has been **deprecated** and replaced with a Flutter cross-platform application.
### Why the Migration?
1. **Better Cross-Platform Support**: Flutter provides native performance on Windows, Android, and iOS
2. **Mobile Compatibility**: Native mobile apps with proper notifications and UI
3. **Smaller Bundle Size**: Flutter apps are more efficient than Electron
4. **Native Performance**: Better resource usage and responsiveness
5. **Unified Codebase**: Single codebase for all platforms
### New Flutter App Location
The active development has moved to: `../flutter_app/`
### Features Migrated
**All core functionality maintained:**
- Real-time AUEC price tracking
- Bloomberg-style terminal interface
- WebSocket connections
- Price alerts with native notifications
- Historical charts and data visualization
- Cross-platform compatibility
**New features added:**
- Native mobile support (Android/iOS)
- Custom notification sounds
- Better responsive design
- Loading screens and improved UX
- Platform-specific optimizations
### Installation
**Use the new Flutter version instead:**
1. **Windows**: Download `rmtPocketWatcher-Windows-v{version}.zip` from releases
2. **Android**: Download `rmtPocketWatcher-Android-v{version}.apk` from releases
### Development
**For developers:**
```bash
# Use the Flutter app instead
cd ../flutter_app
flutter pub get
flutter run -d windows # Windows
flutter run -d android # Android
```
### Legacy Support
This Electron version is kept for reference only and will not receive updates. All new features and bug fixes are implemented in the Flutter version.
---
**Lambda Banking Conglomerate**
*Star Citizen AUEC Price Tracking*

2
flutter_app/.env.example Normal file
View File

@@ -0,0 +1,2 @@
WS_URL=ws://localhost:3000/ws/index
API_URL=http://localhost:3000

217
flutter_app/.gitignore vendored Normal file
View File

@@ -0,0 +1,217 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# iOS related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral/
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS related
**/macos/Flutter/GeneratedPluginRegistrant.swift
# Windows related
**/windows/flutter/generated_plugin_registrant.cc
**/windows/flutter/generated_plugin_registrant.h
**/windows/flutter/generated_plugins.cmake
# Linux related
**/linux/flutter/generated_plugin_registrant.cc
**/linux/flutter/generated_plugin_registrant.h
**/linux/flutter/generated_plugins.cmake
# Web related
lib/generated_plugin_registrant.dart
# Coverage
coverage/
# Environment files
.env
.env.local
.env.production
.env.staging
# Firebase
**/ios/Runner/GoogleService-Info.plist
**/android/app/google-services.json
firebase_options.dart
# FVM Version Cache
.fvm/
# Local database files
*.db
*.sqlite
*.sqlite3
# Generated files
*.g.dart
*.freezed.dart
*.gr.dart
# Platform specific build outputs
build/
dist/
out/
# IDE specific
.vscode/settings.json
.vscode/launch.json
*.code-workspace
# Temporary files
*.tmp
*.temp
*~
# OS generated files
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
# Package files
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env.test
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# Flutter build outputs
/build/app/outputs/flutter-apk/
/build/app/outputs/bundle/
/build/app/intermediates/
/build/app/tmp/
/build/web/
/build/windows/
/build/macos/
/build/linux/
# Android specific
android/.gradle/
android/captures/
android/gradlew
android/gradlew.bat
android/local.properties
android/app/src/main/java/io/flutter/plugins/
# iOS specific
ios/Pods/
ios/Runner.xcworkspace/
ios/.symlinks/
ios/Flutter/flutter_export_environment.sh
# Generated plugin files
**/generated_plugin_registrant.dart
**/GeneratedPluginRegistrant.swift
**/generated_plugin_registrant.cc
**/generated_plugin_registrant.h
**/generated_plugins.cmake
# Code signing certificates (NEVER commit private keys!)
certificates/*.pfx
certificates/*.p12
certificates/*.key
# Public certificates can be committed if needed
# certificates/*.cer
# certificates/*.crt

36
flutter_app/.metadata Normal file
View File

@@ -0,0 +1,36 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "66dd93f9a27ffe2a9bfc8297506ce066ff51265f"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
- platform: android
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
- platform: ios
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
- platform: windows
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -0,0 +1,126 @@
# rmtPocketWatcher - Windows Build Instructions
This document explains how to build rmtPocketWatcher for Windows distribution.
## Prerequisites
- Flutter SDK 3.22.3 or later
- Windows 10/11 with Visual Studio Build Tools
- PowerShell (for advanced build script)
## Quick Build (Batch Script)
For a simple build process, use the batch script:
```cmd
build_windows.bat
```
This will create:
- `build\windows\standalone\rmtpocketwatcher.exe` - Standalone executable with all dependencies
- `build\rmtPocketWatcher-Windows-Standalone.zip` - Distribution archive
## Advanced Build (PowerShell Script)
For more control and MSIX installer creation:
```powershell
# Release build (recommended for distribution)
.\build_windows.ps1 -Release
# Debug build (for development)
.\build_windows.ps1 -Debug
```
This creates:
- **Standalone Package**: Complete folder with all dependencies
- **Portable Executable**: Single-file distribution (if possible)
- **MSIX Installer**: Windows Store-style installer
- **Distribution Archive**: ZIP file ready for sharing
## Output Files
After building, you'll find:
### Standalone Distribution
- `build\windows\standalone\` - Complete application folder
- `build\windows\standalone\rmtpocketwatcher.exe` - Main executable
- `build\windows\standalone\data\` - Flutter engine and assets
- `build\windows\standalone\VERSION.txt` - Version information
### Distribution Archives
- `build\rmtPocketWatcher-Windows-v{version}.zip` - Full standalone package
- `build\rmtPocketWatcher-Windows-Portable-v{version}.zip` - Portable version (exe only)
### Installer (if created)
- `build\windows\x64\runner\Release\*.msix` - Windows installer package
## Distribution Options
### Option 1: Standalone ZIP (Recommended)
- Share the `rmtPocketWatcher-Windows-v{version}.zip` file
- Users extract and run `rmtpocketwatcher.exe`
- No installation required
- All dependencies included
### Option 2: Portable Executable
- Share the `rmtPocketWatcher-Windows-Portable-v{version}.zip` file
- Contains only the executable
- Smallest download size
- May require Visual C++ Redistributable on target machine
### Option 3: MSIX Installer
- Share the `.msix` file
- Windows Store-style installation
- Automatic updates support
- Requires Windows 10 version 1809 or later
## Testing
To test the standalone build:
```cmd
cd build\windows\standalone
rmtpocketwatcher.exe
```
## Troubleshooting
### Build Fails
- Ensure Flutter is properly installed: `flutter doctor`
- Check Windows development setup: `flutter doctor -v`
- Clean and retry: `flutter clean && flutter pub get`
### MSIX Creation Fails
- MSIX creation is optional and may fail on some systems
- The standalone executable will still be created
- Install Windows SDK if you need MSIX support
### Runtime Issues
- Ensure the `.env` file is present in the build directory
- Check that all assets are included in the `data` folder
- Verify Visual C++ Redistributable is installed on target machine
## CI/CD Integration
The build scripts are designed to work with the GitHub Actions workflow in `.gitea/workflows/release.yml`. The workflow automatically:
1. Builds both standalone and portable versions
2. Creates MSIX installer (if possible)
3. Packages everything for release
4. Uploads artifacts to the release
## Manual Flutter Commands
If you prefer manual control:
```cmd
# Basic build
flutter build windows --release
# Create MSIX (requires msix package)
flutter pub get
flutter pub run msix:create
```
Note: Manual builds won't include the packaging and organization provided by the build scripts.

View File

@@ -0,0 +1,236 @@
# Code Signing Certificate Guide for rmtPocketWatcher
This guide explains how to create and use code signing certificates for your Windows application to eliminate security warnings and build user trust.
## Quick Start
1. **Create Certificate**: `.\create_certificate.ps1`
2. **Build & Sign**: `.\build_windows.ps1 -Release`
3. **Distribute**: Share the signed executables and optionally the public certificate
## Certificate Types
### Self-Signed Certificates (Free)
- **Cost**: Free
- **Trust Level**: Low (requires manual installation)
- **Best For**: Development, internal distribution, open source projects
- **Limitations**: Users see "Unknown Publisher" warnings initially
### Commercial Certificates ($100-$500/year)
- **Cost**: $100-$500 annually
- **Trust Level**: High (automatically trusted)
- **Best For**: Commercial software, wide distribution
- **Providers**: DigiCert, Sectigo, GlobalSign, Entrust
## Self-Signed Certificate Setup
### Step 1: Create Certificate
```powershell
.\create_certificate.ps1
```
This creates:
- `certificates/rmtPocketWatcher.pfx` - Private certificate (keep secure!)
- `certificates/rmtPocketWatcher.cer` - Public certificate (for distribution)
- `certificates/CERTIFICATE_INFO.txt` - Certificate details
### Step 2: Build with Signing
```powershell
.\build_windows.ps1 -Release
```
This automatically:
- Builds the application
- Signs the executable with your certificate
- Creates signed MSIX installer
- Packages everything for distribution
### Step 3: Manual Signing (if needed)
```powershell
.\sign_executable.ps1 -ExePath "path\to\your\app.exe"
```
## Certificate Installation for Users
### Automatic Installation (Recommended)
When users run your signed app for the first time:
1. Windows shows "Unknown Publisher" warning
2. User clicks "More info" → "Run anyway"
3. Certificate is automatically added to their trusted store
### Manual Installation (Optional)
For organizations or power users:
1. Distribute the `.cer` file alongside your app
2. Users double-click the `.cer` file
3. Click "Install Certificate"
4. Choose "Local Machine" (requires admin) or "Current User"
5. Select "Trusted Root Certification Authorities"
6. Click "Next" and "Finish"
## Commercial Certificate Setup
### Step 1: Purchase Certificate
Popular providers:
- **DigiCert**: $474/year (EV), $239/year (OV)
- **Sectigo**: $199/year (EV), $85/year (OV)
- **GlobalSign**: $249/year (EV), $127/year (OV)
### Step 2: Certificate Validation
- **Organization Validation (OV)**: Business verification (1-3 days)
- **Extended Validation (EV)**: Enhanced verification (1-5 days)
- **Individual**: Personal ID verification
### Step 3: Install Certificate
1. Download certificate from provider
2. Install to Windows Certificate Store
3. Update build scripts with certificate details
### Step 4: Update Configuration
```yaml
# pubspec.yaml
msix_config:
certificate_path: path/to/commercial/cert.pfx
certificate_password: your_secure_password
```
## Security Best Practices
### Certificate Storage
- **Never commit** `.pfx` or `.p12` files to version control
- Store certificates in secure, encrypted locations
- Use strong passwords for certificate files
- Backup certificates securely
### Password Management
- Use strong, unique passwords for certificates
- Store passwords in secure password managers
- Use environment variables in CI/CD pipelines
- Rotate passwords regularly
### CI/CD Integration
```yaml
# GitHub Actions example
- name: Setup Certificate
run: |
echo "${{ secrets.CERT_BASE64 }}" | base64 -d > cert.pfx
- name: Sign Application
run: |
signtool sign /f cert.pfx /p "${{ secrets.CERT_PASSWORD }}" app.exe
```
## Troubleshooting
### "SignTool not found"
**Solution**: Install Windows SDK
- Download from: https://developer.microsoft.com/windows/downloads/windows-sdk/
- Or install Visual Studio with Windows development tools
### "Certificate not valid for code signing"
**Solution**: Ensure certificate has "Code Signing" usage
```powershell
# Check certificate usage
Get-PfxCertificate -FilePath cert.pfx | Select-Object -ExpandProperty Extensions
```
### "Timestamp server unavailable"
**Solution**: Try different timestamp servers
- http://timestamp.digicert.com
- http://timestamp.sectigo.com
- http://timestamp.globalsign.com
### "Access denied" when signing
**Solution**: Run PowerShell as Administrator
```powershell
# Check if running as admin
([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
```
### Users still see warnings
**Possible causes**:
1. Certificate not properly installed
2. Certificate expired
3. System clock incorrect
4. Antivirus interference
## Certificate Lifecycle
### Monitoring Expiration
```powershell
# Check certificate expiration
$cert = Get-PfxCertificate -FilePath "certificates/rmtPocketWatcher.pfx"
$daysUntilExpiry = ($cert.NotAfter - (Get-Date)).Days
Write-Host "Certificate expires in $daysUntilExpiry days"
```
### Renewal Process
1. **Self-signed**: Run `.\create_certificate.ps1 -Force`
2. **Commercial**: Renew through certificate provider
3. Update build scripts with new certificate
4. Re-sign and redistribute applications
### Migration to Commercial
1. Purchase commercial certificate
2. Update `pubspec.yaml` configuration
3. Update build scripts
4. Re-sign all distributed applications
5. Notify users of the change
## Cost-Benefit Analysis
### Self-Signed Certificates
**Pros**:
- Free
- Full control
- Good for development/testing
- Suitable for open source projects
**Cons**:
- Users see warnings initially
- Requires user education
- Not suitable for commercial distribution
### Commercial Certificates
**Pros**:
- Immediate trust
- Professional appearance
- Better user experience
- Required for some distribution channels
**Cons**:
- Annual cost
- Validation process
- Vendor dependency
## Recommendations
### For Development/Testing
- Use self-signed certificates
- Document installation process for users
- Consider upgrading for production releases
### For Commercial Distribution
- Invest in commercial certificates
- Choose reputable certificate authorities
- Plan for certificate lifecycle management
### For Open Source Projects
- Start with self-signed certificates
- Consider community funding for commercial certificates
- Provide clear installation instructions
## Scripts Reference
| Script | Purpose | Usage |
|--------|---------|-------|
| `create_certificate.ps1` | Create self-signed certificate | `.\create_certificate.ps1` |
| `sign_executable.ps1` | Sign individual executables | `.\sign_executable.ps1 -ExePath app.exe` |
| `build_windows.ps1` | Build and sign complete package | `.\build_windows.ps1 -Release` |
## Support
For certificate-related issues:
1. Check Windows Event Viewer for detailed errors
2. Verify certificate validity and usage
3. Test on clean Windows installation
4. Consult certificate provider documentation

View File

@@ -0,0 +1,149 @@
# CI/CD Certificate Setup Guide
This guide explains how to set up code signing certificates for automated builds in your CI/CD pipeline.
## Prerequisites
1. **Certificate Created**: Run `.\create_certificate.ps1` to create your certificate
2. **Local Testing**: Verify signing works locally with `.\build_windows.ps1 -Release`
## Step 1: Encode Certificate
Run the encoding script to prepare your certificate for CI/CD:
```powershell
.\encode_certificate.ps1
```
This creates `certificate_base64.txt` containing your certificate encoded as base64.
## Step 2: Add Action Secrets
### For Gitea Actions:
1. Go to your repository settings
2. Navigate to "Secrets and Variables" → "Actions"
3. Add these secrets:
| Secret Name | Value | Description |
|-------------|-------|-------------|
| `CERT_BASE64` | Contents of `certificate_base64.txt` | Base64 encoded certificate |
| `CERT_PASSWORD` | `rmtPocketWatcher2024!` | Certificate password |
### For GitHub Actions:
1. Go to repository "Settings" → "Secrets and variables" → "Actions"
2. Click "New repository secret"
3. Add the same secrets as above
## Step 3: Security Cleanup
**IMPORTANT**: After adding the secrets, delete the local files:
```powershell
Remove-Item certificate_base64.txt -Force
```
## Step 4: Verify Setup
1. Push a change to trigger the workflow
2. Check the build logs for:
- "✅ Certificate installed successfully"
- "✅ Executable signed successfully"
- "✅ MSIX installer signed successfully"
## How It Works
### Certificate Installation
The workflow automatically:
1. Decodes the base64 certificate
2. Saves it as `certificates/rmtPocketWatcher.pfx`
3. Uses it for signing during the build process
### Signing Process
The build script signs:
- **Standalone executable**: `rmtpocketwatcher.exe`
- **MSIX installer**: `*.msix` file
### Environment Variables
- `CERT_PASSWORD`: Used by signing scripts
- `MSIX_CERTIFICATE_PASSWORD`: Used by MSIX creation
## Troubleshooting
### "Certificate not found" Error
- Verify `CERT_BASE64` secret is set correctly
- Check the base64 encoding is complete (no line breaks)
### "Invalid certificate password" Error
- Verify `CERT_PASSWORD` secret matches your certificate password
- Default password is `rmtPocketWatcher2024!`
### "SignTool not found" Error
- This should not occur in GitHub/Gitea runners
- Windows runners include Windows SDK by default
### Unsigned Executables
- Check workflow logs for certificate setup messages
- Verify both secrets are set correctly
- Ensure certificate is valid and not expired
## Security Best Practices
### Certificate Protection
- Never commit `.pfx` files to version control
- Use repository secrets for sensitive data
- Regularly rotate certificate passwords
### Access Control
- Limit repository access to trusted contributors
- Use branch protection rules
- Require reviews for workflow changes
### Monitoring
- Monitor build logs for signing failures
- Set up notifications for failed builds
- Regularly verify certificate expiration dates
## Commercial Certificate Migration
When upgrading to a commercial certificate:
1. **Obtain Certificate**: Purchase from DigiCert, Sectigo, etc.
2. **Update Secrets**: Replace `CERT_BASE64` with new certificate
3. **Update Password**: Change `CERT_PASSWORD` if different
4. **Test Build**: Verify signing works with new certificate
## Certificate Lifecycle
### Monitoring Expiration
Add this to your workflow to check certificate expiration:
```yaml
- name: Check Certificate Expiration
shell: pwsh
run: |
if (Test-Path "certificates\rmtPocketWatcher.pfx") {
$cert = Get-PfxCertificate -FilePath "certificates\rmtPocketWatcher.pfx"
$daysUntilExpiry = ($cert.NotAfter - (Get-Date)).Days
Write-Host "Certificate expires in $daysUntilExpiry days"
if ($daysUntilExpiry -lt 30) {
Write-Warning "Certificate expires soon!"
}
}
```
### Renewal Process
1. Create new certificate (self-signed or commercial)
2. Encode with `.\encode_certificate.ps1`
3. Update `CERT_BASE64` secret
4. Update `CERT_PASSWORD` if changed
5. Test with a new build
## Support
For issues with certificate setup:
1. Check workflow logs for detailed error messages
2. Verify certificate validity locally first
3. Test encoding/decoding process manually
4. Consult the main Certificate Guide for certificate creation issues

View File

View File

@@ -0,0 +1,204 @@
# rmtPocketWatcher - Complete Deployment Summary
## 🎉 System Overview
Your rmtPocketWatcher Flutter application now has a complete, professional deployment system with:
**Self-Signed Code Signing Certificate**
**Signed Standalone Executable**
**Signed MSIX Installer**
**Automated Build & Signing Pipeline**
**RSS-Based Update System**
**CI/CD Integration**
## 📁 Generated Files
### Certificates (Keep Secure!)
- `certificates/rmtPocketWatcher.pfx` - Private certificate (password: `rmtPocketWatcher2024!`)
- `certificates/rmtPocketWatcher.cer` - Public certificate for user installation
- `certificates/CERTIFICATE_INFO.txt` - Certificate details and instructions
### Distribution Files
- `build/windows/standalone/rmtpocketwatcher.exe` - **Signed standalone executable**
- `build/windows/x64/runner/Release/rmtpocketwatcher.msix` - **Signed MSIX installer**
- `build/rmtPocketWatcher-Windows-v1.0.1-release.zip` - Complete distribution package
## 🚀 Quick Start Commands
### Create Certificate (One-time setup)
```powershell
.\create_certificate.ps1
```
### Build & Sign Everything
```powershell
.\build_windows.ps1 -Release
```
### Sign Individual Files
```powershell
.\sign_executable.ps1 -ExePath "path\to\app.exe"
```
## 📦 Distribution Options
### Option 1: Standalone ZIP (Recommended)
**File**: `rmtPocketWatcher-Windows-v1.0.1-release.zip`
- **Size**: ~50-100MB
- **User Experience**: Extract and run - no installation needed
- **Trust Level**: Signed executable reduces Windows warnings
- **Best For**: General distribution, users without admin rights
### Option 2: MSIX Installer
**File**: `rmtpocketwatcher.msix`
- **Size**: ~30-60MB
- **User Experience**: Double-click to install via Windows Package Manager
- **Trust Level**: Signed installer, clean install/uninstall
- **Best For**: Users who prefer traditional installation, enterprise deployment
### Option 3: Public Certificate Distribution
**File**: `rmtPocketWatcher.cer`
- **Size**: ~2KB
- **Purpose**: Pre-install certificate for enhanced trust
- **Best For**: Organizations, power users, eliminating all warnings
## 🔒 Security Features
### Code Signing Benefits
-**Eliminates "Unknown Publisher" warnings**
-**Verifies file integrity** (detects tampering)
-**Establishes publisher identity**
-**Enables Windows SmartScreen trust**
-**Professional appearance**
### Certificate Details
- **Subject**: Lambda Banking Conglomerate
- **Valid**: 3 years (until December 2028)
- **Algorithm**: SHA256 with RSA encryption
- **Timestamp**: DigiCert timestamp server (ensures validity even after cert expires)
## 🔄 Update System
### Automatic Updates
- Checks RSS feed every 4 hours: `https://git.hudsonriggs.systems/LambdaBankingConglomerate/rmtPocketWatcher/releases.rss`
- Shows notification banner when updates available
- Users can manually check via title bar button
- Supports multiple download formats (Portable, Full, MSIX)
### Version Management
- Current version: `1.0.1` (from pubspec.yaml)
- Semantic versioning: MAJOR.MINOR.PATCH
- Automatic CI/CD releases on version changes
## 🏗️ CI/CD Pipeline
### Automated Workflow
The `.gitea/workflows/release.yml` automatically:
1. **Detects version changes** in pubspec.yaml
2. **Builds Windows & Android** versions
3. **Signs all executables** (when certificates available)
4. **Creates multiple distribution formats**
5. **Publishes to releases page** with detailed notes
### Manual Triggers
- Push to main branch with version change
- Manual workflow dispatch
- Tag creation (v1.0.1 format)
## 👥 User Instructions
### For End Users (Standalone ZIP)
```
1. Download rmtPocketWatcher-Windows-v1.0.1-release.zip
2. Extract to any folder (Desktop, Program Files, etc.)
3. Double-click rmtpocketwatcher.exe
4. If Windows shows a warning:
- Click "More info" → "Run anyway" (first time only)
- Certificate will be automatically trusted for future runs
```
### For End Users (MSIX Installer)
```
1. Download rmtpocketwatcher.msix
2. Double-click the file
3. Click "Install" when prompted
4. Find "rmtPocketWatcher" in Start Menu
5. Updates can be installed over existing version
```
### For Organizations (Certificate Pre-installation)
```
1. Distribute rmtPocketWatcher.cer to users
2. Users double-click and install to "Trusted Root"
3. All future app versions will be automatically trusted
4. No security warnings for any Lambda Banking Conglomerate software
```
## 🛠️ Maintenance
### Certificate Renewal (Every 3 Years)
```powershell
# Check expiration
$cert = Get-PfxCertificate -FilePath "certificates/rmtPocketWatcher.pfx"
$daysLeft = ($cert.NotAfter - (Get-Date)).Days
Write-Host "Certificate expires in $daysLeft days"
# Renew certificate
.\create_certificate.ps1 -Force
```
### Upgrading to Commercial Certificate
1. Purchase from DigiCert, Sectigo, or similar ($100-500/year)
2. Update `pubspec.yaml` with new certificate path
3. Update build scripts with new password
4. Re-sign and redistribute applications
## 📊 Trust Levels Comparison
| Distribution Method | Initial Trust | User Action Required | Long-term Trust |
|-------------------|---------------|---------------------|-----------------|
| **Unsigned** | ❌ High warnings | Click through multiple warnings | ❌ Always warns |
| **Self-signed** | ⚠️ Moderate warning | "More info" → "Run anyway" | ✅ Trusted after first run |
| **Self-signed + Pre-installed Cert** | ✅ Full trust | None | ✅ Always trusted |
| **Commercial Certificate** | ✅ Full trust | None | ✅ Always trusted |
## 🎯 Recommendations
### For Development/Testing
- ✅ Current self-signed setup is perfect
- Provides professional appearance
- Eliminates most user friction
### For Commercial Distribution
- Consider upgrading to commercial certificate ($200-500/year)
- Provides immediate trust without user interaction
- Required for some enterprise environments
### For Open Source Projects
- ✅ Current setup is ideal
- Document certificate installation for power users
- Consider community funding for commercial certificate
## 📞 Support & Troubleshooting
### Common Issues
1. **"Windows protected your PC"** - Click "More info" → "Run anyway"
2. **Certificate expired** - Run `.\create_certificate.ps1 -Force`
3. **SignTool not found** - Install Windows SDK
4. **Access denied** - Run PowerShell as Administrator
### Getting Help
- Check `CERTIFICATE_GUIDE.md` for detailed troubleshooting
- Review Windows Event Viewer for signing errors
- Verify certificate validity with `Get-AuthenticodeSignature`
## 🏆 Achievement Unlocked!
Your rmtPocketWatcher application now has:
- **Professional code signing** ✅
- **Multiple distribution formats** ✅
- **Automated build pipeline** ✅
- **Built-in update system** ✅
- **Enterprise-ready deployment** ✅
Users will see "Lambda Banking Conglomerate" as the verified publisher, eliminating security warnings and building trust in your Star Citizen AUEC price tracking application!

View File

@@ -0,0 +1,163 @@
# rmtPocketWatcher - Distribution Guide
This guide explains how to distribute rmtPocketWatcher to end users.
## Available Distribution Formats
### 1. Standalone ZIP Package (Recommended)
**File**: `rmtPocketWatcher-Windows-v{version}.zip`
- **Size**: ~50-100MB (includes all dependencies)
- **Requirements**: Windows 10/11 (any edition)
- **Installation**: Extract ZIP and run `rmtpocketwatcher.exe`
- **Pros**: Works on any Windows system, no installation needed
- **Cons**: Larger download size
### 2. MSIX Installer
**File**: `rmtpocketwatcher.msix`
- **Size**: ~30-60MB
- **Requirements**: Windows 10 version 1809+ or Windows 11
- **Installation**: Double-click to install via Windows Package Manager
- **Pros**: Clean installation/uninstallation, automatic updates support
- **Cons**: Requires newer Windows versions
### 3. Portable Executable (Future)
**File**: `rmtPocketWatcher-Windows-Portable-v{version}.zip`
- **Size**: ~5-15MB (single executable)
- **Requirements**: Windows 10/11 + Visual C++ Redistributable
- **Installation**: Extract and run `rmtpocketwatcher.exe`
- **Pros**: Smallest download, truly portable
- **Cons**: May require additional runtime libraries
## Distribution Channels
### Direct Download
1. Upload files to your Gitea releases page
2. Users download appropriate version for their system
3. Provide installation instructions
### GitHub/Gitea Releases
- Automated via CI/CD pipeline
- Includes release notes and changelogs
- Multiple download options in one place
## User Instructions
### For Standalone ZIP (Most Users)
```
1. Download rmtPocketWatcher-Windows-v{version}.zip
2. Extract the ZIP file to any folder (e.g., Desktop, Program Files)
3. Double-click rmtpocketwatcher.exe to run
4. No installation or admin rights required
```
### For MSIX Installer (Advanced Users)
```
1. Download rmtpocketwatcher.msix
2. Double-click the file
3. Click "Install" when prompted
4. Find "rmtPocketWatcher" in Start Menu
5. Uninstall via Settings > Apps if needed
```
## System Requirements
### Minimum Requirements
- **OS**: Windows 10 version 1903 or later
- **RAM**: 4GB (8GB recommended)
- **Storage**: 200MB free space
- **Network**: Internet connection for price data
### Recommended Requirements
- **OS**: Windows 11
- **RAM**: 8GB or more
- **Storage**: 1GB free space (for data storage)
- **Network**: Stable broadband connection
## Troubleshooting
### Common Issues
#### "Windows protected your PC" SmartScreen Warning
- Click "More info" → "Run anyway"
- This happens because the app isn't digitally signed
- Consider code signing for production releases
#### Missing Visual C++ Runtime
- Download and install Microsoft Visual C++ Redistributable
- Usually only affects portable versions
- Standalone ZIP includes all dependencies
#### Antivirus False Positives
- Some antivirus software may flag the executable
- Add exception for rmtpocketwatcher.exe
- This is common with unsigned executables
#### App Won't Start
- Check Windows Event Viewer for error details
- Ensure .env file is present (for standalone version)
- Try running as administrator
### Performance Issues
- Close other resource-intensive applications
- Check network connectivity for real-time data
- Consider increasing Windows virtual memory
## Security Considerations
### For Developers
- Consider code signing certificates for production
- Implement automatic update verification
- Use HTTPS for all network communications
### For Users
- Download only from official sources
- Verify file checksums if provided
- Keep Windows and antivirus software updated
## Update Process
### Automatic Updates (Built-in)
- App checks for updates every 4 hours
- Shows notification banner when available
- Users can manually check via refresh button
- Downloads handled by system browser
### Manual Updates
- Download new version
- Replace old files with new ones (standalone)
- Or install new MSIX over existing installation
## Support Information
### Getting Help
- Check the GitHub/Gitea issues page
- Review the README.md file
- Contact Lambda Banking Conglomerate
### Reporting Issues
- Include Windows version and build number
- Describe steps to reproduce the problem
- Attach relevant log files if available
- Mention which distribution format you're using
## Developer Notes
### Building for Distribution
```powershell
# Create all distribution formats
.\build_windows.ps1 -Release
# Test the build
cd build\windows\standalone
.\rmtpocketwatcher.exe
```
### CI/CD Integration
- Builds are automated via GitHub Actions
- Releases are created automatically on version changes
- All distribution formats are included in releases
### Version Management
- Update version in `pubspec.yaml`
- Follow semantic versioning (MAJOR.MINOR.PATCH)
- Include changelog in release notes

120
flutter_app/README.md Normal file
View File

@@ -0,0 +1,120 @@
# rmtPocketWatcher Flutter App
A cross-platform Flutter application for tracking Star Citizen AUEC prices.
## Setup Requirements
### Windows Desktop Development
You need Visual Studio 2022 with specific components. Run the Visual Studio Installer and ensure you have:
1. **Desktop development with C++** workload
2. **MSVC v143 - VS 2022 C++ x64/x86 build tools** (latest version)
3. **Windows 11 SDK** (10.0.22621.0 or later)
4. **CMake tools for Visual Studio**
### Quick Fix for Visual Studio Components
1. Open **Visual Studio Installer**
2. Click **Modify** on Visual Studio Community 2022
3. Go to **Workloads** tab
4. Check **Desktop development with C++**
5. Go to **Individual components** tab
6. Ensure these are checked:
- MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest)
- Windows 11 SDK (10.0.22621.0)
- CMake tools for Visual Studio
7. Click **Modify** and wait for installation
## Development Commands
```bash
# Install dependencies
flutter pub get
# Check setup
flutter doctor
# Run on Windows (after fixing VS components)
flutter run -d windows
# Build for Windows
flutter build windows
# Run on web (works without C++ components)
flutter run -d chrome
```
## Project Structure
```
lib/
├── main.dart # App entry point
├── models/
│ └── price_data.dart # Data models
├── providers/
│ └── price_provider.dart # State management
├── screens/
│ └── home_screen.dart # Main dashboard
├── services/
│ ├── api_service.dart # REST API
│ ├── websocket_service.dart # WebSocket
│ └── storage_service.dart # Local storage
└── widgets/
├── alerts_panel.dart # Price alerts
├── price_chart.dart # Charts
├── price_stats_card.dart # Stats cards
└── vendor_table.dart # Vendor table
```
## Features
- **Real-time price tracking** via WebSocket
- **Bloomberg-style dashboard** with stats cards
- **Interactive price charts** with fl_chart
- **Price alerts** with local notifications
- **Vendor comparison table** with sorting
- **Cross-platform support** (Windows, macOS, Linux, Android, iOS)
## Backend Integration
The app connects to the existing TypeScript backend:
- **API**: `http://localhost:3000`
- **WebSocket**: `ws://localhost:3000/ws/index`
Configure in `.env` file:
```env
API_URL=http://localhost:3000
WS_URL=ws://localhost:3000/ws/index
```
## Troubleshooting
### "Visual Studio is missing necessary components"
- Install the C++ workload and components listed above
- Restart your terminal after installation
- Run `flutter doctor` to verify
### "Unable to find directory entry in pubspec.yaml"
- Ensure the `assets/` directory exists
- Run `flutter clean && flutter pub get`
### WebSocket connection issues
- Ensure backend is running on port 3000
- Check firewall settings
- Verify `.env` configuration
## Mobile Support
The same codebase works on mobile with responsive design:
- **Desktop**: Multi-column layout
- **Mobile**: Single-column scrollable layout
- **Responsive charts** and tables
```bash
# Run on Android (requires Android Studio)
flutter run -d android
# Run on iOS (requires Xcode on macOS)
flutter run -d ios
```

View File

@@ -0,0 +1,60 @@
# Update System
The rmtPocketWatcher Flutter app includes an automatic update checking system that monitors the Gitea repository for new releases.
## How It Works
1. **Automatic Checks**: The app checks for updates every 4 hours automatically
2. **Manual Checks**: Users can manually check for updates using the refresh button in the title bar
3. **Version Comparison**: Compares current app version (from pubspec.yaml) with latest release tag
4. **Platform Detection**: Automatically detects the appropriate download asset for the current platform
## Components
### UpdateService
- Fetches releases from Gitea RSS feed: `https://git.hudsonriggs.systems/LambdaBankingConglomerate/rmtPocketWatcher/releases.rss`
- Parses XML/RSS format to extract release information
- Compares version numbers and generates platform-specific download URLs
### UpdateProvider
- Manages update checking state and notifications
- Stores last check time and dismissed updates in SharedPreferences
- Provides reactive state for UI components
### UpdateNotificationBanner
- Shows a dismissible banner when updates are available
- Displays version information and release details
- Provides direct links to download or view release page
## Configuration
The system is configured to work with your Gitea repository structure:
- Repository: `LambdaBankingConglomerate/rmtPocketWatcher`
- RSS Feed: `https://git.hudsonriggs.systems/LambdaBankingConglomerate/rmtPocketWatcher/releases.rss`
- Release tags should follow semantic versioning (e.g., `v1.0.0`, `v1.2.3`)
- Expected asset naming convention:
- Windows: `rmtPocketWatcher-Windows-v{version}.zip`
- Android: `rmtPocketWatcher-Android-v{version}.apk`
- Future platforms can be added with similar naming patterns
## Usage
The update system is automatically initialized when the app starts:
```dart
// In HomeScreen initState
context.read<UpdateProvider>().initialize();
context.read<UpdateProvider>().startPeriodicChecks();
```
Users will see:
1. A notification banner when updates are available
2. An update icon in the title bar (desktop)
3. A detailed dialog with release notes and download options
## Privacy & Security
- Only checks public release information
- No personal data is transmitted
- Downloads are handled by the system browser/app store
- Users control when to update (no forced updates)

View File

@@ -0,0 +1,230 @@
# Window Management & System Tray Guide
Your rmtPocketWatcher application now includes comprehensive window management and system tray functionality for a professional desktop experience.
## 🪟 Window Controls
### Title Bar Features
- **Draggable Area**: Click and drag anywhere on the title bar to move the window
- **Double-click**: Double-click the title bar to maximize/restore the window
- **Custom Controls**: Professional-looking minimize, maximize, and close buttons
### Window Control Buttons
| Button | Icon | Function | Tooltip |
|--------|------|----------|---------|
| **Minimize** | `` | Minimizes to system tray | "Minimize to system tray" |
| **Maximize** | `⛶`/`⧉` | Toggle maximize/restore | "Maximize window" / "Restore window" |
| **Close** | `×` | Closes to system tray | "Close to system tray" |
### Key Behaviors
- **Close button** minimizes to tray instead of exiting the application
- **Minimize button** sends the window directly to the system tray
- **Window dragging** works from anywhere on the title bar
- **Maximize toggle** remembers window state
## 🔔 System Tray Integration
### Tray Icon Features
- **Dynamic Tooltip**: Shows current connection status
- **Single Click**: Toggle window visibility (show/hide)
- **Right Click**: Opens context menu with options
- **Visual Indicator**: Icon represents the application state
### System Tray Menu
| Menu Item | Function |
|-----------|----------|
| **Show rmtPocketWatcher** | Restores window from tray |
| **Check for Updates** | Shows window and triggers update check |
| **🔄 Update Available!** | Appears when updates are detected |
| **About** | Shows application information dialog |
| **Exit** | Completely closes the application |
### Dynamic Status Updates
- **Tooltip Updates**: Reflects connection status ("Connected", "Disconnecting", etc.)
- **Menu Updates**: Shows update availability in real-time
- **Status Integration**: Syncs with price provider and update provider
## 🎛️ User Experience
### Window Lifecycle
1. **Startup**: Window appears normally
2. **Minimize**: Window hides to system tray
3. **Tray Click**: Window restores and focuses
4. **Close**: Window hides to tray (doesn't exit)
5. **Exit**: Only via tray menu "Exit" option
### Professional Features
- **No Taskbar Clutter**: Minimized windows don't show in taskbar
- **Background Operation**: App continues running when minimized
- **Quick Access**: Single click to restore from tray
- **Status Awareness**: Tray shows current application state
## 🔧 Technical Implementation
### Core Components
#### WindowService (`lib/services/window_service.dart`)
- Manages all window operations
- Handles system tray integration
- Provides event listeners for window and tray events
- Integrates with application providers
#### Key Methods
```dart
WindowService().minimizeToTray() // Hide to system tray
WindowService().showWindow() // Restore from tray
WindowService().maximizeWindow() // Toggle maximize state
WindowService().closeWindow() // Close to tray
WindowService().exitApp() // Complete application exit
```
#### Provider Integration
- **PriceProvider**: Updates tray tooltip with connection status
- **UpdateProvider**: Updates tray menu with update availability
- **Real-time Sync**: Tray reflects current application state
### Event Handling
#### Window Events
- `onWindowClose()`: Intercepts close and minimizes to tray
- `onWindowMinimize()`: Handles minimize operations
- `onWindowRestore()`: Manages window restoration
- `onWindowMaximize()`/`onWindowUnmaximize()`: Toggle states
#### Tray Events
- `onTrayIconMouseDown()`: Single click to toggle visibility
- `onTrayIconRightMouseDown()`: Right click for context menu
- `onTrayMenuItemClick()`: Handle menu item selections
## 🎨 Visual Design
### Title Bar Styling
- **Background**: Dark theme (`#1A1F3A`)
- **Text**: White application name and organization
- **Buttons**: Consistent with application theme
- **Height**: 40px for comfortable interaction
### System Tray
- **Icon**: Application logo (falls back to analytics icon)
- **Tooltip**: Dynamic status information
- **Menu**: Consistent with application theme
## 🚀 Usage Examples
### For End Users
#### Basic Window Management
```
• Drag title bar to move window
• Double-click title bar to maximize
• Click minimize () to hide to tray
• Click close (×) to hide to tray
• Right-click tray icon for menu
• Click "Exit" in tray menu to quit
```
#### System Tray Operations
```
• Single-click tray icon: Show/hide window
• Right-click tray icon: Open context menu
• Menu → "Show rmtPocketWatcher": Restore window
• Menu → "Check for Updates": Check for new versions
• Menu → "About": View application information
• Menu → "Exit": Completely close application
```
### For Developers
#### Integrating with Providers
```dart
// Update tray status based on connection
WindowService().updateTrayTooltip('Connected to servers');
// Update menu with update availability
WindowService().updateTrayMenu(hasUpdate: true);
// Show about dialog from tray
WindowService()._showAboutDialog();
```
## 🔒 Security & Privacy
### Safe Operations
- **No Data Collection**: Window management doesn't collect user data
- **Local Storage**: All preferences stored locally
- **Secure Minimize**: Sensitive data hidden when minimized
- **Clean Exit**: Proper cleanup on application exit
### User Control
- **Explicit Actions**: All operations require user interaction
- **Clear Feedback**: Visual and tooltip feedback for all actions
- **Reversible**: All window states can be restored
- **Transparent**: Clear indication of application status
## 🛠️ Troubleshooting
### Common Issues
#### System Tray Icon Not Appearing
- **Cause**: System tray initialization failed
- **Solution**: Check Windows notification area settings
- **Workaround**: Use window controls instead of tray
#### Window Won't Restore
- **Cause**: Window service not initialized
- **Solution**: Restart application
- **Debug**: Check console for initialization errors
#### Tray Menu Not Working
- **Cause**: Context menu creation failed
- **Solution**: Update tray manager package
- **Workaround**: Use window controls
### Debug Information
```dart
// Check window service status
print('Window service initialized: ${WindowService().isInitialized}');
print('Minimized to tray: ${WindowService().isMinimizedToTray}');
```
## 📈 Future Enhancements
### Planned Features
- **Tray Notifications**: Show price alerts in tray
- **Quick Actions**: Direct price check from tray menu
- **Status Icons**: Different icons for different connection states
- **Keyboard Shortcuts**: Global hotkeys for window operations
### Customization Options
- **Tray Behavior**: Option to minimize to taskbar instead
- **Close Behavior**: Option to exit instead of minimize
- **Startup Options**: Start minimized to tray
- **Theme Integration**: Tray menu theme matching
## 📋 Summary
Your rmtPocketWatcher application now provides:
**Professional Window Management**
- Draggable title bar with custom controls
- Proper minimize, maximize, and close operations
- Seamless window state management
**Complete System Tray Integration**
- Hide to tray instead of taskbar clutter
- Dynamic status updates in tooltip
- Full-featured context menu
**User-Friendly Experience**
- Intuitive window operations
- Clear visual feedback
- Professional desktop application behavior
**Developer-Friendly Architecture**
- Clean service-based implementation
- Provider integration for real-time updates
- Extensible for future enhancements
The application now behaves like a professional desktop application with proper window management and system tray integration, providing users with a seamless and intuitive experience for monitoring AUEC prices.

View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

14
flutter_app/android/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,49 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.lambdabanking.rmtpocketwatcher"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.lambdabanking.rmtpocketwatcher"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,54 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Internet permissions (required for API/WebSocket) -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Notification permissions -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:label="rmtpocketwatcher"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,5 @@
package com.lambdabanking.rmtpocketwatcher
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,9 @@
<?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>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

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

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip

View File

@@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
}
include(":app")

View File

@@ -0,0 +1 @@
# Keep this directory in git

BIN
flutter_app/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

81
flutter_app/build_sfx.ps1 Normal file
View File

@@ -0,0 +1,81 @@
# rmtPocketWatcher Self-Extracting Executable Builder
# Creates a single .exe that extracts and runs the app
$ErrorActionPreference = "Stop"
$7zPath = "${env:ProgramFiles}\7-Zip\7z.exe"
if (-not (Test-Path $7zPath)) {
$7zPath = "${env:ProgramFiles(x86)}\7-Zip\7z.exe"
}
if (-not (Test-Path $7zPath)) {
Write-Error "7-Zip not found. Please install 7-Zip from https://7-zip.org"
exit 1
}
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$StandaloneDir = Join-Path $ScriptDir "build\windows\standalone"
$OutputDir = Join-Path $ScriptDir "build\windows\sfx"
$SfxModule = "${env:ProgramFiles}\7-Zip\7z.sfx"
$PubspecPath = Join-Path $ScriptDir "pubspec.yaml"
if (-not (Test-Path $StandaloneDir)) {
Write-Error "Standalone build not found. Run build_windows.ps1 first."
exit 1
}
Write-Host "Creating self-extracting executable..." -ForegroundColor Green
# Create output directory
if (Test-Path $OutputDir) { Remove-Item -Recurse -Force $OutputDir }
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
# Create SFX config file - silent extract to AppData and run
$SfxConfig = @"
;!@Install@!UTF-8!
Title="rmtPocketWatcher"
InstallPath="%LOCALAPPDATA%\\rmtPocketWatcher"
RunProgram="rmtpocketwatcher.exe"
GUIMode="2"
OverwriteMode="2"
;!@InstallEnd@!
"@
$SfxConfigPath = "$OutputDir\sfx_config.txt"
$SfxConfig | Out-File -FilePath $SfxConfigPath -Encoding UTF8
# Create 7z archive of standalone folder
$ArchivePath = "$OutputDir\app.7z"
Write-Host "Compressing application..." -ForegroundColor Yellow
& $7zPath a -t7z -mx=9 -mf=BCJ2 -r $ArchivePath "$StandaloneDir\*" | Out-Null
if (-not (Test-Path $ArchivePath)) {
Write-Error "Failed to create archive"
exit 1
}
# Combine SFX module + config + archive
$Version = (Select-String -Path $PubspecPath -Pattern "^version: (.+)$").Matches[0].Groups[1].Value -replace '\+.*', ''
$SfxExePath = "$OutputDir\rmtPocketWatcher-v$Version-Portable.exe"
Write-Host "Building self-extracting executable..." -ForegroundColor Yellow
# Read binary files and concatenate
$sfxBytes = [System.IO.File]::ReadAllBytes($SfxModule)
$configBytes = [System.IO.File]::ReadAllBytes($SfxConfigPath)
$archiveBytes = [System.IO.File]::ReadAllBytes($ArchivePath)
$outputStream = [System.IO.File]::Create($SfxExePath)
$outputStream.Write($sfxBytes, 0, $sfxBytes.Length)
$outputStream.Write($configBytes, 0, $configBytes.Length)
$outputStream.Write($archiveBytes, 0, $archiveBytes.Length)
$outputStream.Close()
# Cleanup temp files
Remove-Item $SfxConfigPath -Force
Remove-Item $ArchivePath -Force
$FileSize = [math]::Round((Get-Item $SfxExePath).Length / 1MB, 2)
Write-Host "`n✅ Self-extracting executable created!" -ForegroundColor Green
Write-Host " File: $SfxExePath" -ForegroundColor Cyan
Write-Host " Size: $FileSize MB" -ForegroundColor Cyan
Write-Host "`nThis single .exe can be distributed and run on any Windows PC." -ForegroundColor Yellow

View File

@@ -0,0 +1,69 @@
@echo off
REM rmtPocketWatcher Windows Build Script (Batch version)
REM Creates both standalone executable and MSIX installer
echo Building rmtPocketWatcher for Windows (Release mode)
echo =============================================
REM Clean previous builds
echo Cleaning previous builds...
flutter clean
if exist "build" rmdir /s /q "build"
REM Install dependencies
echo Installing dependencies...
flutter pub get
REM Build Flutter Windows app
echo Building Flutter Windows app...
flutter build windows --release
REM Check if build was successful
if not exist "build\windows\x64\runner\Release\rmtpocketwatcher.exe" (
echo ERROR: Build failed - executable not found
pause
exit /b 1
)
echo ✓ Flutter build completed successfully
REM Create standalone executable directory
echo Creating standalone executable package...
if exist "build\windows\standalone" rmdir /s /q "build\windows\standalone"
mkdir "build\windows\standalone"
REM Copy all necessary files for standalone distribution
xcopy "build\windows\x64\runner\Release\*" "build\windows\standalone\" /E /I /H /Y
REM Create version info
echo rmtPocketWatcher - Lambda Banking Conglomerate > "build\windows\standalone\README.txt"
echo Star Citizen AUEC Price Tracker >> "build\windows\standalone\README.txt"
echo. >> "build\windows\standalone\README.txt"
echo To run: Double-click rmtpocketwatcher.exe >> "build\windows\standalone\README.txt"
echo No installation required - all dependencies included. >> "build\windows\standalone\README.txt"
echo ✓ Standalone executable created at: build\windows\standalone
REM Create MSIX installer (optional)
echo Creating MSIX installer...
flutter pub run msix:create
if %errorlevel% neq 0 (
echo WARNING: MSIX installer creation failed, continuing with standalone only...
) else (
echo ✓ MSIX installer created
)
REM Create distribution archive
echo Creating distribution archive...
powershell -Command "Compress-Archive -Path 'build\windows\standalone\*' -DestinationPath 'build\rmtPocketWatcher-Windows-Standalone.zip' -CompressionLevel Optimal -Force"
echo.
echo 🎉 Build completed successfully!
echo =============================================
echo Standalone executable: build\windows\standalone\rmtpocketwatcher.exe
echo Distribution archive: build\rmtPocketWatcher-Windows-Standalone.zip
echo.
echo To test: cd build\windows\standalone ^&^& rmtpocketwatcher.exe
echo To distribute: Share the ZIP file
echo.
pause

View File

@@ -0,0 +1,177 @@
# rmtPocketWatcher Windows Build Script
# Creates both standalone executable and MSIX installer
param(
[switch]$Release = $false,
[switch]$Debug = $false
)
$ErrorActionPreference = "Stop"
# Determine build mode
$BuildMode = if ($Release) { "release" } elseif ($Debug) { "debug" } else { "release" }
$BuildModeCapital = (Get-Culture).TextInfo.ToTitleCase($BuildMode)
Write-Host "Building rmtPocketWatcher for Windows ($BuildModeCapital mode)" -ForegroundColor Green
Write-Host "=============================================" -ForegroundColor Green
# Clean previous builds
Write-Host "Cleaning previous builds..." -ForegroundColor Yellow
flutter clean
if (Test-Path "build") {
Remove-Item -Recurse -Force "build"
}
# Install dependencies
Write-Host "Installing dependencies..." -ForegroundColor Yellow
flutter pub get
# Build Flutter Windows app
Write-Host "Building Flutter Windows app..." -ForegroundColor Yellow
flutter build windows --$BuildMode
# Check if build was successful
$ExePath = "build\windows\x64\runner\$BuildModeCapital\rmtpocketwatcher.exe"
if (-not (Test-Path $ExePath)) {
Write-Error "Build failed - executable not found at $ExePath"
exit 1
}
Write-Host "✅ Flutter build completed successfully" -ForegroundColor Green
# Create standalone executable directory
$StandaloneDir = "build\windows\standalone"
Write-Host "Creating standalone executable package..." -ForegroundColor Yellow
if (Test-Path $StandaloneDir) {
Remove-Item -Recurse -Force $StandaloneDir
}
New-Item -ItemType Directory -Path $StandaloneDir -Force | Out-Null
# Copy all necessary files for standalone distribution
$SourceDir = "build\windows\x64\runner\$BuildModeCapital"
Copy-Item -Path "$SourceDir\*" -Destination $StandaloneDir -Recurse -Force
# Create version info
$Version = (Select-String -Path "pubspec.yaml" -Pattern "^version: (.+)$").Matches[0].Groups[1].Value -replace '\+.*', ''
$VersionInfo = @"
rmtPocketWatcher v$Version
Lambda Banking Conglomerate
Star Citizen AUEC Price Tracker
Built: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Mode: $BuildModeCapital
To run: Double-click rmtpocketwatcher.exe
No installation required - all dependencies included.
"@
$VersionInfo | Out-File -FilePath "$StandaloneDir\VERSION.txt" -Encoding UTF8
Write-Host "✅ Standalone executable created at: $StandaloneDir" -ForegroundColor Green
# Sign the standalone executable
$CertPath = "certificates\rmtPocketWatcher.pfx"
if (Test-Path $CertPath) {
Write-Host "Signing standalone executable..." -ForegroundColor Yellow
try {
.\sign_executable.ps1 -ExePath "$StandaloneDir\rmtpocketwatcher.exe" -Force
} catch {
Write-Warning "Failed to sign executable: $($_.Exception.Message)"
Write-Host "Continuing without signing..." -ForegroundColor Yellow
}
} else {
Write-Host "No certificate found - executable will be unsigned" -ForegroundColor Yellow
Write-Host "Run .\create_certificate.ps1 to create a self-signed certificate" -ForegroundColor Yellow
}
# Create MSIX installer
Write-Host "Creating MSIX installer..." -ForegroundColor Yellow
try {
# Check for certificate
$CertPath = "certificates\rmtPocketWatcher.pfx"
$CertPassword = if ($env:MSIX_CERTIFICATE_PASSWORD) { $env:MSIX_CERTIFICATE_PASSWORD } else { "rmtPocketWatcher2024!" }
if (Test-Path $CertPath) {
Write-Host "Using certificate for signing: $CertPath" -ForegroundColor Cyan
$env:MSIX_CERTIFICATE_PATH = $CertPath
$env:MSIX_CERTIFICATE_PASSWORD = $CertPassword
} else {
Write-Host "No certificate found - creating unsigned MSIX" -ForegroundColor Yellow
Write-Host "Run .\create_certificate.ps1 to create a self-signed certificate" -ForegroundColor Yellow
}
flutter pub run msix:create
$MsixPath = Get-ChildItem -Path "build\windows\x64\runner\$BuildModeCapital" -Filter "*.msix" | Select-Object -First 1
if ($MsixPath) {
Write-Host "✅ MSIX installer created: $($MsixPath.FullName)" -ForegroundColor Green
# Sign the MSIX if certificate exists
if (Test-Path $CertPath) {
Write-Host "Signing MSIX installer..." -ForegroundColor Yellow
try {
$signtool = "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe"
if (-not (Test-Path $signtool)) {
# Try to find signtool in common locations
$signtool = Get-ChildItem -Path "${env:ProgramFiles(x86)}\Windows Kits" -Recurse -Name "signtool.exe" | Select-Object -First 1
if ($signtool) {
$signtool = "${env:ProgramFiles(x86)}\Windows Kits\$signtool"
}
}
if (Test-Path $signtool) {
& $signtool sign /f $CertPath /p $CertPassword /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 $MsixPath.FullName
if ($LASTEXITCODE -eq 0) {
Write-Host "✅ MSIX installer signed successfully" -ForegroundColor Green
} else {
Write-Warning "Failed to sign MSIX installer"
}
} else {
Write-Warning "SignTool not found - install Windows SDK to enable signing"
}
} catch {
Write-Warning "Failed to sign MSIX: $($_.Exception.Message)"
}
}
} else {
Write-Warning "MSIX installer creation completed but file not found in expected location"
}
} catch {
Write-Warning "MSIX installer creation failed: $($_.Exception.Message)"
Write-Host "Continuing with standalone executable only..." -ForegroundColor Yellow
}
# Create distribution archive
Write-Host "Creating distribution archive..." -ForegroundColor Yellow
$ArchiveName = "rmtPocketWatcher-Windows-v$Version-$BuildMode.zip"
$ArchivePath = "build\$ArchiveName"
if (Test-Path $ArchivePath) {
Remove-Item $ArchivePath -Force
}
Compress-Archive -Path "$StandaloneDir\*" -DestinationPath $ArchivePath -CompressionLevel Optimal
Write-Host "✅ Distribution archive created: $ArchivePath" -ForegroundColor Green
# Summary
Write-Host "`n🎉 Build completed successfully!" -ForegroundColor Green
Write-Host "=============================================" -ForegroundColor Green
Write-Host "Standalone executable: $StandaloneDir\rmtpocketwatcher.exe" -ForegroundColor Cyan
Write-Host "Distribution archive: $ArchivePath" -ForegroundColor Cyan
if ($MsixPath) {
Write-Host "MSIX installer: $($MsixPath.FullName)" -ForegroundColor Cyan
}
Write-Host "`nTo test the standalone version:" -ForegroundColor Yellow
Write-Host " cd $StandaloneDir" -ForegroundColor White
Write-Host " .\rmtpocketwatcher.exe" -ForegroundColor White
Write-Host "`nTo distribute:" -ForegroundColor Yellow
Write-Host " - Share the ZIP file: $ArchiveName" -ForegroundColor White
Write-Host " - Users extract and run rmtpocketwatcher.exe" -ForegroundColor White
if ($MsixPath) {
Write-Host " - Or share the MSIX installer for Windows Store-style installation" -ForegroundColor White
}

View File

@@ -0,0 +1,31 @@
rmtPocketWatcher Code Signing Certificate
========================================
Certificate Details:
- Subject: CN=Lambda Banking Conglomerate, O=Lambda Banking Conglomerate, C=US
- Thumbprint: 4A4AFB542D1E34E3C96FD6EAAD3B88A6BA246093
- Valid From: 12/14/2025 23:26:52
- Valid Until: 12/14/2028 23:36:52
- Algorithm: sha1RSA
Files Created:
- certificates\rmtPocketWatcher.pfx (PFX with private key - keep secure!)
- certificates\rmtPocketWatcher.cer (Public certificate for distribution)
Password: rmtPocketWatcher2024!
Usage:
- Use the PFX file for signing applications
- Distribute the CER file to users who need to trust your apps
- Keep the PFX file secure and never share it publicly
Installation Instructions for Users:
1. Double-click certificates\rmtPocketWatcher.cer
2. Click "Install Certificate"
3. Choose "Local Machine" (requires admin) or "Current User"
4. Select "Place all certificates in the following store"
5. Browse and select "Trusted Root Certification Authorities"
6. Click "Next" and "Finish"
Note: This is a self-signed certificate. For production use,
consider purchasing a certificate from a trusted CA.

Binary file not shown.

View File

@@ -0,0 +1,162 @@
# Self-Signed Certificate Creation Script for rmtPocketWatcher
# Creates a code signing certificate for Windows applications
param(
[string]$CertName = "Lambda Banking Conglomerate",
[string]$AppName = "rmtPocketWatcher",
[int]$ValidYears = 3,
[switch]$Force = $false
)
$ErrorActionPreference = "Stop"
Write-Host "Creating Self-Signed Certificate for $AppName" -ForegroundColor Green
Write-Host "================================================" -ForegroundColor Green
# Certificate paths
$CertDir = "certificates"
$CertPath = "$CertDir\$AppName.pfx"
$CerPath = "$CertDir\$AppName.cer"
$Password = "rmtPocketWatcher2024!"
# Create certificates directory
if (-not (Test-Path $CertDir)) {
New-Item -ItemType Directory -Path $CertDir -Force | Out-Null
Write-Host "Created certificates directory: $CertDir" -ForegroundColor Yellow
}
# Check if certificate already exists
if ((Test-Path $CertPath) -and -not $Force) {
Write-Host "Certificate already exists at: $CertPath" -ForegroundColor Yellow
Write-Host "Use -Force to recreate the certificate" -ForegroundColor Yellow
# Check if certificate is still valid
try {
$cert = Get-PfxCertificate -FilePath $CertPath
$daysUntilExpiry = ($cert.NotAfter - (Get-Date)).Days
if ($daysUntilExpiry -gt 30) {
Write-Host "Current certificate is valid for $daysUntilExpiry more days" -ForegroundColor Green
Write-Host "Certificate Subject: $($cert.Subject)" -ForegroundColor Cyan
Write-Host "Certificate Thumbprint: $($cert.Thumbprint)" -ForegroundColor Cyan
return
} else {
Write-Host "Certificate expires in $daysUntilExpiry days, recreating..." -ForegroundColor Yellow
$Force = $true
}
} catch {
Write-Host "Existing certificate is invalid, recreating..." -ForegroundColor Yellow
$Force = $true
}
}
# Remove existing certificate if forcing recreation
if ($Force -and (Test-Path $CertPath)) {
Remove-Item $CertPath -Force
Write-Host "Removed existing certificate" -ForegroundColor Yellow
}
Write-Host "Creating new self-signed certificate..." -ForegroundColor Yellow
# Create the certificate
$notAfter = (Get-Date).AddYears($ValidYears)
$cert = New-SelfSignedCertificate `
-Type CodeSigningCert `
-Subject "CN=$CertName, O=$CertName, C=US" `
-KeyAlgorithm RSA `
-KeyLength 2048 `
-Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" `
-KeyExportPolicy Exportable `
-KeyUsage DigitalSignature `
-NotAfter $notAfter `
-CertStoreLocation "Cert:\CurrentUser\My"
Write-Host "✅ Certificate created successfully" -ForegroundColor Green
Write-Host "Certificate Thumbprint: $($cert.Thumbprint)" -ForegroundColor Cyan
Write-Host "Valid Until: $($cert.NotAfter)" -ForegroundColor Cyan
# Export certificate to PFX (with private key)
$securePassword = ConvertTo-SecureString -String $Password -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath $CertPath -Password $securePassword | Out-Null
Write-Host "✅ Exported PFX certificate to: $CertPath" -ForegroundColor Green
# Export certificate to CER (public key only, for distribution)
Export-Certificate -Cert $cert -FilePath $CerPath | Out-Null
Write-Host "✅ Exported CER certificate to: $CerPath" -ForegroundColor Green
# Install certificate to Trusted Root (requires admin)
Write-Host "Installing certificate to Trusted Root Certification Authorities..." -ForegroundColor Yellow
try {
# Check if running as administrator
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
if ($isAdmin) {
Import-Certificate -FilePath $CerPath -CertStoreLocation "Cert:\LocalMachine\Root" | Out-Null
Write-Host "✅ Certificate installed to Trusted Root (system-wide)" -ForegroundColor Green
} else {
Import-Certificate -FilePath $CerPath -CertStoreLocation "Cert:\CurrentUser\Root" | Out-Null
Write-Host "✅ Certificate installed to Trusted Root (current user)" -ForegroundColor Green
Write-Host "⚠️ Run as Administrator to install system-wide" -ForegroundColor Yellow
}
} catch {
Write-Host "❌ Failed to install certificate to Trusted Root: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "You may need to install it manually" -ForegroundColor Yellow
}
# Create certificate info file
$certInfo = @"
rmtPocketWatcher Code Signing Certificate
========================================
Certificate Details:
- Subject: $($cert.Subject)
- Thumbprint: $($cert.Thumbprint)
- Valid From: $($cert.NotBefore)
- Valid Until: $($cert.NotAfter)
- Algorithm: $($cert.SignatureAlgorithm.FriendlyName)
Files Created:
- $CertPath (PFX with private key - keep secure!)
- $CerPath (Public certificate for distribution)
Password: $Password
Usage:
- Use the PFX file for signing applications
- Distribute the CER file to users who need to trust your apps
- Keep the PFX file secure and never share it publicly
Installation Instructions for Users:
1. Double-click $CerPath
2. Click "Install Certificate"
3. Choose "Local Machine" (requires admin) or "Current User"
4. Select "Place all certificates in the following store"
5. Browse and select "Trusted Root Certification Authorities"
6. Click "Next" and "Finish"
Note: This is a self-signed certificate. For production use,
consider purchasing a certificate from a trusted CA.
"@
$certInfo | Out-File -FilePath "$CertDir\CERTIFICATE_INFO.txt" -Encoding UTF8
Write-Host "✅ Certificate information saved to: $CertDir\CERTIFICATE_INFO.txt" -ForegroundColor Green
Write-Host "`n🎉 Certificate setup completed!" -ForegroundColor Green
Write-Host "================================================" -ForegroundColor Green
Write-Host "PFX Certificate: $CertPath" -ForegroundColor Cyan
Write-Host "Public Certificate: $CerPath" -ForegroundColor Cyan
Write-Host "Password: $Password" -ForegroundColor Cyan
Write-Host "`nNext steps:" -ForegroundColor Yellow
Write-Host "1. Update your build scripts to use this certificate" -ForegroundColor White
Write-Host "2. Test signing your application" -ForegroundColor White
Write-Host "3. Distribute the .cer file to users if needed" -ForegroundColor White
# Add to .gitignore if not already there
$gitignorePath = ".gitignore"
if (Test-Path $gitignorePath) {
$gitignoreContent = Get-Content $gitignorePath -Raw
if ($gitignoreContent -notmatch "certificates/") {
Add-Content $gitignorePath "`n# Code signing certificates`ncertificates/*.pfx`ncertificates/*.p12"
Write-Host "✅ Added certificate files to .gitignore" -ForegroundColor Green
}
}

View File

@@ -0,0 +1,37 @@
# Encode Certificate for CI/CD
# Converts the PFX certificate to base64 for use as GitHub/Gitea action secret
param(
[string]$CertPath = "certificates\rmtPocketWatcher.pfx",
[string]$OutputFile = "certificate_base64.txt"
)
$ErrorActionPreference = "Stop"
Write-Host "Encoding Certificate for CI/CD" -ForegroundColor Green
Write-Host "==============================" -ForegroundColor Green
# Check if certificate exists
if (-not (Test-Path $CertPath)) {
Write-Error "Certificate not found at: $CertPath"
Write-Host "Create a certificate first using .\create_certificate.ps1" -ForegroundColor Yellow
exit 1
}
# Read certificate and encode as base64
Write-Host "Reading certificate: $CertPath" -ForegroundColor Yellow
$certBytes = [System.IO.File]::ReadAllBytes((Resolve-Path $CertPath))
$certBase64 = [System.Convert]::ToBase64String($certBytes)
# Save to file
$certBase64 | Out-File -FilePath $OutputFile -Encoding ASCII -NoNewline
Write-Host "✅ Certificate encoded successfully!" -ForegroundColor Green
Write-Host "Base64 encoded certificate saved to: $OutputFile" -ForegroundColor Cyan
Write-Host ""
Write-Host "Next steps:" -ForegroundColor Yellow
Write-Host "1. Copy the contents of $OutputFile" -ForegroundColor White
Write-Host "2. Add as action secret named 'CERT_BASE64'" -ForegroundColor White
Write-Host "3. Add certificate password as secret named 'CERT_PASSWORD'" -ForegroundColor White
Write-Host ""
Write-Host "⚠️ IMPORTANT: Delete $OutputFile after copying to keep certificate secure!" -ForegroundColor Red

BIN
flutter_app/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

34
flutter_app/ios/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -0,0 +1,26 @@
<?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>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1,616 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
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_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
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_IMPLICIT_RETAIN_SELF = 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_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
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 = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.lambdabanking.rmtpocketwatcher;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.lambdabanking.rmtpocketwatcher.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.lambdabanking.rmtpocketwatcher.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.lambdabanking.rmtpocketwatcher.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
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_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
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_IMPLICIT_RETAIN_SELF = 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_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
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 = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
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_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
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_IMPLICIT_RETAIN_SELF = 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_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
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 = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.lambdabanking.rmtpocketwatcher;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.lambdabanking.rmtpocketwatcher;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

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,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>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,13 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1 @@
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</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="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,55 @@
<?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>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Rmtpocketwatcher</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>rmtpocketwatcher</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<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>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>NSUserNotificationAlertStyle</key>
<string>alert</string>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

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