diff --git a/.metadata b/.metadata
index d3910a9..ac7818b 100644
--- a/.metadata
+++ b/.metadata
@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
- revision: "2663184aa79047d0a33a14a3b607954f8fdd8730"
+ revision: "4cf24164269a5ebf0c16a028a00727d0e77bbb05"
channel: "stable"
project_type: app
@@ -13,11 +13,14 @@ project_type: app
migration:
platforms:
- platform: root
- create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- - platform: ios
- create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
+ create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
+ base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
+ - platform: web
+ create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
+ base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
+ - platform: windows
+ create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
+ base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
# User provided section
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 0d29021..bf8d421 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -7,6 +7,15 @@
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
+analyzer:
+ exclude:
+ - build/**
+ - android/**
+ - ios/**
+ - web/**
+ - windows/**
+ - macos/**
+ - linux/**
include: package:flutter_lints/flutter.yaml
linter:
diff --git a/android/app/build.gradle b/android/app/build.gradle
index e0b5291..0629286 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -1,51 +1,46 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'dev.flutter.flutter-gradle-plugin'
+}
+
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
+
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
-def flutterRoot = localProperties.getProperty('flutter.sdk')
-if (flutterRoot == null) {
- throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
-}
-
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
-
android {
namespace = "com.settimer.settimer"
- compileSdk = 34 // Ensure this is appropriate for AGP 7.3.0
+ compileSdk = 36
+ ndkVersion = "28.2.13676358"
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = JavaVersion.VERSION_11
+ jvmTarget = '17'
}
defaultConfig {
applicationId = "com.settimer.settimer"
- minSdk = 21
- targetSdk = 34 // Ensure this is appropriate
+ minSdkVersion = flutter.minSdkVersion
+ targetSdk = 36
versionCode = 1
versionName = "1.0.0"
-
multiDexEnabled true
}
signingConfigs {
debug {
- // Default debug signing config
}
+
release {
- // TODO: Configure your release signing key
- // For now, this block can be empty or set up as needed for release builds.
- // Do NOT put 'signingConfig signingConfigs.debug' here!
}
}
@@ -55,18 +50,14 @@ android {
debuggable true
signingConfig signingConfigs.debug
}
+
release {
signingConfig signingConfigs.debug
- minifyEnabled true
- shrinkResources true
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
-
- // Optimize for distribution
- zipAlignEnabled true
- debuggable false
+ minifyEnabled false
+ shrinkResources false
}
}
-
+
buildFeatures {
buildConfig true
}
@@ -76,6 +67,6 @@ flutter {
source = '../..'
}
-dependencies {
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
+flutter {
+ source = '../..'
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 08275bd..ae73f1c 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -7,7 +7,7 @@
android:label="SetTimer"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
- android:roundIcon="@mipmap/ic_launcher_round"
+ android:roundIcon="@mipmap/ic_launcher"
android:allowBackup="false"
android:supportsRtl="true"
android:theme="@style/LaunchTheme">
diff --git a/android/build.gradle b/android/build.gradle
index dcd263b..33c7816 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,16 +1,3 @@
-buildscript {
- ext.kotlin_version = '1.9.24'
- repositories {
- google()
- mavenCentral()
- }
-
- dependencies {
- classpath 'com.android.tools.build:gradle:7.3.0'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- }
-}
-
allprojects {
repositories {
google()
@@ -19,13 +6,15 @@ allprojects {
}
rootProject.buildDir = '../build'
+
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
+
subprojects {
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
-}
+}
\ No newline at end of file
diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html
new file mode 100644
index 0000000..01fee78
--- /dev/null
+++ b/android/build/reports/problems/problems-report.html
@@ -0,0 +1,663 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Gradle Configuration Cache
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
diff --git a/android/gradle.properties b/android/gradle.properties
index 8804973..1451c01 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,7 +1,7 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=1G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
-android.enableJetifier=true
-android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
-android.nonFinalResIds=false
-org.gradle.java.home=/Library/Java/JavaVirtualMachines/microsoft-11.jdk/Contents/Home
+# This builtInKotlin flag was added automatically by Flutter migrator
+android.builtInKotlin=false
+# This newDsl flag was added automatically by Flutter migrator
+android.newDsl=false
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index e1ca574..5237aff 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
\ No newline at end of file
diff --git a/android/settings.gradle b/android/settings.gradle
index a32233a..e14c0be 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -18,8 +18,8 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version "7.3.0" apply false
- id "org.jetbrains.kotlin.android" version "1.9.24" apply false
+ id "com.android.application" version "8.11.1" apply false
+ id "org.jetbrains.kotlin.android" version "2.2.20" apply false
}
-include ":app"
+include ":app"
\ No newline at end of file
diff --git a/lib/controllers/timer_controller.dart b/lib/controllers/timer_controller.dart
index bffd528..a4b66bf 100644
--- a/lib/controllers/timer_controller.dart
+++ b/lib/controllers/timer_controller.dart
@@ -1,5 +1,4 @@
import 'dart:async';
-import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
@@ -49,7 +48,8 @@ class TimerController extends ChangeNotifier with WidgetsBindingObserver {
bool clearPreset = true, // Add flag to control preset clearing
}) {
// Only update if timer is not running
- if (_timer.state != TimerState.running && _timer.state != TimerState.resting) {
+ if (_timer.state != TimerState.running &&
+ _timer.state != TimerState.resting) {
final newSetDuration = setDurationSeconds ?? _timer.setDurationSeconds;
_timer = _timer.copyWith(
@@ -57,18 +57,25 @@ class TimerController extends ChangeNotifier with WidgetsBindingObserver {
setDurationSeconds: newSetDuration,
restDurationSeconds: restDurationSeconds,
restAfterSets: restAfterSets,
- remainingSeconds: _timer.state == TimerState.idle ? newSetDuration : _timer.remainingSeconds,
+ remainingSeconds: _timer.state == TimerState.idle
+ ? newSetDuration
+ : _timer.remainingSeconds,
currentSet: _timer.state == TimerState.idle ? 1 : _timer.currentSet,
- isInRestPeriod: _timer.state == TimerState.idle ? false : _timer.isInRestPeriod,
+ isInRestPeriod:
+ _timer.state == TimerState.idle ? false : _timer.isInRestPeriod,
);
// Clear preset when settings are manually changed (unless called from loadPreset)
if (clearPreset && _currentPreset != null) {
// Check if the settings still match the current preset
- bool settingsMatch = (totalSets ?? _timer.totalSets) == _currentPreset!.totalSets &&
- (setDurationSeconds ?? _timer.setDurationSeconds) == _currentPreset!.setDurationSeconds &&
- (restDurationSeconds ?? _timer.restDurationSeconds) == _currentPreset!.restDurationSeconds &&
- (restAfterSets ?? _timer.restAfterSets) == _currentPreset!.restAfterSets;
+ bool settingsMatch =
+ (totalSets ?? _timer.totalSets) == _currentPreset!.totalSets &&
+ (setDurationSeconds ?? _timer.setDurationSeconds) ==
+ _currentPreset!.setDurationSeconds &&
+ (restDurationSeconds ?? _timer.restDurationSeconds) ==
+ _currentPreset!.restDurationSeconds &&
+ (restAfterSets ?? _timer.restAfterSets) ==
+ _currentPreset!.restAfterSets;
if (!settingsMatch) {
clearCurrentPreset();
@@ -129,7 +136,8 @@ class TimerController extends ChangeNotifier with WidgetsBindingObserver {
// Enable wakelock to prevent screen from sleeping during workout
try {
await WakelockPlus.enable();
- print('🔒 Screen wakelock enabled - screen will stay on during workout');
+ print(
+ '🔒 Screen wakelock enabled - screen will stay on during workout');
} catch (e) {
print('⚠️ Failed to enable wakelock: $e');
}
@@ -143,7 +151,8 @@ class TimerController extends ChangeNotifier with WidgetsBindingObserver {
}
void pauseTimer() async {
- if (_timer.state == TimerState.running || _timer.state == TimerState.resting) {
+ if (_timer.state == TimerState.running ||
+ _timer.state == TimerState.resting) {
_countdownTimer?.cancel();
_timer = _timer.copyWith(state: TimerState.paused);
@@ -287,7 +296,8 @@ class TimerController extends ChangeNotifier with WidgetsBindingObserver {
await _sessionService.completeSession();
// Update achievements and check for new unlocks
- final newlyUnlocked = await _achievementService.updateAchievementProgress();
+ final newlyUnlocked =
+ await _achievementService.updateAchievementProgress();
// Show achievement notifications if any achievements were unlocked
if (newlyUnlocked.isNotEmpty) {
@@ -327,7 +337,8 @@ class TimerController extends ChangeNotifier with WidgetsBindingObserver {
}
void _handleAppPaused() async {
- if (_timer.state == TimerState.running || _timer.state == TimerState.resting) {
+ if (_timer.state == TimerState.running ||
+ _timer.state == TimerState.resting) {
_pausedAt = DateTime.now();
await _sessionService.pauseSession();
print('📱 App paused - Timer will continue in background');
@@ -335,7 +346,9 @@ class TimerController extends ChangeNotifier with WidgetsBindingObserver {
}
void _handleAppResumed() async {
- if (_pausedAt != null && (_timer.state == TimerState.running || _timer.state == TimerState.resting)) {
+ if (_pausedAt != null &&
+ (_timer.state == TimerState.running ||
+ _timer.state == TimerState.resting)) {
final pauseDuration = DateTime.now().difference(_pausedAt!);
final secondsPassed = pauseDuration.inSeconds;
@@ -344,7 +357,8 @@ class TimerController extends ChangeNotifier with WidgetsBindingObserver {
}
// Re-enable wakelock when resuming active timer
- if (_timer.state == TimerState.running || _timer.state == TimerState.resting) {
+ if (_timer.state == TimerState.running ||
+ _timer.state == TimerState.resting) {
try {
await WakelockPlus.enable();
print('🔒 Screen wakelock re-enabled after app resume');
@@ -355,7 +369,8 @@ class TimerController extends ChangeNotifier with WidgetsBindingObserver {
await _sessionService.resumeSession();
_pausedAt = null;
- print('📱 App resumed - Timer synced after ${secondsPassed}s in background');
+ print(
+ '📱 App resumed - Timer synced after ${secondsPassed}s in background');
}
}
@@ -457,12 +472,12 @@ class TimerController extends ChangeNotifier with WidgetsBindingObserver {
_backgroundService.disableBackgroundMode();
_sessionService.dispose();
WidgetsBinding.instance.removeObserver(this);
-
+
// Ensure wakelock is disabled when disposing
WakelockPlus.disable().catchError((e) {
print('⚠️ Failed to disable wakelock during dispose: $e');
});
-
+
super.dispose();
}
}
diff --git a/lib/main.dart b/lib/main.dart
index 4f03e18..b53c34c 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -34,7 +34,8 @@ class WorkoutSetTimerApp extends StatelessWidget {
return ChangeNotifierProvider(
create: (context) {
final controller = TimerController();
- controller.initialize(); // Initialize the controller and session service
+ controller
+ .initialize(); // Initialize the controller and session service
return controller;
},
child: MaterialApp(
@@ -52,14 +53,16 @@ class WorkoutSetTimerApp extends StatelessWidget {
// Improved input decoration theme
inputDecorationTheme: InputDecorationTheme(
filled: true,
- fillColor: Colors.white.withOpacity(0.05),
+ fillColor: Colors.white.withValues(alpha: 0.05),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
- borderSide: BorderSide(color: Colors.white.withOpacity(0.2)),
+ borderSide:
+ BorderSide(color: Colors.white.withValues(alpha: 0.2)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
- borderSide: BorderSide(color: Colors.white.withOpacity(0.2)),
+ borderSide:
+ BorderSide(color: Colors.white.withValues(alpha: 0.2)),
),
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
@@ -83,7 +86,7 @@ class WorkoutSetTimerApp extends StatelessWidget {
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: Colors.white70,
- side: BorderSide(color: Colors.white.withOpacity(0.3)),
+ side: BorderSide(color: Colors.white.withValues(alpha: 0.3)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
diff --git a/lib/services/achievement_service.dart b/lib/services/achievement_service.dart
index 9576fbf..623b5a7 100644
--- a/lib/services/achievement_service.dart
+++ b/lib/services/achievement_service.dart
@@ -15,11 +15,15 @@ class AchievementService {
final DatabaseService _databaseService = DatabaseService();
final WorkoutSessionService _sessionService = WorkoutSessionService();
- final StreamController _achievementUnlockedController = StreamController.broadcast();
- Stream get achievementUnlocked => _achievementUnlockedController.stream;
+ final StreamController _achievementUnlockedController =
+ StreamController.broadcast();
+ Stream get achievementUnlocked =>
+ _achievementUnlockedController.stream;
- final StreamController> _achievementProgressController = StreamController>.broadcast();
- Stream> get achievementProgress => _achievementProgressController.stream;
+ final StreamController> _achievementProgressController =
+ StreamController>.broadcast();
+ Stream> get achievementProgress =>
+ _achievementProgressController.stream;
List _achievements = [];
bool _isInitialized = false;
@@ -67,7 +71,10 @@ class AchievementService {
final achievements = await getUnlockedAchievements();
final sevenDaysAgo = DateTime.now().subtract(const Duration(days: 7));
- return achievements.where((a) => a.unlockedAt != null && a.unlockedAt!.isAfter(sevenDaysAgo)).toList();
+ return achievements
+ .where(
+ (a) => a.unlockedAt != null && a.unlockedAt!.isAfter(sevenDaysAgo))
+ .toList();
}
/// Update achievement progress after workout completion
@@ -131,16 +138,22 @@ class AchievementService {
return _personalStats['streak'] as int;
case AchievementType.totalWorkouts:
- return (_personalStats['analytics'] as Map)['totalWorkouts'] as int;
+ return (_personalStats['analytics']
+ as Map)['totalWorkouts'] as int;
case AchievementType.totalTime:
- return (_personalStats['analytics'] as Map)['totalWorkoutTime'] as int;
+ return (_personalStats['analytics']
+ as Map)['totalWorkoutTime'] as int;
case AchievementType.completion:
- return ((_personalStats['analytics'] as Map)['averageCompletionRate'] as double).round();
+ return ((_personalStats['analytics']
+ as Map)['averageCompletionRate'] as double)
+ .round();
case AchievementType.consistency:
- return ((_personalStats['analytics'] as Map)['workoutConsistency'] as double).round();
+ return ((_personalStats['analytics']
+ as Map)['workoutConsistency'] as double)
+ .round();
case AchievementType.personal:
return await _getPersonalAchievementProgress(achievement);
@@ -149,7 +162,8 @@ class AchievementService {
/// Get progress for personal achievements with detailed tracking
Future _getPersonalAchievementProgress(Achievement achievement) async {
- final personalBests = _personalStats['personalBests'] as Map;
+ final personalBests =
+ _personalStats['personalBests'] as Map;
final sessions = _personalStats['sessions'] as List;
switch (achievement.id) {
@@ -184,7 +198,8 @@ class AchievementService {
/// Check if user achieved speed demon (20 sets in under 10 minutes)
bool _checkSpeedDemonAchievement(List sessions) {
for (final session in sessions) {
- if (session.completedSets >= 20 && session.actualWorkoutDurationSeconds <= 600) {
+ if (session.completedSets >= 20 &&
+ session.actualWorkoutDurationSeconds <= 600) {
return true;
}
}
@@ -193,20 +208,26 @@ class AchievementService {
/// Count workouts completed before 7 AM
int _countEarlyMorningWorkouts(List sessions) {
- return sessions.where((session) => session.isCompleted && session.startTime.hour < 7).length;
+ return sessions
+ .where((session) => session.isCompleted && session.startTime.hour < 7)
+ .length;
}
/// Count workouts completed after 10 PM
int _countLateNightWorkouts(List sessions) {
- return sessions.where((session) => session.isCompleted && session.startTime.hour >= 22).length;
+ return sessions
+ .where((session) => session.isCompleted && session.startTime.hour >= 22)
+ .length;
}
/// Count workouts completed on weekends
int _countWeekendWorkouts(List sessions) {
final weekendDates = {};
for (final session in sessions) {
- if (session.isCompleted && (session.startTime.weekday == 6 || session.startTime.weekday == 7)) {
- final dateKey = '${session.startTime.year}-${session.startTime.month}-${session.startTime.day}';
+ if (session.isCompleted &&
+ (session.startTime.weekday == 6 || session.startTime.weekday == 7)) {
+ final dateKey =
+ '${session.startTime.year}-${session.startTime.month}-${session.startTime.day}';
weekendDates.add(dateKey);
}
}
@@ -215,8 +236,10 @@ class AchievementService {
/// Count unique presets used
int _countUniquePresets(List sessions) {
- final uniquePresets =
- sessions.where((session) => session.isCompleted && session.presetId != null).map((session) => session.presetId).toSet();
+ final uniquePresets = sessions
+ .where((session) => session.isCompleted && session.presetId != null)
+ .map((session) => session.presetId)
+ .toSet();
return uniquePresets.length;
}
@@ -232,14 +255,16 @@ class AchievementService {
debugPrint('🏆 Achievement Unlocked: ${achievement.title}');
debugPrint(' ${achievement.description}');
debugPrint(' Tier: ${achievement.tier.name.toUpperCase()}');
- debugPrint(' Points: ${AchievementDefinitions.getAchievementPoints(achievement)}');
+ debugPrint(
+ ' Points: ${AchievementDefinitions.getAchievementPoints(achievement)}');
}
}
/// Get achievement statistics
Future