Compare commits
10 Commits
b7a7eee777
...
v1.0.0
Author | SHA1 | Date | |
---|---|---|---|
897069c08b | |||
a1b577ef4c | |||
64895c12b9 | |||
7601cf3a22 | |||
8c7150a1ed | |||
a29f090895 | |||
8640ad0499 | |||
e62c74b72c | |||
75454c6edc | |||
997dd5b6b3 |
377
lib/main.dart
377
lib/main.dart
@@ -1,6 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer' as developer;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
@@ -12,11 +15,20 @@ class MyApp extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Simple Timer',
|
title: 'Just a timer',
|
||||||
theme: ThemeData(
|
darkTheme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
brightness: Brightness.dark,
|
||||||
|
useMaterial3: true,
|
||||||
|
scaffoldBackgroundColor: const Color(0xFF111827),
|
||||||
|
colorSchemeSeed: const Color(0xFF111827),
|
||||||
|
highlightColor: const Color(0xff1f2937),
|
||||||
|
textTheme: TextTheme(
|
||||||
|
bodyLarge: TextStyle(fontSize: 22),
|
||||||
|
bodyMedium: TextStyle(fontSize: 20),
|
||||||
|
bodySmall: TextStyle(fontSize: 17),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
home: const MyHomePage(title: 'Simple Timer'),
|
home: const MyHomePage(title: 'Just a timer'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,6 +40,25 @@ class MyHomePage extends StatefulWidget {
|
|||||||
State<MyHomePage> createState() => _MyHomePageState();
|
State<MyHomePage> createState() => _MyHomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TimerEvent {
|
||||||
|
final String type; // "start", "pause", "lap", "stop"
|
||||||
|
final DateTime timestamp;
|
||||||
|
|
||||||
|
TimerEvent(this.type, this.timestamp);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'type': type,
|
||||||
|
'timestamp': timestamp.toIso8601String(),
|
||||||
|
};
|
||||||
|
|
||||||
|
factory TimerEvent.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TimerEvent(
|
||||||
|
json['type'] as String,
|
||||||
|
DateTime.parse(json['timestamp'] as String),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
DateTime? _startTime;
|
DateTime? _startTime;
|
||||||
@@ -36,6 +67,154 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
bool _isRunning = false;
|
bool _isRunning = false;
|
||||||
List<String> _laps = [];
|
List<String> _laps = [];
|
||||||
final FocusNode _focusNode = FocusNode();
|
final FocusNode _focusNode = FocusNode();
|
||||||
|
List<TimerEvent> _events = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
_loadTimerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple file operations using app's temporary directory
|
||||||
|
Future<File> get _timerFile async {
|
||||||
|
final directory = Directory.systemTemp.path;
|
||||||
|
return File('$directory/timer_data.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveTimerData() async {
|
||||||
|
try {
|
||||||
|
final file = await _timerFile;
|
||||||
|
final data = {
|
||||||
|
'events': _events.map((e) => e.toJson()).toList(),
|
||||||
|
'laps': _laps,
|
||||||
|
};
|
||||||
|
await file.writeAsString(jsonEncode(data));
|
||||||
|
} catch (e) {
|
||||||
|
// Handle error silently using proper logging
|
||||||
|
developer.log('Failed to save timer data: $e', name: 'simple_timer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadTimerData() async {
|
||||||
|
try {
|
||||||
|
final file = await _timerFile;
|
||||||
|
if (await file.exists()) {
|
||||||
|
final jsonData = await file.readAsString();
|
||||||
|
final data = jsonDecode(jsonData) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
// Load events
|
||||||
|
_events = (data['events'] as List)
|
||||||
|
.map((e) => TimerEvent.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Load laps
|
||||||
|
_laps = (data['laps'] as List).cast<String>();
|
||||||
|
|
||||||
|
// Reconstruct timer state from events
|
||||||
|
_reconstructTimerState();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Handle error silently using proper logging
|
||||||
|
developer.log('Failed to load timer data: $e', name: 'simple_timer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reconstructTimerState() {
|
||||||
|
if (_events.isEmpty) return;
|
||||||
|
|
||||||
|
// Find the most recent event sequence
|
||||||
|
DateTime? lastStartTime;
|
||||||
|
DateTime? lastPauseTime;
|
||||||
|
Duration totalPausedTime = Duration.zero;
|
||||||
|
Duration accumulatedRunningTime = Duration.zero;
|
||||||
|
bool wasRunning = false;
|
||||||
|
|
||||||
|
// Process events chronologically to determine the current state
|
||||||
|
for (int i = 0; i < _events.length; i++) {
|
||||||
|
final event = _events[i];
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case 'start':
|
||||||
|
if (lastStartTime == null) {
|
||||||
|
// New start
|
||||||
|
lastStartTime = event.timestamp;
|
||||||
|
wasRunning = true;
|
||||||
|
} else if (lastPauseTime != null) {
|
||||||
|
// Resume after pause - add the pause duration to totalPausedTime
|
||||||
|
totalPausedTime += event.timestamp.difference(lastPauseTime);
|
||||||
|
lastPauseTime = null;
|
||||||
|
wasRunning = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pause':
|
||||||
|
if (lastStartTime != null && lastPauseTime == null) {
|
||||||
|
// Pause an active timer
|
||||||
|
lastPauseTime = event.timestamp;
|
||||||
|
accumulatedRunningTime += lastPauseTime.difference(lastStartTime);
|
||||||
|
wasRunning = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'stop':
|
||||||
|
// Reset everything
|
||||||
|
lastStartTime = null;
|
||||||
|
lastPauseTime = null;
|
||||||
|
totalPausedTime = Duration.zero;
|
||||||
|
accumulatedRunningTime = Duration.zero;
|
||||||
|
wasRunning = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine timer state based on most recent events
|
||||||
|
if (lastStartTime != null) {
|
||||||
|
if (wasRunning) {
|
||||||
|
// Timer was running when app was closed
|
||||||
|
// Calculate elapsed time: current time - start time - total paused time
|
||||||
|
final now = DateTime.now();
|
||||||
|
_elapsedTime = now.difference(lastStartTime) - totalPausedTime;
|
||||||
|
_startTime = lastStartTime;
|
||||||
|
_pauseTime = null;
|
||||||
|
_isRunning = true;
|
||||||
|
|
||||||
|
// Start the timer without resetting anything
|
||||||
|
_resumeTimerFromSavedState();
|
||||||
|
} else {
|
||||||
|
// Timer was paused
|
||||||
|
_startTime = lastStartTime;
|
||||||
|
_pauseTime = lastPauseTime;
|
||||||
|
_elapsedTime = accumulatedRunningTime;
|
||||||
|
_isRunning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume the timer from a saved state without resetting anything
|
||||||
|
void _resumeTimerFromSavedState() {
|
||||||
|
// Safety check
|
||||||
|
if (_startTime == null) return;
|
||||||
|
|
||||||
|
_timer = Timer.periodic(const Duration(milliseconds: 16), (timer) {
|
||||||
|
setState(() {
|
||||||
|
// When restoring, we need the total elapsed time since the original start
|
||||||
|
// This accounts for any pauses that happened before
|
||||||
|
final now = DateTime.now();
|
||||||
|
_elapsedTime = now.difference(_startTime!);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isRunning = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addEvent(String type) {
|
||||||
|
final event = TimerEvent(type, DateTime.now());
|
||||||
|
_events.add(event);
|
||||||
|
_saveTimerData();
|
||||||
|
}
|
||||||
|
|
||||||
void _startTimer() {
|
void _startTimer() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
@@ -44,14 +223,17 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
final pausedDuration = now.difference(_pauseTime!);
|
final pausedDuration = now.difference(_pauseTime!);
|
||||||
_startTime = _startTime?.add(pausedDuration);
|
_startTime = _startTime?.add(pausedDuration);
|
||||||
_pauseTime = null;
|
_pauseTime = null;
|
||||||
|
_addEvent('start');
|
||||||
} else {
|
} else {
|
||||||
// Starting fresh
|
// Starting fresh
|
||||||
_startTime = now;
|
_startTime = now;
|
||||||
_elapsedTime = Duration.zero;
|
_elapsedTime = Duration.zero;
|
||||||
_laps = [];
|
_laps = [];
|
||||||
|
_events = []; // Clear previous events
|
||||||
|
_addEvent('start');
|
||||||
}
|
}
|
||||||
|
|
||||||
_timer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
|
_timer = Timer.periodic(const Duration(milliseconds: 16), (timer) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_elapsedTime = DateTime.now().difference(_startTime!);
|
_elapsedTime = DateTime.now().difference(_startTime!);
|
||||||
});
|
});
|
||||||
@@ -65,6 +247,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
void _pauseTimer() {
|
void _pauseTimer() {
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_pauseTime = DateTime.now();
|
_pauseTime = DateTime.now();
|
||||||
|
_addEvent('pause');
|
||||||
setState(() {
|
setState(() {
|
||||||
_isRunning = false;
|
_isRunning = false;
|
||||||
});
|
});
|
||||||
@@ -72,64 +255,72 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
|
|
||||||
void _stopTimer() {
|
void _stopTimer() {
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
|
_addEvent('stop');
|
||||||
setState(() {
|
setState(() {
|
||||||
_isRunning = false;
|
_isRunning = false;
|
||||||
_startTime = null;
|
_startTime = null;
|
||||||
_pauseTime = null;
|
_pauseTime = null;
|
||||||
_elapsedTime = Duration.zero;
|
_elapsedTime = Duration.zero;
|
||||||
});
|
});
|
||||||
|
_saveTimerData();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _recordLap() {
|
void _recordLap() {
|
||||||
if (_isRunning) {
|
if (_isRunning) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
|
_addEvent('lap');
|
||||||
setState(() {
|
setState(() {
|
||||||
// Store both timestamp and formatted duration for this lap
|
// Store both timestamp and formatted duration for this lap
|
||||||
_laps.add('${now.toIso8601String()} - ${_formatDuration(_elapsedTime)}');
|
_laps.add('${now.toIso8601String()} - ${_formatDurationForLap(_elapsedTime)}');
|
||||||
});
|
});
|
||||||
|
_saveTimerData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatDuration(Duration duration) {
|
String _formatDuration(Duration duration) {
|
||||||
// Format the duration as ISO 8601 duration format without 0 values
|
// Format with fixed width on a single line, hiding zero hours/minutes
|
||||||
final hours = duration.inHours;
|
final hours = duration.inHours;
|
||||||
final minutes = duration.inMinutes.remainder(60);
|
final minutes = duration.inMinutes.remainder(60);
|
||||||
final seconds = duration.inSeconds.remainder(60);
|
final seconds = duration.inSeconds.remainder(60);
|
||||||
|
final milliseconds = duration.inMilliseconds.remainder(1000);
|
||||||
|
|
||||||
// Round milliseconds to nearest 100ms for more consistent display
|
// Use padLeft to ensure consistent width with leading zeros
|
||||||
// This helps avoid floating point inconsistencies (0.201, 0.301, etc.)
|
final secondsStr = seconds.toString().padLeft(2, '0');
|
||||||
final milliseconds = ((duration.inMilliseconds % 1000) / 100).round() * 100;
|
final msStr = milliseconds.toString().padLeft(3, '0');
|
||||||
final formattedMs = (milliseconds ~/ 100).toString();
|
|
||||||
|
|
||||||
// Start with PT prefix
|
// Build output - only show non-zero hours and minutes
|
||||||
String result = 'PT';
|
StringBuffer result = StringBuffer();
|
||||||
|
|
||||||
// Only add parts that are non-zero (except if everything is zero, then show 0S)
|
|
||||||
if (hours > 0) {
|
if (hours > 0) {
|
||||||
result += '${hours}H';
|
result.write('${hours.toString().padLeft(2, '0')}H ');
|
||||||
}
|
}
|
||||||
|
if (hours > 0 || minutes > 0) {
|
||||||
if (minutes > 0 || hours > 0) {
|
result.write('${minutes.toString().padLeft(2, '0')}M ');
|
||||||
result += '${minutes}M';
|
|
||||||
}
|
}
|
||||||
|
// Always show seconds and milliseconds
|
||||||
|
result.write('${secondsStr}S ${msStr}MS');
|
||||||
|
|
||||||
// Always include seconds with a single decimal place (tenth of a second)
|
return result.toString();
|
||||||
result += '${seconds}.${formattedMs}S';
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleKeyEvent(RawKeyEvent event) {
|
// For lap recording - use the same format
|
||||||
if (event is RawKeyDownEvent) {
|
String _formatDurationForLap(Duration duration) {
|
||||||
|
return _formatDuration(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update keyboard handler method to use modern KeyEvent API
|
||||||
|
void _handleKeyEvent(KeyEvent event) {
|
||||||
|
// Only process key down events to avoid duplicate triggers
|
||||||
|
if (event is KeyDownEvent) {
|
||||||
|
// Handle Space key
|
||||||
if (event.logicalKey == LogicalKeyboardKey.space) {
|
if (event.logicalKey == LogicalKeyboardKey.space) {
|
||||||
// Space: Start if paused, Lap if running
|
|
||||||
if (_isRunning) {
|
if (_isRunning) {
|
||||||
_recordLap();
|
_recordLap();
|
||||||
} else {
|
} else {
|
||||||
_startTimer();
|
_startTimer();
|
||||||
}
|
}
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.enter) {
|
}
|
||||||
// Enter: Stop if paused, Pause if running
|
// Handle Enter key
|
||||||
|
else if (event.logicalKey == LogicalKeyboardKey.enter) {
|
||||||
if (_isRunning) {
|
if (_isRunning) {
|
||||||
_pauseTimer();
|
_pauseTimer();
|
||||||
} else {
|
} else {
|
||||||
@@ -139,12 +330,6 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_focusNode.requestFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
@@ -154,78 +339,84 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RawKeyboardListener(
|
// Use Focus directly to capture key events
|
||||||
|
return Focus(
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
onKey: _handleKeyEvent,
|
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
|
onKeyEvent: (FocusNode node, KeyEvent event) {
|
||||||
|
_handleKeyEvent(event);
|
||||||
|
// Always return KeyEventResult.handled to indicate we've processed the event
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||||
title: Text(widget.title),
|
title: Text(widget.title),
|
||||||
),
|
),
|
||||||
body: Center(
|
body: GestureDetector(
|
||||||
child: Column(
|
// This ensures tapping anywhere gives focus back to our listener
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
onTap: () => _focusNode.requestFocus(),
|
||||||
children: [
|
behavior: HitTestBehavior.translucent,
|
||||||
Text(
|
child: Center(
|
||||||
'Time Elapsed:',
|
child: Column(
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
),
|
children: [
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Container(
|
// Use a fixed height container to prevent expanding and force horizontal scrolling if needed
|
||||||
width: 300, // Fixed width container to prevent "flashing"
|
SizedBox(
|
||||||
alignment: Alignment.center,
|
height: 50,
|
||||||
child: Text(
|
child: SingleChildScrollView(
|
||||||
_formatDuration(_elapsedTime),
|
scrollDirection: Axis.horizontal,
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
child: Text(
|
||||||
fontFamily: 'monospace', // Use monospaced font for fixed-width characters
|
_formatDuration(_elapsedTime),
|
||||||
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||||
|
fontFamily: 'monospace', // Use monospaced font for fixed-width characters
|
||||||
|
),
|
||||||
|
softWrap: false, // Prevent text wrapping
|
||||||
|
overflow: TextOverflow.visible, // Allow text to extend beyond bounds
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 30),
|
||||||
const SizedBox(height: 30),
|
Row(
|
||||||
Row(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
children: [
|
||||||
children: [
|
ElevatedButton(
|
||||||
ElevatedButton(
|
onPressed: _isRunning ? _pauseTimer : _startTimer,
|
||||||
onPressed: _isRunning ? _pauseTimer : _startTimer,
|
child: Text(_isRunning ? 'Pause' : 'Start'),
|
||||||
child: Text(_isRunning ? 'Pause' : 'Start'),
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _stopTimer,
|
||||||
|
child: const Text('Stop'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _recordLap,
|
||||||
|
child: const Text('Lap'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
if (_laps.isNotEmpty) ...[
|
||||||
|
Text(
|
||||||
|
'Laps:',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(height: 10),
|
||||||
ElevatedButton(
|
Expanded(
|
||||||
onPressed: _stopTimer,
|
child: ListView.builder(
|
||||||
child: const Text('Stop'),
|
itemCount: _laps.length,
|
||||||
),
|
itemBuilder: (context, index) {
|
||||||
const SizedBox(width: 20),
|
return ListTile(
|
||||||
ElevatedButton(
|
title: Text('Lap ${(index + 1).toString().padLeft(3, '0')}: ${_laps[index]}'),
|
||||||
onPressed: _recordLap,
|
);
|
||||||
child: const Text('Lap'),
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Text(
|
|
||||||
'Keyboard: SPACE for Start/Lap, ENTER for Pause/Stop',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
if (_laps.isNotEmpty) ...[
|
|
||||||
Text(
|
|
||||||
'Laps:',
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: _laps.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return ListTile(
|
|
||||||
title: Text('Lap ${index + 1}: ${_laps[index]}'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@@ -5,6 +5,8 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import path_provider_foundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
}
|
}
|
||||||
|
82
pubspec.lock
82
pubspec.lock
@@ -57,6 +57,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -139,6 +147,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
path_provider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.16"
|
||||||
|
path_provider_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_foundation
|
||||||
|
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.6"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -208,6 +280,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.1"
|
version: "14.3.1"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.7.0 <4.0.0"
|
dart: ">=3.7.0 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.27.0"
|
||||||
|
@@ -34,6 +34,7 @@ dependencies:
|
|||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
|
path_provider: ^2.1.2 # For accessing local file system
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@@ -25,10 +25,10 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
|||||||
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||||
|
|
||||||
FlutterWindow window(project);
|
FlutterWindow window(project);
|
||||||
Win32Window::Size size(1920, 1080);
|
Win32Window::Size size(1200, 800);
|
||||||
Win32Window::Point origin((GetSystemMetrics(SM_CXSCREEN) - size.width) / 2,
|
Win32Window::Point origin((GetSystemMetrics(SM_CXSCREEN) - size.width) / 2,
|
||||||
(GetSystemMetrics(SM_CYSCREEN) - size.height) / 2);
|
(GetSystemMetrics(SM_CYSCREEN) - size.height) / 2);
|
||||||
if (!window.Create(L"simple_timer", origin, size)) {
|
if (!window.Create(L"Just a timer", origin, size)) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
window.SetQuitOnClose(true);
|
window.SetQuitOnClose(true);
|
||||||
|
Reference in New Issue
Block a user