Compare commits

..

No commits in common. "d34cccc253549ffcc48278de52f0b87d3f0b3669" and "8ea06b12f7f5fbb0aebd2cb61954496fc243ea85" have entirely different histories.

248 changed files with 1625 additions and 176060 deletions

View File

@ -1,138 +0,0 @@
# Dashboard Build System
This document explains how to keep the Flutter web dashboard in sync with the server's static files.
## Overview
The XP Nix server serves static files from `xp_server/lib/src/web/static/` directory. The Flutter dashboard is a separate project in `xp_dashboard/` that needs to be built for web and copied to the static directory.
## Build Methods
### Method 1: Using the Server Command (Recommended)
When the server is running, you can use the interactive `build` command:
1. Start the server:
```bash
cd xp_server
dart run bin/xp_nix.dart
```
2. Type `build` in the server console and press Enter:
```
build
```
The server will:
- Check if Flutter is available
- Build the Flutter dashboard with `flutter build web --release`
- Copy all files from `xp_dashboard/build/web/` to `xp_server/lib/src/web/static/`
- Provide feedback on the build status
### Method 2: Using the Shell Script
For building outside of the running server, use the provided shell script:
```bash
./build_dashboard.sh
```
This script:
- Checks for Flutter availability
- Builds the Flutter dashboard
- Clears old static files
- Copies new files to the static directory
## Build Process Details
### What Gets Built
The Flutter build process creates:
- `index.html` - Main HTML file
- `main.dart.js` - Compiled Dart code
- `flutter.js`, `flutter_bootstrap.js` - Flutter runtime
- `flutter_service_worker.js` - Service worker for PWA features
- `assets/` - Flutter assets and fonts
- `canvaskit/` - CanvasKit rendering engine
- `icons/` - App icons
- `manifest.json` - Web app manifest
- Other supporting files
### File Synchronization
The build process:
1. **Clears** the existing `xp_server/lib/src/web/static/` directory
2. **Copies** all files from `xp_dashboard/build/web/` to the static directory
3. **Preserves** the directory structure including subdirectories
## Development Workflow
### When to Rebuild
Rebuild the dashboard when you:
- Make changes to Flutter widgets in `xp_dashboard/lib/`
- Update dependencies in `xp_dashboard/pubspec.yaml`
- Modify theme or styling
- Add new features to the dashboard
### Typical Workflow
1. Make changes to the Flutter dashboard code
2. Test locally if needed: `cd xp_dashboard && flutter run -d web-server`
3. Build and deploy: Use either the server `build` command or `./build_dashboard.sh`
4. Refresh your browser to see the changes
## Troubleshooting
### Flutter Not Found
```
❌ Flutter is not available in the system PATH
```
**Solution**: Ensure Flutter is installed and added to your PATH.
### Build Failed
```
❌ Flutter build failed
```
**Solution**: Check the error output, usually related to:
- Dart compilation errors
- Missing dependencies (`flutter pub get`)
- Invalid Flutter code
### Files Not Copied
```
❌ Build output directory not found
```
**Solution**: The Flutter build didn't complete successfully. Check the build output for errors.
## File Structure
```
xp_nix/
├── xp_dashboard/ # Flutter dashboard source
│ ├── lib/ # Flutter source code
│ ├── web/ # Web-specific files
│ └── build/web/ # Build output (generated)
├── xp_server/
│ ├── lib/src/web/static/ # Static files served by server
│ └── lib/src/build/ # Build system code
├── build_dashboard.sh # Standalone build script
└── BUILD_DASHBOARD.md # This documentation
```
## Advanced Usage
### Custom Build Options
To modify build options, edit the Flutter build command in:
- `xp_server/lib/src/build/dashboard_builder.dart` (for server command)
- `build_dashboard.sh` (for shell script)
Current build command: `flutter build web --release`
### Development vs Production
- **Development**: Use `flutter build web` (debug mode, faster builds)
- **Production**: Use `flutter build web --release` (optimized, smaller files)
The current setup uses `--release` for optimal performance.

108
bin/xp_nix.dart Normal file
View File

@ -0,0 +1,108 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:sqlite3/sqlite3.dart';
import 'package:xp_nix/src/monitors/productivity_monitor.dart';
import 'package:xp_nix/src/config/config_manager.dart';
import 'package:xp_nix/src/logging/logger.dart';
import 'package:xp_nix/src/web/dashboard_server.dart';
import 'package:xp_nix/src/database/database_manager.dart';
import 'package:xp_nix/src/detectors/idle_monitor.dart';
import 'package:xp_nix/src/providers/system_time_provider.dart';
import 'package:xp_nix/src/enhancers/hyprland_enhancer.dart';
// Enhanced main function with interactive commands and one-shot mode
void main(List<String> args) async {
// Initialize logging system
await Logger.instance.initialize(level: LogLevel.info, logDirectory: 'logs', maxFileSizeMB: 10, maxFiles: 5);
// Initialize configuration manager
await ConfigManager.instance.initialize();
final db = sqlite3.open('productivity_tracker.db');
// Create production dependencies
final idleMonitor = IdleMonitor();
final timeProvider = SystemTimeProvider();
final desktopEnhancer = HyprlandEnhancer();
// Create monitor with dependency injection
final monitor = ProductivityMonitor(
db: db,
idleMonitor: idleMonitor,
timeProvider: timeProvider,
desktopEnhancer: desktopEnhancer,
// No activity detector provided - will use legacy polling mode
);
final dashboardServer = DashboardServer.withDatabase(DatabaseManager(db));
ProcessSignal.sigint.watch().listen((_) async {
Logger.info('Shutting down XP Nix...');
print('\nShutting down...');
monitor.stop();
await dashboardServer.stop();
await Logger.instance.dispose();
db.dispose();
exit(0);
});
// Start the dashboard server
try {
await dashboardServer.start(8080);
Logger.info('Dashboard available at: ${dashboardServer.dashboardUrl}');
print('🌐 Dashboard available at: ${dashboardServer.dashboardUrl}');
} catch (e) {
Logger.error('Failed to start dashboard server: $e');
print('⚠️ Dashboard server failed to start: $e');
}
monitor.start();
monitor.printDetailedStats();
// Add command listener for manual controls
stdin.transform(utf8.decoder).transform(LineSplitter()).listen((line) {
final parts = line.trim().split(' ');
final command = parts[0].toLowerCase();
switch (command) {
case 'stats':
monitor.printDetailedStats();
break;
case 'test':
if (parts.length > 1) {
final level = int.tryParse(parts[1]) ?? 1;
monitor.testTheme(level);
}
break;
case 'restore':
monitor.restoreDesktop();
break;
case 'refresh':
monitor.refreshConfig();
break;
case 'help':
print('''
Available commands:
- stats: Show current productivity stats
- test [level]: Test theme for specific level
- restore: Restore desktop backup
- refresh: Refresh base config from current system config
- help: Show this help
''');
break;
}
});
print('💡 Type "help" for available commands');
// Keep running and show stats periodically
while (true) {
await Future.delayed(Duration(seconds: 1));
if (DateTime.now().second == 0 && DateTime.now().minute % 10 == 0) {
monitor.printDetailedStats();
}
}
}

View File

@ -1,50 +0,0 @@
#!/bin/bash
# Build Dashboard Script
# This script builds the Flutter dashboard and copies the files to the server's static directory
set -e # Exit on any error
echo "🔨 Building Flutter Dashboard..."
echo "================================"
# Check if Flutter is available
if ! command -v flutter &> /dev/null; then
echo "❌ Flutter is not available in the system PATH"
echo " Please ensure Flutter is installed and available in your PATH"
exit 1
fi
# Check if dashboard directory exists
if [ ! -d "xp_dashboard" ]; then
echo "❌ Dashboard directory 'xp_dashboard' not found"
echo " Please run this script from the project root directory"
exit 1
fi
# Build Flutter web app
echo "🚀 Running flutter build web..."
cd xp_dashboard
flutter build web --release
cd ..
# Check if build was successful
if [ ! -d "xp_dashboard/build/web" ]; then
echo "❌ Build failed - output directory not found"
exit 1
fi
# Create static directory if it doesn't exist
mkdir -p xp_server/lib/src/web/static
# Clear existing static files
echo "🧹 Clearing old static files..."
rm -rf xp_server/lib/src/web/static/*
# Copy new files
echo "📁 Copying new files to static directory..."
cp -r xp_dashboard/build/web/* xp_server/lib/src/web/static/
echo "✅ Dashboard build completed successfully!"
echo " Files copied to: xp_server/lib/src/web/static/"
echo " The server will now serve the updated Flutter dashboard"

View File

26
flake.nix Normal file
View File

@ -0,0 +1,26 @@
{
description = "Simple dart flake";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = {
flake-utils,
nixpkgs,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
};
in {
devShell = pkgs.mkShell {
buildInputs = with pkgs; [
dart
sqlite
];
shellHook = ''
export LD_LIBRARY_PATH="${pkgs.sqlite.out}/lib:$LD_LIBRARY_PATH"
'';
};
});
}

View File

@ -40,9 +40,6 @@ class Logger {
_maxFileSizeMB = maxFileSizeMB;
_maxFiles = maxFiles;
// Clean up old log files first
await _cleanupOldLogs();
await _setupLogFile();
_info('Logger initialized with level: ${level.name}');
}
@ -53,7 +50,6 @@ class Logger {
await logDir.create(recursive: true);
}
// Always create a new log file with current timestamp
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-').split('.')[0];
_currentLogFile = File('$_logDirectory/xp_nix_$timestamp.log');
@ -93,7 +89,7 @@ class Logger {
if (logFiles.length >= _maxFiles) {
for (int i = _maxFiles - 1; i < logFiles.length; i++) {
print('Deleting old log file: ${logFiles[i].path}');
_info('Deleting old log file ${logFiles[i].toString()}');
await logFiles[i].delete();
}
}

View File

@ -13,24 +13,23 @@ import '../database/database_manager.dart';
import '../config/config_manager.dart';
import '../logging/logger.dart';
import '../notifications/xp_notification_manager.dart';
import '../web/websocket_manager.dart';
/// Unified ProductivityMonitor with dependency injection for both production and testing
class ProductivityMonitor {
late final DatabaseManager _dbManager;
final DatabaseManager _dbManager;
ConfigManager get _configManager => ConfigManager.instance;
late final IIdleMonitor _idleMonitor;
late final IActivityDetector? _activityDetector;
late final ITimeProvider _timeProvider;
late final IDesktopEnhancer _desktopEnhancer;
late final XPNotificationManager _xpNotificationManager;
late final ZoomDetector? _zoomDetector;
final IIdleMonitor _idleMonitor;
final IActivityDetector? _activityDetector;
final ITimeProvider _timeProvider;
final IDesktopEnhancer _desktopEnhancer;
late XPNotificationManager _xpNotificationManager;
Timer? _pollTimer;
String? _lastActiveWindow;
String? _lastActiveWindowTitle;
DateTime? _lastActivityTime;
DateTime? _lastActiveTime;
late ZoomDetector _zoomDetector;
ZoomStatus _lastZoomStatus = ZoomStatus.none;
DateTime? _lastZoomStatusTime;
int _lastKnownLevel = 1;
@ -47,31 +46,27 @@ class ProductivityMonitor {
required ITimeProvider timeProvider,
required IDesktopEnhancer desktopEnhancer,
IActivityDetector? activityDetector,
}) {
_dbManager = DatabaseManager(db);
_idleMonitor = idleMonitor;
_activityDetector = activityDetector;
_timeProvider = timeProvider;
_desktopEnhancer = desktopEnhancer;
_xpNotificationManager = XPNotificationManager(_dbManager);
// Initialize zoom detector (only if not in test mode)
if (_activityDetector == null) {
_zoomDetector = ZoomDetector();
} else {
_zoomDetector = null;
}
}
}) : _dbManager = DatabaseManager(db),
_idleMonitor = idleMonitor,
_activityDetector = activityDetector,
_timeProvider = timeProvider,
_desktopEnhancer = desktopEnhancer;
void start() {
_dbManager.initDatabase();
// Initialize XP notification manager
_xpNotificationManager = XPNotificationManager(_dbManager);
_xpNotificationManager.start();
// Start idle monitor
_idleMonitor.start();
// Initialize zoom detector (only if not in test mode)
if (_activityDetector == null) {
_zoomDetector = ZoomDetector();
}
// Listen to idle state changes
_idleSubscription = _idleMonitor.idleStateStream.listen((idleStatus) {
_handleIdleStateChange(idleStatus);
@ -112,7 +107,7 @@ class ProductivityMonitor {
/// Handle idle state changes from the idle monitor
void _handleIdleStateChange(IdleStatus idleStatus) {
print('DEBUG: Idle state changed to: $idleStatus');
// When user goes deep idle, end the current activity and award XP
if (idleStatus == IdleStatus.deepIdle) {
print('😴 User went deep idle - ending current activity');
@ -127,7 +122,7 @@ class ProductivityMonitor {
if (_lastActiveWindow != null && _lastActivityTime != null) {
final duration = _timeProvider.now().difference(_lastActivityTime!).inSeconds;
print('DEBUG: Ending activity $_lastActiveWindow due to deep idle with duration ${duration}s');
// Save the activity if it meets the minimum duration requirement
if (duration >= _activityDurationCutoffSeconds) {
_saveActivityEvent(_lastActiveWindow!, duration, _lastActiveWindowTitle ?? '');
@ -135,7 +130,7 @@ class ProductivityMonitor {
} else {
print('DEBUG: Activity duration too short ($duration < $_activityDurationCutoffSeconds), not saving');
}
// Clear the current activity state
_lastActiveWindow = null;
_lastActiveWindowTitle = null;
@ -229,9 +224,6 @@ class ProductivityMonitor {
// Send level up notification
await _xpNotificationManager.showLevelUp(newLevel: currentLevel, totalXP: stats['xp'] as int, stats: stats);
// Broadcast level up via WebSocket
_broadcastLevelUp(currentLevel);
}
}
@ -337,9 +329,6 @@ class ProductivityMonitor {
xpReward: achievement['xp_reward'],
currentLevel: level,
);
// Broadcast achievement via WebSocket
_broadcastAchievementUnlocked(achievement);
}
void _showLevelUpMessage(int level, Map<String, dynamic> stats) {
@ -419,13 +408,6 @@ class ProductivityMonitor {
// Send focus session notification
_xpNotificationManager.showFocusSession(durationMinutes: focusMinutes, bonusXP: bonusXP, sessionType: 'focus');
// Broadcast focus session completion via WebSocket
_broadcastFocusSessionComplete(focusMinutes, bonusXP);
// Broadcast updated stats and XP breakdown
_broadcastStatsUpdate();
_broadcastXPBreakdownUpdate();
}
// Enhanced stats display
@ -529,7 +511,7 @@ class ProductivityMonitor {
}
Future<void> _checkZoomActivity(DateTime now) async {
final currentZoomStatus = await _zoomDetector!.getZoomStatus();
final currentZoomStatus = await _zoomDetector.getZoomStatus();
if (_lastZoomStatus != currentZoomStatus) {
if (_lastZoomStatus != ZoomStatus.none && _lastZoomStatusTime != null) {
@ -646,10 +628,6 @@ class ProductivityMonitor {
activity: event,
durationMinutes: (durationSeconds / 60).round(),
);
// Broadcast updated stats and XP breakdown via WebSocket
_broadcastStatsUpdate();
_broadcastXPBreakdownUpdate();
}
bool _isFocusActivity(ActivityEventType activityType) {
@ -667,88 +645,4 @@ class ProductivityMonitor {
Future<void> checkForLevelUpNow() async {
await _checkForLevelUp();
}
// WebSocket broadcasting methods
void _broadcastLevelUp(int newLevel) {
if (WebSocketManager.instance.hasConnections) {
final message = {
'type': 'level_up',
'data': {'level': newLevel},
'timestamp': DateTime.now().millisecondsSinceEpoch,
};
WebSocketManager.instance.broadcast(message);
Logger.info('Broadcasted level up to WebSocket clients: Level $newLevel');
}
}
void _broadcastAchievementUnlocked(Map<String, dynamic> achievement) {
if (WebSocketManager.instance.hasConnections) {
final message = {
'type': 'achievement_unlocked',
'data': achievement,
'timestamp': DateTime.now().millisecondsSinceEpoch,
};
WebSocketManager.instance.broadcast(message);
Logger.info('Broadcasted achievement to WebSocket clients: ${achievement['name']}');
}
}
void _broadcastStatsUpdate() {
if (WebSocketManager.instance.hasConnections) {
final stats = _dbManager.getTodayStats();
final streaks = _dbManager.getStreakStats();
final recentActivity = _dbManager.getRecentActivity(5);
final statsData = {
'today': stats,
'streaks': streaks,
'recent_activity': recentActivity
.map(
(row) => {
'type': row['type'],
'application': row['application'],
'timestamp': row['timestamp'],
'duration_seconds': row['duration_seconds'],
},
)
.toList(),
'timestamp': DateTime.now().millisecondsSinceEpoch,
};
final message = {'type': 'stats_update', 'data': statsData, 'timestamp': DateTime.now().millisecondsSinceEpoch};
WebSocketManager.instance.broadcast(message);
Logger.info('Broadcasted stats update to WebSocket clients');
}
}
void _broadcastXPBreakdownUpdate() {
if (WebSocketManager.instance.hasConnections) {
final breakdown = _dbManager.getTodayXPBreakdown();
final message = {
'type': 'xp_breakdown_update',
'data': breakdown,
'timestamp': DateTime.now().millisecondsSinceEpoch,
};
WebSocketManager.instance.broadcast(message);
Logger.info('Broadcasted XP breakdown update to WebSocket clients');
}
}
void _broadcastFocusSessionComplete(int durationMinutes, int bonusXP) {
if (WebSocketManager.instance.hasConnections) {
final sessionData = {
'duration_minutes': durationMinutes,
'bonus_xp': bonusXP,
'timestamp': DateTime.now().millisecondsSinceEpoch,
};
final message = {
'type': 'focus_session_complete',
'data': sessionData,
'timestamp': DateTime.now().millisecondsSinceEpoch,
};
WebSocketManager.instance.broadcast(message);
Logger.info('Broadcasted focus session completion to WebSocket clients: ${durationMinutes}min, +${bonusXP}XP');
}
}
}

View File

@ -5,12 +5,10 @@ import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf_static/shelf_static.dart';
import 'package:shelf_web_socket/shelf_web_socket.dart';
import 'package:sqlite3/sqlite3.dart';
import '../database/database_manager.dart';
import '../config/config_manager.dart';
import '../logging/logger.dart';
import 'websocket_manager.dart';
class DashboardServer {
static DashboardServer? _instance;
@ -46,7 +44,7 @@ class DashboardServer {
router.get('/api/unclassified', _handleGetUnclassified);
// WebSocket for real-time updates
router.get('/ws', _handleWebSocket());
router.get('/ws', _handleWebSocket);
// Static file handler for the web UI
final staticHandler = createStaticHandler('lib/src/web/static', defaultDocument: 'index.html');
@ -59,8 +57,8 @@ class DashboardServer {
.addHandler(cascade.handler);
try {
_server = await shelf_io.serve(handler, InternetAddress.anyIPv6, _port);
Logger.info('Dashboard server started on http://[::]:$_port (IPv4 and IPv6)');
_server = await shelf_io.serve(handler, 'localhost', _port);
Logger.info('Dashboard server started on http://localhost:$_port');
} catch (e) {
Logger.error('Failed to start dashboard server: $e');
rethrow;
@ -101,16 +99,17 @@ class DashboardServer {
final response = {
'today': stats,
'streaks': streaks,
'recent_activity': recentActivity
.map(
(row) => {
'type': row['type'],
'application': row['application'],
'timestamp': _convertTimestamp(row['timestamp']),
'duration_seconds': row['duration_seconds'],
},
)
.toList(),
'recent_activity':
recentActivity
.map(
(row) => {
'type': row['type'],
'application': row['application'],
'timestamp': row['timestamp'],
'duration_seconds': row['duration_seconds'],
},
)
.toList(),
'timestamp': DateTime.now().millisecondsSinceEpoch,
};
@ -164,18 +163,19 @@ class DashboardServer {
try {
final achievements = _dbManager.getAllAchievements();
final achievementList = achievements
.map(
(row) => {
'id': row['id'],
'name': row['name'],
'description': row['description'],
'xp_reward': row['xp_reward'],
'achieved_at': row['achieved_at'] != null ? _convertTimestamp(row['achieved_at']) : null,
'level_at_achievement': row['level_at_achievement'],
},
)
.toList();
final achievementList =
achievements
.map(
(row) => {
'id': row['id'],
'name': row['name'],
'description': row['description'],
'xp_reward': row['xp_reward'],
'achieved_at': row['achieved_at'],
'level_at_achievement': row['level_at_achievement'],
},
)
.toList();
return Response.ok(jsonEncode(achievementList), headers: {'Content-Type': 'application/json'});
} catch (e) {
@ -189,18 +189,19 @@ class DashboardServer {
final limit = int.tryParse(request.url.queryParameters['limit'] ?? '100') ?? 100;
final activities = _dbManager.getRecentActivities(limit);
final activityList = activities
.map(
(row) => {
'id': row['id'],
'type': row['type'],
'application': row['application'],
'metadata': row['metadata'] != null ? jsonDecode(row['metadata']) : null,
'timestamp': _convertTimestamp(row['timestamp']),
'duration_seconds': row['duration_seconds'],
},
)
.toList();
final activityList =
activities
.map(
(row) => {
'id': row['id'],
'type': row['type'],
'application': row['application'],
'metadata': row['metadata'] != null ? jsonDecode(row['metadata']) : null,
'timestamp': row['timestamp'],
'duration_seconds': row['duration_seconds'],
},
)
.toList();
return Response.ok(jsonEncode(activityList), headers: {'Content-Type': 'application/json'});
} catch (e) {
@ -214,17 +215,18 @@ class DashboardServer {
final limit = int.tryParse(request.url.queryParameters['limit'] ?? '50') ?? 50;
final sessions = _dbManager.getRecentFocusSessions(limit);
final sessionList = sessions
.map(
(row) => {
'id': row['id'],
'date': row['date'],
'duration_minutes': row['duration_minutes'],
'bonus_xp': row['bonus_xp'],
'timestamp': row['timestamp'],
},
)
.toList();
final sessionList =
sessions
.map(
(row) => {
'id': row['id'],
'date': row['date'],
'duration_minutes': row['duration_minutes'],
'bonus_xp': row['bonus_xp'],
'timestamp': row['timestamp'],
},
)
.toList();
return Response.ok(jsonEncode(sessionList), headers: {'Content-Type': 'application/json'});
} catch (e) {
@ -237,7 +239,7 @@ class DashboardServer {
try {
final date = request.url.queryParameters['date'];
final Map<String, int> breakdown;
if (date != null) {
breakdown = _dbManager.getXPBreakdownForDate(date);
} else {
@ -313,8 +315,8 @@ class DashboardServer {
'id': row['id'],
'application_name': row['application_name'],
'category_id': row['category_id'],
'created_at': _convertTimestamp(row['created_at']),
'updated_at': _convertTimestamp(row['updated_at']),
'created_at': row['created_at'],
'updated_at': row['updated_at'],
},
)
.toList();
@ -380,8 +382,8 @@ class DashboardServer {
(row) => {
'id': row['id'],
'application_name': row['application_name'],
'first_seen': _convertTimestamp(row['first_seen']),
'last_seen': _convertTimestamp(row['last_seen']),
'first_seen': row['first_seen'],
'last_seen': row['last_seen'],
'occurrence_count': row['occurrence_count'],
},
)
@ -394,21 +396,11 @@ class DashboardServer {
}
}
Handler _handleWebSocket() {
return webSocketHandler((webSocket) {
Logger.info('New WebSocket connection established');
WebSocketManager.instance.addConnection(webSocket);
});
Future<Response> _handleWebSocket(Request request) async {
// Basic WebSocket upgrade (simplified)
// In a real implementation, you'd use a proper WebSocket library
return Response.notFound('WebSocket not implemented yet');
}
String get dashboardUrl => 'http://localhost:$_port';
/// Converts a database timestamp (int) to milliseconds since epoch for JSON serialization
int _convertTimestamp(dynamic timestamp) {
if (timestamp is int) {
// Assume database stores seconds, convert to milliseconds
return timestamp * 1000;
}
return timestamp as int;
}
}

View File

@ -0,0 +1,653 @@
class ProductivityDashboard {
constructor() {
this.chart = null;
this.refreshInterval = null;
this.init();
}
async init() {
await this.loadInitialData();
this.setupEventListeners();
this.startAutoRefresh();
this.setupChart();
}
async loadInitialData() {
try {
await Promise.all([
this.updateStats(),
this.updateActivity(),
this.updateAchievements(),
this.updateXPBreakdown(),
this.updateLogs(),
this.loadConfig(),
this.updateClassifications(),
this.updateUnclassified()
]);
} catch (error) {
console.error('Failed to load initial data:', error);
this.showMessage('Failed to load dashboard data', 'error');
}
}
async updateStatsAndActivity() {
try {
const response = await fetch('/api/stats');
const data = await response.json();
// Update header stats
document.getElementById('current-level').textContent = data.today.level;
document.getElementById('current-xp').textContent = data.today.xp;
document.getElementById('current-streak').textContent = data.streaks.current_streak;
// Update progress bars
const focusHours = Math.floor(data.today.focus_time / 3600);
const focusMinutes = Math.floor((data.today.focus_time % 3600) / 60);
const meetingHours = Math.floor(data.today.meeting_time / 3600);
const meetingMinutes = Math.floor((data.today.meeting_time % 3600) / 60);
document.getElementById('focus-time').textContent = `${focusHours}h ${focusMinutes}m`;
document.getElementById('meeting-time').textContent = `${meetingHours}h ${meetingMinutes}m`;
document.getElementById('focus-sessions').textContent = data.today.focus_sessions;
// Update progress bars (assuming 8 hours = 100%)
const focusPercent = Math.min((data.today.focus_time / (8 * 3600)) * 100, 100);
const meetingPercent = Math.min((data.today.meeting_time / (4 * 3600)) * 100, 100);
document.getElementById('focus-progress').style.width = `${focusPercent}%`;
document.getElementById('meeting-progress').style.width = `${meetingPercent}%`;
// Update recent activity
const activityContainer = document.getElementById('recent-activity');
if (data.recent_activity && data.recent_activity.length > 0) {
activityContainer.innerHTML = data.recent_activity.map(activity => {
const date = new Date(activity.timestamp);
const timeStr = date.toLocaleTimeString();
const durationMin = Math.floor(activity.duration_seconds / 60);
return `
<div class="activity-item">
<span class="activity-type">${this.capitalizeFirst(activity.type)}</span>
<div class="activity-details">
${activity.application} ${durationMin}m ${timeStr}
</div>
</div>
`;
}).join('');
} else {
activityContainer.innerHTML = '<div class="activity-item"><span class="activity-type">No recent activity</span></div>';
}
} catch (error) {
console.error('Failed to update stats and activity:', error);
}
}
// Backward compatibility methods
async updateStats() {
return this.updateStatsAndActivity();
}
async updateActivity() {
return this.updateStatsAndActivity();
}
async updateAchievements() {
try {
const response = await fetch('/api/achievements?limit=5');
const achievements = await response.json();
const achievementsContainer = document.getElementById('achievements-list');
if (achievements && achievements.length > 0) {
achievementsContainer.innerHTML = achievements.map(achievement => {
const date = new Date(achievement.achieved_at);
const dateStr = date.toLocaleDateString();
return `
<div class="achievement-item">
<span class="achievement-name">${achievement.name}</span>
<div class="achievement-description">
${achievement.description} +${achievement.xp_reward} XP ${dateStr}
</div>
</div>
`;
}).join('');
} else {
achievementsContainer.innerHTML = '<div class="achievement-item"><span class="achievement-name">No achievements yet</span></div>';
}
} catch (error) {
console.error('Failed to update achievements:', error);
}
}
async updateXPBreakdown() {
try {
const response = await fetch('/api/xp-breakdown');
const breakdown = await response.json();
const breakdownContainer = document.getElementById('xp-breakdown');
if (breakdown && Object.keys(breakdown).length > 0) {
const totalXP = Object.values(breakdown).reduce((sum, xp) => sum + xp, 0);
breakdownContainer.innerHTML = Object.entries(breakdown)
.sort(([,a], [,b]) => b - a) // Sort by XP amount descending
.map(([source, xp]) => {
const percentage = totalXP > 0 ? ((xp / totalXP) * 100).toFixed(1) : 0;
const icon = this.getXPSourceIcon(source);
return `
<div class="xp-source-item">
<div class="xp-source-header">
<span class="xp-source-icon">${icon}</span>
<span class="xp-source-name">${this.formatXPSourceName(source)}</span>
<span class="xp-source-amount">+${xp} XP</span>
</div>
<div class="xp-source-bar">
<div class="xp-source-progress" style="width: ${percentage}%"></div>
</div>
<div class="xp-source-percentage">${percentage}%</div>
</div>
`;
}).join('');
} else {
breakdownContainer.innerHTML = '<div class="xp-source-item"><span class="xp-source-name">No XP earned today</span></div>';
}
} catch (error) {
console.error('Failed to update XP breakdown:', error);
// If the endpoint doesn't exist yet, show a placeholder
const breakdownContainer = document.getElementById('xp-breakdown');
if (breakdownContainer) {
breakdownContainer.innerHTML = '<div class="xp-source-item"><span class="xp-source-name">XP breakdown coming soon...</span></div>';
}
}
}
getXPSourceIcon(source) {
const icons = {
'coding': '💻',
'focused_browsing': '🔍',
'collaboration': '🤝',
'meetings': '📅',
'misc': '📝',
'uncategorized': '❓',
'focus_session': '🎯',
'achievement': '🏆',
'manual_boost': '🚀',
// Legacy category support
'browsing': '🌐',
'communication': '💬',
'meeting': '🤝',
'terminal': '⌨️',
'security': '🔐',
'other': '📝'
};
return icons[source] || '📊';
}
formatXPSourceName(source) {
const names = {
'coding': 'Coding',
'focused_browsing': 'Focused Browsing',
'collaboration': 'Collaboration',
'meetings': 'Meetings',
'misc': 'Miscellaneous',
'uncategorized': 'Uncategorized',
'focus_session': 'Focus Sessions',
'achievement': 'Achievements',
'manual_boost': 'Manual Boosts',
// Legacy category support
'browsing': 'Web Browsing',
'communication': 'Communication',
'meeting': 'Meetings',
'terminal': 'Terminal/CLI',
'security': 'Security Tools',
'other': 'Other Activities'
};
return names[source] || source.charAt(0).toUpperCase() + source.slice(1);
}
async updateLogs() {
try {
const level = document.getElementById('log-level').value;
const url = level ? `/api/logs?level=${level}&count=50` : '/api/logs?count=50';
const response = await fetch(url);
const data = await response.json();
const logsContainer = document.getElementById('logs-container');
if (data.logs && data.logs.length > 0) {
logsContainer.innerHTML = data.logs.map(log => {
const logClass = this.getLogClass(log);
return `<div class="log-entry ${logClass}">${this.escapeHtml(log)}</div>`;
}).join('');
} else {
logsContainer.innerHTML = '<div class="log-entry">No logs available</div>';
}
// Auto-scroll to bottom
logsContainer.scrollTop = logsContainer.scrollHeight;
} catch (error) {
console.error('Failed to update logs:', error);
}
}
async loadConfig() {
try {
const response = await fetch('/api/config');
const config = await response.json();
// Update config inputs
document.getElementById('coding-xp').value = config.xp_rewards?.base_multipliers?.coding || 10;
document.getElementById('research-xp').value = config.xp_rewards?.base_multipliers?.research || 8;
document.getElementById('meeting-xp').value = config.xp_rewards?.base_multipliers?.meeting || 3;
document.getElementById('focus-bonus').value = config.xp_rewards?.focus_session_bonuses?.base_xp_per_minute || 5;
} catch (error) {
console.error('Failed to load config:', error);
}
}
async saveConfig() {
try {
const updates = {
'xp_rewards.base_multipliers.coding': parseInt(document.getElementById('coding-xp').value),
'xp_rewards.base_multipliers.research': parseInt(document.getElementById('research-xp').value),
'xp_rewards.base_multipliers.meeting': parseInt(document.getElementById('meeting-xp').value),
'xp_rewards.focus_session_bonuses.base_xp_per_minute': parseInt(document.getElementById('focus-bonus').value)
};
const response = await fetch('/api/config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updates)
});
if (response.ok) {
this.showMessage('Configuration saved successfully!', 'success');
} else {
throw new Error('Failed to save configuration');
}
} catch (error) {
console.error('Failed to save config:', error);
this.showMessage('Failed to save configuration', 'error');
}
}
async setupChart() {
try {
const response = await fetch('/api/stats/history?days=7');
const history = await response.json();
const ctx = document.getElementById('xp-chart').getContext('2d');
const labels = history.map(day => {
const date = new Date(day.date);
return date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
});
const xpData = history.map(day => day.xp);
const levelData = history.map(day => day.level);
this.chart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'XP',
data: xpData,
borderColor: '#667eea',
backgroundColor: 'rgba(102, 126, 234, 0.1)',
tension: 0.4,
fill: true,
yAxisID: 'y'
},
{
label: 'Level',
data: levelData,
borderColor: '#764ba2',
backgroundColor: 'rgba(118, 75, 162, 0.1)',
tension: 0.4,
fill: false,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false,
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'Date'
}
},
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'XP'
},
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: 'Level'
},
grid: {
drawOnChartArea: false,
},
}
},
plugins: {
legend: {
display: true,
position: 'top'
},
title: {
display: false
}
}
}
});
} catch (error) {
console.error('Failed to setup chart:', error);
}
}
async updateChart() {
try {
const response = await fetch('/api/stats/history?days=7');
const history = await response.json();
if (this.chart) {
const labels = history.map(day => {
const date = new Date(day.date);
return date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
});
const xpData = history.map(day => day.xp);
const levelData = history.map(day => day.level);
// Update chart data
this.chart.data.labels = labels;
this.chart.data.datasets[0].data = xpData;
this.chart.data.datasets[1].data = levelData;
// Refresh the chart
this.chart.update('none'); // 'none' for no animation during updates
}
} catch (error) {
console.error('Failed to update chart:', error);
}
}
setupEventListeners() {
// Save config button
document.getElementById('save-config').addEventListener('click', () => {
this.saveConfig();
});
// Refresh logs button
document.getElementById('refresh-logs').addEventListener('click', () => {
this.updateLogs();
});
// Log level filter
document.getElementById('log-level').addEventListener('change', () => {
this.updateLogs();
});
}
startAutoRefresh() {
// Refresh data every 30 seconds
this.refreshInterval = setInterval(() => {
this.updateStatsAndActivity();
this.updateChart();
this.updateAchievements();
this.updateXPBreakdown();
}, 30000);
}
stopAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = null;
}
}
getLogClass(logEntry) {
if (logEntry.includes('[ERROR]')) return 'error';
if (logEntry.includes('[WARN]')) return 'warn';
if (logEntry.includes('[INFO]')) return 'info';
if (logEntry.includes('[DEBUG]')) return 'debug';
return '';
}
capitalizeFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
showMessage(message, type = 'info') {
// Create message element
const messageEl = document.createElement('div');
messageEl.className = `message ${type}`;
messageEl.textContent = message;
// Insert at top of container
const container = document.querySelector('.container');
container.insertBefore(messageEl, container.firstChild);
// Remove after 5 seconds
setTimeout(() => {
if (messageEl.parentNode) {
messageEl.parentNode.removeChild(messageEl);
}
}, 5000);
}
async updateClassifications() {
try {
const response = await fetch('/api/classifications');
const classifications = await response.json();
const classificationsContainer = document.getElementById('classifications-list');
if (classifications && classifications.length > 0) {
classificationsContainer.innerHTML = classifications.map(classification => {
const categoryIcon = this.getCategoryIcon(classification.category_id);
const categoryName = this.formatCategoryName(classification.category_id);
return `
<div class="classification-item">
<div class="classification-header">
<span class="classification-icon">${categoryIcon}</span>
<span class="classification-app">${classification.application_name}</span>
<span class="classification-category">${categoryName}</span>
<button class="btn-delete" onclick="dashboard.deleteClassification('${classification.application_name}')">×</button>
</div>
</div>
`;
}).join('');
} else {
classificationsContainer.innerHTML = '<div class="classification-item"><span class="classification-name">No classifications yet</span></div>';
}
} catch (error) {
console.error('Failed to update classifications:', error);
}
}
async updateUnclassified() {
try {
const response = await fetch('/api/unclassified');
const unclassified = await response.json();
const unclassifiedContainer = document.getElementById('unclassified-list');
if (unclassified && unclassified.length > 0) {
unclassifiedContainer.innerHTML = unclassified.map(app => {
const lastSeen = new Date(app.last_seen);
const timeStr = lastSeen.toLocaleDateString();
return `
<div class="unclassified-item">
<div class="unclassified-header">
<span class="unclassified-name">${app.application_name}</span>
<span class="unclassified-count">${app.occurrence_count} times</span>
<span class="unclassified-date">Last: ${timeStr}</span>
</div>
<div class="classification-controls">
<select class="category-select" id="category-${app.id}">
<option value="">Select category...</option>
<option value="coding">💻 Coding</option>
<option value="focused_browsing">🔍 Focused Browsing</option>
<option value="collaboration">🤝 Collaboration</option>
<option value="meetings">📅 Meetings</option>
<option value="misc">📝 Miscellaneous</option>
<option value="uncategorized"> Uncategorized</option>
</select>
<button class="btn-classify" onclick="dashboard.classifyApplication('${app.application_name}', 'category-${app.id}')">Classify</button>
</div>
</div>
`;
}).join('');
} else {
unclassifiedContainer.innerHTML = '<div class="unclassified-item"><span class="unclassified-name">No unclassified applications</span></div>';
}
} catch (error) {
console.error('Failed to update unclassified applications:', error);
}
}
async classifyApplication(applicationName, selectId) {
try {
const selectElement = document.getElementById(selectId);
const categoryId = selectElement.value;
if (!categoryId) {
this.showMessage('Please select a category', 'error');
return;
}
const response = await fetch('/api/classifications', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
application_name: applicationName,
category_id: categoryId
})
});
if (response.ok) {
this.showMessage(`${applicationName} classified as ${this.formatCategoryName(categoryId)}`, 'success');
await this.updateClassifications();
await this.updateUnclassified();
} else {
throw new Error('Failed to classify application');
}
} catch (error) {
console.error('Failed to classify application:', error);
this.showMessage('Failed to classify application', 'error');
}
}
async deleteClassification(applicationName) {
try {
const encodedName = encodeURIComponent(applicationName);
const response = await fetch(`/api/classifications/${encodedName}`, {
method: 'DELETE'
});
if (response.ok) {
this.showMessage(`Classification for ${applicationName} removed`, 'success');
await this.updateClassifications();
await this.updateUnclassified();
} else {
throw new Error('Failed to delete classification');
}
} catch (error) {
console.error('Failed to delete classification:', error);
this.showMessage('Failed to delete classification', 'error');
}
}
getCategoryIcon(categoryId) {
const icons = {
'coding': '💻',
'focused_browsing': '🔍',
'collaboration': '🤝',
'meetings': '📅',
'misc': '📝',
'uncategorized': '❓',
// Legacy category support
'browsing': '🌐',
'communication': '💬',
'meeting': '🤝',
'terminal': '⌨️',
'security': '🔐',
'other': '📝'
};
return icons[categoryId] || '📊';
}
formatCategoryName(categoryId) {
const names = {
'coding': 'Coding',
'focused_browsing': 'Focused Browsing',
'collaboration': 'Collaboration',
'meetings': 'Meetings',
'misc': 'Miscellaneous',
'uncategorized': 'Uncategorized',
// Legacy category support
'browsing': 'Web Browsing',
'communication': 'Communication',
'meeting': 'Meetings',
'terminal': 'Terminal/CLI',
'security': 'Security Tools',
'other': 'Other'
};
return names[categoryId] || categoryId.charAt(0).toUpperCase() + categoryId.slice(1);
}
destroy() {
this.stopAutoRefresh();
if (this.chart) {
this.chart.destroy();
}
}
}
// Initialize dashboard when page loads
document.addEventListener('DOMContentLoaded', () => {
window.dashboard = new ProductivityDashboard();
});
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
if (window.dashboard) {
window.dashboard.destroy();
}
});

View File

@ -0,0 +1,159 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XP Nix - Productivity Dashboard</title>
<link rel="stylesheet" href="style.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="container">
<header>
<h1>🎮 XP Nix Productivity Dashboard</h1>
<div class="header-stats">
<div class="stat-card">
<span class="stat-label">Level</span>
<span class="stat-value" id="current-level">1</span>
</div>
<div class="stat-card">
<span class="stat-label">XP</span>
<span class="stat-value" id="current-xp">0</span>
</div>
<div class="stat-card">
<span class="stat-label">Streak</span>
<span class="stat-value" id="current-streak">0</span>
</div>
</div>
</header>
<div class="dashboard-grid">
<!-- Today's Stats -->
<div class="card">
<h2>📊 Today's Progress</h2>
<div class="progress-stats">
<div class="progress-item">
<span class="progress-label">Focus Time</span>
<div class="progress-bar">
<div class="progress-fill" id="focus-progress"></div>
</div>
<span class="progress-value" id="focus-time">0h 0m</span>
</div>
<div class="progress-item">
<span class="progress-label">Meeting Time</span>
<div class="progress-bar">
<div class="progress-fill" id="meeting-progress"></div>
</div>
<span class="progress-value" id="meeting-time">0h 0m</span>
</div>
<div class="progress-item">
<span class="progress-label">Focus Sessions</span>
<span class="progress-value" id="focus-sessions">0</span>
</div>
</div>
</div>
<!-- XP History Chart -->
<div class="card chart-card">
<h2>📈 XP Progress (7 Days)</h2>
<canvas id="xp-chart"></canvas>
</div>
<!-- Recent Activity -->
<div class="card">
<h2>⚡ Recent Activity</h2>
<div class="activity-list" id="recent-activity">
<div class="activity-item">
<span class="activity-type">No recent activity</span>
</div>
</div>
</div>
<!-- XP Breakdown -->
<div class="card">
<h2>💎 XP Sources Today</h2>
<div class="xp-breakdown" id="xp-breakdown">
<div class="xp-source-item">
<span class="xp-source-name">Loading XP breakdown...</span>
</div>
</div>
</div>
<!-- Achievements -->
<div class="card">
<h2>🏆 Recent Achievements</h2>
<div class="achievements-list" id="achievements-list">
<div class="achievement-item">
<span class="achievement-name">No achievements yet</span>
</div>
</div>
</div>
<!-- Configuration Panel -->
<div class="card config-card">
<h2>⚙️ Configuration</h2>
<div class="config-section">
<h3>XP Multipliers</h3>
<div class="config-group">
<label for="coding-xp">Coding XP per minute:</label>
<input type="number" id="coding-xp" min="1" max="50" value="10">
</div>
<div class="config-group">
<label for="research-xp">Research XP per minute:</label>
<input type="number" id="research-xp" min="1" max="50" value="8">
</div>
<div class="config-group">
<label for="meeting-xp">Meeting XP per minute:</label>
<input type="number" id="meeting-xp" min="1" max="50" value="3">
</div>
<div class="config-group">
<label for="focus-bonus">Focus session bonus per minute:</label>
<input type="number" id="focus-bonus" min="1" max="20" value="5">
</div>
<button id="save-config" class="btn-primary">Save Configuration</button>
</div>
</div>
<!-- Application Classifications -->
<div class="card classification-card">
<h2>🏷️ Application Classifications</h2>
<div class="classification-section">
<h3>Unclassified Applications</h3>
<div class="unclassified-list" id="unclassified-list">
<div class="unclassified-item">
<span class="unclassified-name">Loading unclassified applications...</span>
</div>
</div>
<h3>Current Classifications</h3>
<div class="classifications-list" id="classifications-list">
<div class="classification-item">
<span class="classification-name">Loading classifications...</span>
</div>
</div>
</div>
</div>
<!-- System Logs -->
<div class="card logs-card">
<h2>📝 System Logs</h2>
<div class="logs-controls">
<select id="log-level">
<option value="">All Levels</option>
<option value="debug">Debug</option>
<option value="info">Info</option>
<option value="warn">Warning</option>
<option value="error">Error</option>
</select>
<button id="refresh-logs" class="btn-secondary">Refresh</button>
</div>
<div class="logs-container" id="logs-container">
<div class="log-entry">Loading logs...</div>
</div>
</div>
</div>
</div>
<script src="dashboard.js"></script>
</body>
</html>

View File

@ -0,0 +1,579 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
header {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
header h1 {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 20px;
text-align: center;
}
.header-stats {
display: flex;
justify-content: center;
gap: 30px;
flex-wrap: wrap;
}
.stat-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 12px;
color: white;
min-width: 120px;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
}
.stat-label {
font-size: 0.9rem;
opacity: 0.9;
margin-bottom: 5px;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 25px;
}
.card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 25px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
}
.card h2 {
font-size: 1.4rem;
margin-bottom: 20px;
color: #4a5568;
border-bottom: 2px solid #e2e8f0;
padding-bottom: 10px;
}
.progress-stats {
display: flex;
flex-direction: column;
gap: 20px;
}
.progress-item {
display: flex;
align-items: center;
gap: 15px;
}
.progress-label {
min-width: 100px;
font-weight: 600;
color: #4a5568;
}
.progress-bar {
flex: 1;
height: 8px;
background: #e2e8f0;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 4px;
transition: width 0.5s ease;
width: 0%;
}
.progress-value {
min-width: 80px;
text-align: right;
font-weight: 600;
color: #2d3748;
}
.chart-card {
grid-column: span 2;
}
.chart-card canvas {
max-height: 300px;
}
.activity-list, .achievements-list {
max-height: 300px;
overflow-y: auto;
}
.activity-item, .achievement-item {
padding: 12px;
border-left: 4px solid #667eea;
background: #f7fafc;
margin-bottom: 10px;
border-radius: 0 8px 8px 0;
transition: background 0.2s ease;
}
.activity-item:hover, .achievement-item:hover {
background: #edf2f7;
}
.activity-type, .achievement-name {
font-weight: 600;
color: #2d3748;
}
.activity-details, .achievement-description {
font-size: 0.9rem;
color: #718096;
margin-top: 5px;
}
/* XP Breakdown Styles */
.xp-breakdown {
max-height: 300px;
overflow-y: auto;
}
.xp-source-item {
padding: 15px;
background: #f7fafc;
margin-bottom: 12px;
border-radius: 8px;
border-left: 4px solid #667eea;
transition: background 0.2s ease;
}
.xp-source-item:hover {
background: #edf2f7;
}
.xp-source-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.xp-source-icon {
font-size: 1.2rem;
margin-right: 8px;
}
.xp-source-name {
font-weight: 600;
color: #2d3748;
flex: 1;
}
.xp-source-amount {
font-weight: 700;
color: #667eea;
font-size: 1.1rem;
}
.xp-source-bar {
height: 6px;
background: #e2e8f0;
border-radius: 3px;
overflow: hidden;
margin-bottom: 5px;
}
.xp-source-progress {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 3px;
transition: width 0.5s ease;
}
.xp-source-percentage {
font-size: 0.8rem;
color: #718096;
text-align: right;
}
.config-section {
display: flex;
flex-direction: column;
gap: 15px;
}
.config-section h3 {
color: #4a5568;
margin-bottom: 10px;
}
.config-group {
display: flex;
align-items: center;
gap: 10px;
}
.config-group label {
min-width: 180px;
font-weight: 500;
color: #4a5568;
}
.config-group input {
padding: 8px 12px;
border: 2px solid #e2e8f0;
border-radius: 6px;
font-size: 1rem;
transition: border-color 0.2s ease;
width: 80px;
}
.config-group input:focus {
outline: none;
border-color: #667eea;
}
.btn-primary, .btn-secondary {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-size: 1rem;
}
.btn-primary {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #e2e8f0;
color: #4a5568;
}
.btn-secondary:hover {
background: #cbd5e0;
}
.logs-card {
grid-column: span 2;
}
.logs-controls {
display: flex;
gap: 15px;
margin-bottom: 20px;
align-items: center;
}
.logs-controls select {
padding: 8px 12px;
border: 2px solid #e2e8f0;
border-radius: 6px;
background: white;
font-size: 1rem;
}
.logs-container {
background: #1a202c;
color: #e2e8f0;
padding: 20px;
border-radius: 8px;
max-height: 400px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.4;
}
.log-entry {
margin-bottom: 5px;
padding: 2px 0;
}
.log-entry.error {
color: #fed7d7;
background: rgba(254, 178, 178, 0.1);
padding: 4px 8px;
border-radius: 4px;
}
.log-entry.warn {
color: #faf089;
background: rgba(250, 240, 137, 0.1);
padding: 4px 8px;
border-radius: 4px;
}
.log-entry.info {
color: #90cdf4;
}
.log-entry.debug {
color: #a0aec0;
}
/* Responsive Design */
@media (max-width: 768px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
.chart-card, .logs-card {
grid-column: span 1;
}
.header-stats {
gap: 15px;
}
.stat-card {
min-width: 100px;
padding: 15px;
}
.config-group {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
.config-group label {
min-width: auto;
}
}
/* Loading Animation */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.loading {
animation: pulse 2s infinite;
}
/* Success/Error Messages */
.message {
padding: 12px 16px;
border-radius: 8px;
margin: 10px 0;
font-weight: 500;
}
.message.success {
background: #c6f6d5;
color: #22543d;
border: 1px solid #9ae6b4;
}
.message.error {
background: #fed7d7;
color: #742a2a;
border: 1px solid #feb2b2;
}
/* Classification Styles */
.classification-card {
grid-column: span 2;
}
.classification-section {
display: flex;
flex-direction: column;
gap: 25px;
}
.classification-section h3 {
color: #4a5568;
margin-bottom: 15px;
font-size: 1.2rem;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 8px;
}
.unclassified-list, .classifications-list {
max-height: 300px;
overflow-y: auto;
}
.unclassified-item, .classification-item {
padding: 15px;
background: #f7fafc;
margin-bottom: 12px;
border-radius: 8px;
border-left: 4px solid #ed8936;
transition: background 0.2s ease;
}
.classification-item {
border-left-color: #48bb78;
}
.unclassified-item:hover, .classification-item:hover {
background: #edf2f7;
}
.unclassified-header, .classification-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
flex-wrap: wrap;
gap: 10px;
}
.unclassified-name, .classification-app {
font-weight: 600;
color: #2d3748;
flex: 1;
min-width: 150px;
}
.unclassified-count, .unclassified-date {
font-size: 0.9rem;
color: #718096;
}
.classification-icon {
font-size: 1.2rem;
margin-right: 8px;
}
.classification-category {
font-weight: 500;
color: #48bb78;
background: rgba(72, 187, 120, 0.1);
padding: 4px 8px;
border-radius: 4px;
font-size: 0.9rem;
}
.classification-controls {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.category-select {
padding: 8px 12px;
border: 2px solid #e2e8f0;
border-radius: 6px;
background: white;
font-size: 0.9rem;
min-width: 180px;
transition: border-color 0.2s ease;
}
.category-select:focus {
outline: none;
border-color: #667eea;
}
.btn-classify {
padding: 8px 16px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.9rem;
}
.btn-classify:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
}
.btn-delete {
background: #e53e3e;
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.btn-delete:hover {
background: #c53030;
transform: scale(1.1);
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}

View File

@ -266,13 +266,13 @@ packages:
source: hosted
version: "1.1.3"
shelf_web_socket:
dependency: "direct main"
dependency: transitive
description:
name: shelf_web_socket
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.0"
source_map_stack_trace:
dependency: transitive
description:
@ -393,14 +393,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
web_socket_channel:
dependency: "direct main"
web_socket:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
name: web_socket
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev"
source: hosted
version: "3.0.3"
webkit_inspection_protocol:
dependency: transitive
description:

View File

@ -12,8 +12,6 @@ dependencies:
shelf: ^1.4.1
shelf_router: ^1.1.4
shelf_static: ^1.1.2
shelf_web_socket: ^2.0.0
web_socket_channel: ^2.4.0
# path: ^1.8.0
dev_dependencies:

View File

@ -1,45 +0,0 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
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
.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

View File

@ -1,5 +0,0 @@
ios/
android/
windows/
macos/
linux/

View File

@ -1,45 +0,0 @@
# 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: "nixpkgs000000000000000000000000000000000"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
- platform: android
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
- platform: ios
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
- platform: linux
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
- platform: macos
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
- platform: web
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
- platform: windows
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
# 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

@ -1,16 +0,0 @@
# xp_dashboard
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -1,28 +0,0 @@
# 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

View File

@ -1,14 +0,0 @@
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

@ -1,44 +0,0 @@
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.example.xp_dashboard"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.xp_dashboard"
// 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 = "../.."
}

View File

@ -1,7 +0,0 @@
<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

@ -1,45 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="xp_dashboard"
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

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

View File

@ -1,12 +0,0 @@
<?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>

View File

@ -1,12 +0,0 @@
<?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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,18 +0,0 @@
<?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

@ -1,18 +0,0 @@
<?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

@ -1,7 +0,0 @@
<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

@ -1,21 +0,0 @@
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

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

View File

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

View File

@ -1,25 +0,0 @@
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.7.3" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")

View File

@ -1 +0,0 @@
{"xp_rewards":{"base_multipliers":{"coding":10,"focused_browsing":6,"collaboration":7,"meetings":3,"misc":2,"uncategorized":1},"time_multipliers":{"deep_work_hours":{"times":["09:00-11:00","14:00-16:00"],"multiplier":1.5},"late_night_penalty":{"times":["22:00-06:00"],"multiplier":0.8}},"focus_session_bonuses":{"base_xp_per_minute":5,"milestones":{"60":100,"120":200,"180":500}},"zoom_multipliers":{"active_meeting":8,"background_meeting":5,"zoom_focused":2,"zoom_background":0}},"achievements":{"level_based":{"5":{"name":"Rising Star","description":"Reached level 5 - Your journey begins to shine!","xp_reward":100},"10":{"name":"Productivity Warrior","description":"Reached level 10 - You've unlocked desktop blur effects!","xp_reward":250},"15":{"name":"Focus Master","description":"Reached level 15 - Your desktop now glows with productivity!","xp_reward":500},"25":{"name":"Legendary Achiever","description":"Reached level 25 - You have transcended ordinary productivity!","xp_reward":1000}},"focus_based":{"deep_focus":{"name":"Deep Focus","description":"Maintained a straight hour of focus time in a day","xp_reward":200,"threshold_hours":1},"focus_titan":{"name":"Focus Titan","description":"Achieved 4 hours of pure focus - Incredible!","xp_reward":500,"threshold_hours":4}},"session_based":{"session_master":{"name":"Session Master","description":"Completed 5+ focus sessions in one day","xp_reward":150,"threshold_sessions":5}},"meeting_based":{"communication_pro":{"name":"Communication Pro","description":"Participated in 3+ hours of meetings, oof.","xp_reward":200,"threshold_hours":3}}},"level_system":{"xp_per_level":100,"max_level":100},"monitoring":{"poll_interval_seconds":30,"idle_threshold_minutes":1,"minimum_activity_seconds":10,"stats_display_interval_minutes":10},"logging":{"level":"INFO","max_file_size_mb":10,"max_files":5,"log_directory":"logs"}}

View File

@ -1,34 +0,0 @@
**/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

@ -1,26 +0,0 @@
<?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>12.0</string>
</dict>
</plist>

View File

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

View File

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

View File

@ -1,616 +0,0 @@
// !$*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 = 12.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.example.xpDashboard;
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.example.xpDashboard.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.example.xpDashboard.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.example.xpDashboard.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 = 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;
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 = 12.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 = 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 = 12.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.example.xpDashboard;
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.example.xpDashboard;
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

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

View File

@ -1,8 +0,0 @@
<?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

@ -1,8 +0,0 @@
<?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

@ -1,101 +0,0 @@
<?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

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

View File

@ -1,8 +0,0 @@
<?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

@ -1,8 +0,0 @@
<?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

@ -1,13 +0,0 @@
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

@ -1,122 +0,0 @@
{
"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" : "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" : "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.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,23 +0,0 @@
{
"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.

Before

Width:  |  Height:  |  Size: 68 B

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