diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5879725c..26cc1170 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + @@ -37,6 +38,12 @@ + + + + + = Build.VERSION_CODES.O) { + val channel = NotificationChannel( + TimerService.TIMER_CHANNEL_ID, + "Timer Channel", + NotificationManager.IMPORTANCE_DEFAULT + ) + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } + } + + + private fun showTimerNotification(milliseconds: Long,timerName:String,context: Context){ + var notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val deleteIntent = Intent(context,TimerNotification::class.java) + deleteIntent.action = "com.example.ultimate_alarm_clock.STOP_TIMERNOTIF" + val deletePendingIntent = PendingIntent.getBroadcast(context, 5, deleteIntent, + PendingIntent.FLAG_IMMUTABLE) + val notification = NotificationCompat.Builder(context, TimerService.TIMER_CHANNEL_ID) + .setSmallIcon(R.mipmap.launcher_icon) + .setContentText("$timerName") + .setContentText(formatDuration(milliseconds)) + .setOnlyAlertOnce(true) + .setDeleteIntent(deletePendingIntent) + . + build() + notificationManager.notify(1,notification) + } + + private fun formatDuration(milliseconds: Long): String { + val seconds = (milliseconds / 1000) % 60 + val minutes = (milliseconds / (1000 * 60)) % 60 + val hours = (milliseconds / (1000 * 60 * 60)) % 24 + + return if (hours > 0) { + String.format("%02d:%02d:%02d", hours, minutes, seconds) + } else { + String.format("%02d:%02d", minutes, seconds) + } + } + + + diff --git a/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/CommonTimerManager.kt b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/CommonTimerManager.kt new file mode 100644 index 00000000..e860b1bb --- /dev/null +++ b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/CommonTimerManager.kt @@ -0,0 +1,39 @@ +package com.example.ultimate_alarm_clock + +import android.os.CountDownTimer + +object CommonTimerManager { + private var commonTimer: CommonTimer? = null + + fun getCommonTimer(listener: TimerListener): CommonTimer { + if (commonTimer == null) { + commonTimer = CommonTimer(listener) + } + return commonTimer!! + } +} +class CommonTimer(private val listener: TimerListener) { + private var timer: CountDownTimer? = null + + fun startTimer(durationMillis: Long) { + timer?.cancel() // Cancel any existing timer + timer = object : CountDownTimer(durationMillis, 1000) { + override fun onTick(millisUntilFinished: Long) { + listener.onTick(millisUntilFinished) + } + + override fun onFinish() { + listener.onFinish() + } + }.start() + } + + fun stopTimer() { + timer?.cancel() + } +} + +interface TimerListener { + fun onTick(millisUntilFinished: Long) + fun onFinish() +} diff --git a/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/GetLatestAlarm.kt b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/GetLatestAlarm.kt index c9011890..52e80b78 100644 --- a/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/GetLatestAlarm.kt +++ b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/GetLatestAlarm.kt @@ -81,18 +81,7 @@ fun getLatestAlarm(db: SQLiteDatabase, wantNextAlarm: Boolean): Long? { } } -fun calculatePriority(days: String, currentDay: Int): Int { - if (days == "0000000") { - return 0 - } - for (i in 1..7) { - val dayIndex = (currentDay + i) % 7 - if (days[dayIndex] == '1') { - return i - } - } - return 7 -} + fun stringToTimeOfDay(time: String): LocalTime { val parts = time.split(":") diff --git a/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/MainActivity.kt b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/MainActivity.kt index 70a65476..92448da9 100644 --- a/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/MainActivity.kt @@ -1,30 +1,25 @@ package com.example.ultimate_alarm_clock -import android.os.Bundle -import androidx.annotation.NonNull -import io.flutter.embedding.android.FlutterActivity -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.embedding.engine.FlutterEngineCache -import io.flutter.embedding.android.FlutterActivityLaunchConfigs -import io.flutter.embedding.engine.dart.DartExecutor -import io.flutter.plugins.GeneratedPluginRegistrant -import io.flutter.plugin.common.MethodChannel +import android.app.ActivityManager import android.app.AlarmManager +import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.os.SystemClock -import android.app.ActivityManager -import android.widget.Toast -import android.os.PowerManager -import android.util.Log -import android.view.WindowManager +import android.content.IntentFilter import android.media.Ringtone import android.media.RingtoneManager import android.net.Uri -import java.time.LocalTime +import android.os.Bundle +import android.os.SystemClock +import android.view.WindowManager +import androidx.annotation.NonNull +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel + -class MainActivity : FlutterActivity() { +class MainActivity : FlutterActivity() { companion object { const val CHANNEL1 = "ulticlock" @@ -37,6 +32,17 @@ class MainActivity : FlutterActivity() { private var ringtone: Ringtone? = null } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + var intentFilter = IntentFilter() + intentFilter.addAction("com.example.ultimate_alarm_clock.START_TIMERNOTIF") + intentFilter.addAction("com.example.ultimate_alarm_clock.STOP_TIMERNOTIF") + context.registerReceiver(TimerNotification(),intentFilter) + } + + + + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) @@ -85,7 +91,18 @@ class MainActivity : FlutterActivity() { } else if (call.method == "stopDefaultAlarm") { stopDefaultAlarm() result.success(null) - } else + } else if(call.method == "runtimerNotif") + { + val startTimerIntent = Intent("com.example.ultimate_alarm_clock.START_TIMERNOTIF") + context.sendBroadcast(startTimerIntent) + + }else if(call.method == "clearTimerNotif"){ + val stopTimerIntent = Intent("com.example.ultimate_alarm_clock.STOP_TIMERNOTIF") + context.sendBroadcast(stopTimerIntent) + var notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(1) + } + else { result.notImplemented() } @@ -118,6 +135,11 @@ class MainActivity : FlutterActivity() { } } + + + + + fun bringAppToForeground(context: Context) { val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager? val appTasks = activityManager?.appTasks diff --git a/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerBroadcasts/CancelTimer.kt b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerBroadcasts/CancelTimer.kt new file mode 100644 index 00000000..7bd9ced1 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerBroadcasts/CancelTimer.kt @@ -0,0 +1,22 @@ +package com.example.ultimate_alarm_clock.TimerBroadcasts + +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.example.ultimate_alarm_clock.CommonTimerManager +import com.example.ultimate_alarm_clock.TimerListener + +class CancelTimer : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent?) { + var notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val commonTimer = CommonTimerManager.getCommonTimer(object : TimerListener { + override fun onTick(millisUntilFinished: Long) { + } + override fun onFinish() { + notificationManager.cancel(1) + } + }) + commonTimer.stopTimer() + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerDatabaseHelper.kt b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerDatabaseHelper.kt new file mode 100644 index 00000000..09a2a795 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerDatabaseHelper.kt @@ -0,0 +1,26 @@ +package com.example.ultimate_alarm_clock +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper + +class TimerDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { + + companion object { + private const val DATABASE_VERSION = 1 + private const val DATABASE_NAME = "timer.db" + } + + override fun onCreate(db: SQLiteDatabase) { + db.rawQuery(""" create table timers ( + id integer primary key autoincrement, + startedOn text not null, + timerValue integer not null, + timeElapsed integer not null, + ringtoneName text not null, + timerName text not null, + isPaused integer not null)""",null) + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + } +} diff --git a/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerFunctions.kt b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerFunctions.kt new file mode 100644 index 00000000..d6e9fbdb --- /dev/null +++ b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerFunctions.kt @@ -0,0 +1,75 @@ +package com.example.ultimate_alarm_clock + +import android.content.ContentValues +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import java.text.SimpleDateFormat +import java.util.* + + +fun getLatestTimer(db: SQLiteDatabase): Triple? { + + val cursor = db.rawQuery( + """ + SELECT id, timerValue, timeElapsed, timerName, startedOn + FROM timers + WHERE isPaused = 0 + AND DATETIME(startedOn, '+' || CAST(timerValue / 1000 AS TEXT) || ' seconds') > DATETIME('now') + ORDER BY (timerValue - timeElapsed) ASC + LIMIT 1; + """, null + ) + + return if (cursor.moveToFirst()) { + val timer = TimerModel.fromCursor(cursor) + cursor.close() + val intervalToTimer = isFutureDatetimeWithMillis(timer.startedOn,timer.timerValue.toLong()) + Triple(timer.id, intervalToTimer.toLong(),timer.timerName) + + } else + { + cursor.close() + null + } +} +fun pauseTimer(db: SQLiteDatabase, timerId: Int): Boolean { + val contentValues = ContentValues() + contentValues.put("isPaused", 1) + + val rowsAffected = db.update("timers", contentValues, "id = ?", arrayOf(timerId.toString())) + + return rowsAffected > 0 +} + +fun isFutureDatetimeWithMillis(datetimeString: String, milliseconds: Long): Long { + try { + val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS") + val providedDatetime = dateFormat.parse(datetimeString) + val updatedDatetime = Date(providedDatetime.time + milliseconds) + val currentDatetime = Date() + + return if (updatedDatetime.after(currentDatetime)) { + updatedDatetime.time - currentDatetime.time + } else { + 0 + } + } catch (e: Exception) { + // Handle invalid datetime format (e.g., parsing error) + return 0 + } +} + + + +private data class TimerModel(val id: Int, val timerValue: Int, val timeElapsed : Int, val timerName: String, val startedOn: String) { + companion object { + fun fromCursor(cursor: Cursor): TimerModel { + val id = cursor.getInt(cursor.getColumnIndex("id")) + val timerValue = cursor.getInt(cursor.getColumnIndex("timerValue")) + val timeElapsed = cursor.getInt(cursor.getColumnIndex("timeElapsed")) + val timerName = cursor.getString(cursor.getColumnIndex("timerName")) + val startedOn = cursor.getString(cursor.getColumnIndex("startedOn")) + return TimerModel(id, timerValue, timeElapsed,timerName,startedOn) + } + } +} diff --git a/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerNotification.kt b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerNotification.kt new file mode 100644 index 00000000..28a0338e --- /dev/null +++ b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerNotification.kt @@ -0,0 +1,96 @@ +package com.example.ultimate_alarm_clock +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.core.app.NotificationCompat + +class TimerNotification: BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val timerdbhelper = TimerDatabaseHelper(context) + val timerdb = timerdbhelper.readableDatabase + val time = getLatestTimer(timerdb) + var notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val commonTimer = CommonTimerManager.getCommonTimer(object : TimerListener { + override fun onTick(millisUntilFinished: Long) { + showTimerNotification(millisUntilFinished,"Timer",context) + } + + override fun onFinish() { + notificationManager.cancel(1) + } + }) + + if(intent.action =="com.example.ultimate_alarm_clock.START_TIMERNOTIF" || intent.action == Intent.ACTION_BOOT_COMPLETED ) + { + createNotificationChannel(context) + + + + if (time!=null){ + + // Start or stop the timer based on your requirements + commonTimer.startTimer(time.second) + + } + + } + if(intent.action=="com.example.ultimate_alarm_clock.STOP_TIMERNOTIF"){ + + commonTimer.stopTimer() + + } + + + } +private fun createNotificationChannel(context: Context) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + TimerService.TIMER_CHANNEL_ID, + "Timer Channel", + NotificationManager.IMPORTANCE_DEFAULT + ) + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } + } + + + private fun showTimerNotification(milliseconds: Long,timerName:String,context: Context){ + var notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val deleteIntent = Intent(context,TimerNotification::class.java) + deleteIntent.action = "com.example.ultimate_alarm_clock.STOP_TIMERNOTIF" + val deletePendingIntent = PendingIntent.getBroadcast(context, 5, deleteIntent, + PendingIntent.FLAG_IMMUTABLE) + val notification = NotificationCompat.Builder(context, TimerService.TIMER_CHANNEL_ID) + .setSmallIcon(R.mipmap.launcher_icon) + .setContentText("$timerName") + .setContentText(formatDuration(milliseconds)) + .setOnlyAlertOnce(true) + .setDeleteIntent(deletePendingIntent) + . + build() + notificationManager.notify(1,notification) + } + + private fun formatDuration(milliseconds: Long): String { + val seconds = (milliseconds / 1000) % 60 + val minutes = (milliseconds / (1000 * 60)) % 60 + val hours = (milliseconds / (1000 * 60 * 60)) % 24 + + return if (hours > 0) { + String.format("%02d:%02d:%02d", hours, minutes, seconds) + } else { + String.format("%02d:%02d", minutes, seconds) + } + } + + + + +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerService.kt b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerService.kt new file mode 100644 index 00000000..e5371589 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/ultimate_alarm_clock/TimerService.kt @@ -0,0 +1,7 @@ +package com.example.ultimate_alarm_clock + +class TimerService { + companion object{ + val TIMER_CHANNEL_ID = "Timer Channel" + } +} \ No newline at end of file diff --git a/lib/app/data/models/timer_model.dart b/lib/app/data/models/timer_model.dart index 6b60c0c7..ba72b675 100644 --- a/lib/app/data/models/timer_model.dart +++ b/lib/app/data/models/timer_model.dart @@ -1,16 +1,44 @@ import 'package:isar/isar.dart'; +part 'timer_model.g.dart'; +@collection class TimerModel { - Id isarId = Isar.autoIncrement; - late String timerTime; - late String mainTimerTime; - late int intervalToAlarm; + Id timerId = Isar.autoIncrement; + late String timerName; + late int timerValue; + late String startedOn; + late int timeElapsed; late String ringtoneName; + late int isPaused; - TimerModel({ - required this.intervalToAlarm, - required this.mainTimerTime, - required this.ringtoneName, - required this.timerTime, - }); + TimerModel( + {required this.timerValue, + required this.startedOn, + required this.ringtoneName, + required this.timerName, + this.isPaused = 0, + this.timeElapsed = 0}); + + Map toMap() { + return { + 'id': timerId, + 'startedOn': startedOn, + 'timerValue': timerValue, + 'timeElapsed': timeElapsed, + 'ringtoneName': ringtoneName, + 'timerName': timerName, + 'isPaused': isPaused, + }; + } + + // Extract a TimerModel object from a Map object. + TimerModel.fromMap(Map map) { + timerId = map['id']; + startedOn = map['startedOn']; + timerValue = map['timerValue']; + timeElapsed= map['timeElapsed']; + ringtoneName = map['ringtoneName']; + timerName = map['timerName']; + isPaused = map['isPaused']; + } } diff --git a/lib/app/data/models/timer_model.g.dart b/lib/app/data/models/timer_model.g.dart new file mode 100644 index 00000000..6b68a216 --- /dev/null +++ b/lib/app/data/models/timer_model.g.dart @@ -0,0 +1,1105 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'timer_model.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetTimerModelCollection on Isar { + IsarCollection get timerModels => this.collection(); +} + +const TimerModelSchema = CollectionSchema( + name: r'TimerModel', + id: 1326376837060457485, + properties: { + r'isPaused': PropertySchema( + id: 0, + name: r'isPaused', + type: IsarType.long, + ), + r'ringtoneName': PropertySchema( + id: 1, + name: r'ringtoneName', + type: IsarType.string, + ), + r'startedOn': PropertySchema( + id: 2, + name: r'startedOn', + type: IsarType.string, + ), + r'timeElapsed': PropertySchema( + id: 3, + name: r'timeElapsed', + type: IsarType.long, + ), + r'timerName': PropertySchema( + id: 4, + name: r'timerName', + type: IsarType.string, + ), + r'timerValue': PropertySchema( + id: 5, + name: r'timerValue', + type: IsarType.long, + ) + }, + estimateSize: _timerModelEstimateSize, + serialize: _timerModelSerialize, + deserialize: _timerModelDeserialize, + deserializeProp: _timerModelDeserializeProp, + idName: r'timerId', + indexes: {}, + links: {}, + embeddedSchemas: {}, + getId: _timerModelGetId, + getLinks: _timerModelGetLinks, + attach: _timerModelAttach, + version: '3.1.0+1', +); + +int _timerModelEstimateSize( + TimerModel object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.ringtoneName.length * 3; + bytesCount += 3 + object.startedOn.length * 3; + bytesCount += 3 + object.timerName.length * 3; + return bytesCount; +} + +void _timerModelSerialize( + TimerModel object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeLong(offsets[0], object.isPaused); + writer.writeString(offsets[1], object.ringtoneName); + writer.writeString(offsets[2], object.startedOn); + writer.writeLong(offsets[3], object.timeElapsed); + writer.writeString(offsets[4], object.timerName); + writer.writeLong(offsets[5], object.timerValue); +} + +TimerModel _timerModelDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = TimerModel( + isPaused: reader.readLongOrNull(offsets[0]) ?? 0, + ringtoneName: reader.readString(offsets[1]), + startedOn: reader.readString(offsets[2]), + timeElapsed: reader.readLongOrNull(offsets[3]) ?? 0, + timerName: reader.readString(offsets[4]), + timerValue: reader.readLong(offsets[5]), + ); + object.timerId = id; + return object; +} + +P _timerModelDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readLongOrNull(offset) ?? 0) as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readString(offset)) as P; + case 3: + return (reader.readLongOrNull(offset) ?? 0) as P; + case 4: + return (reader.readString(offset)) as P; + case 5: + return (reader.readLong(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _timerModelGetId(TimerModel object) { + return object.timerId; +} + +List> _timerModelGetLinks(TimerModel object) { + return []; +} + +void _timerModelAttach(IsarCollection col, Id id, TimerModel object) { + object.timerId = id; +} + +extension TimerModelQueryWhereSort + on QueryBuilder { + QueryBuilder anyTimerId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension TimerModelQueryWhere + on QueryBuilder { + QueryBuilder timerIdEqualTo( + Id timerId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: timerId, + upper: timerId, + )); + }); + } + + QueryBuilder timerIdNotEqualTo( + Id timerId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: timerId, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: timerId, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: timerId, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: timerId, includeUpper: false), + ); + } + }); + } + + QueryBuilder timerIdGreaterThan( + Id timerId, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: timerId, includeLower: include), + ); + }); + } + + QueryBuilder timerIdLessThan( + Id timerId, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: timerId, includeUpper: include), + ); + }); + } + + QueryBuilder timerIdBetween( + Id lowerTimerId, + Id upperTimerId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerTimerId, + includeLower: includeLower, + upper: upperTimerId, + includeUpper: includeUpper, + )); + }); + } +} + +extension TimerModelQueryFilter + on QueryBuilder { + QueryBuilder isPausedEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isPaused', + value: value, + )); + }); + } + + QueryBuilder + isPausedGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'isPaused', + value: value, + )); + }); + } + + QueryBuilder isPausedLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'isPaused', + value: value, + )); + }); + } + + QueryBuilder isPausedBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'isPaused', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + ringtoneNameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ringtoneName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ringtoneNameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'ringtoneName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ringtoneNameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'ringtoneName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ringtoneNameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'ringtoneName', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ringtoneNameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'ringtoneName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ringtoneNameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'ringtoneName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ringtoneNameContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'ringtoneName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ringtoneNameMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'ringtoneName', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ringtoneNameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ringtoneName', + value: '', + )); + }); + } + + QueryBuilder + ringtoneNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'ringtoneName', + value: '', + )); + }); + } + + QueryBuilder startedOnEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'startedOn', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + startedOnGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'startedOn', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder startedOnLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'startedOn', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder startedOnBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'startedOn', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + startedOnStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'startedOn', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder startedOnEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'startedOn', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder startedOnContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'startedOn', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder startedOnMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'startedOn', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + startedOnIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'startedOn', + value: '', + )); + }); + } + + QueryBuilder + startedOnIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'startedOn', + value: '', + )); + }); + } + + QueryBuilder + timeElapsedEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'timeElapsed', + value: value, + )); + }); + } + + QueryBuilder + timeElapsedGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'timeElapsed', + value: value, + )); + }); + } + + QueryBuilder + timeElapsedLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'timeElapsed', + value: value, + )); + }); + } + + QueryBuilder + timeElapsedBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'timeElapsed', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder timerIdEqualTo( + Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'timerId', + value: value, + )); + }); + } + + QueryBuilder + timerIdGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'timerId', + value: value, + )); + }); + } + + QueryBuilder timerIdLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'timerId', + value: value, + )); + }); + } + + QueryBuilder timerIdBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'timerId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder timerNameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'timerName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + timerNameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'timerName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder timerNameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'timerName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder timerNameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'timerName', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + timerNameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'timerName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder timerNameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'timerName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder timerNameContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'timerName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder timerNameMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'timerName', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + timerNameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'timerName', + value: '', + )); + }); + } + + QueryBuilder + timerNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'timerName', + value: '', + )); + }); + } + + QueryBuilder timerValueEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'timerValue', + value: value, + )); + }); + } + + QueryBuilder + timerValueGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'timerValue', + value: value, + )); + }); + } + + QueryBuilder + timerValueLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'timerValue', + value: value, + )); + }); + } + + QueryBuilder timerValueBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'timerValue', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } +} + +extension TimerModelQueryObject + on QueryBuilder {} + +extension TimerModelQueryLinks + on QueryBuilder {} + +extension TimerModelQuerySortBy + on QueryBuilder { + QueryBuilder sortByIsPaused() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPaused', Sort.asc); + }); + } + + QueryBuilder sortByIsPausedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPaused', Sort.desc); + }); + } + + QueryBuilder sortByRingtoneName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ringtoneName', Sort.asc); + }); + } + + QueryBuilder sortByRingtoneNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ringtoneName', Sort.desc); + }); + } + + QueryBuilder sortByStartedOn() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startedOn', Sort.asc); + }); + } + + QueryBuilder sortByStartedOnDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startedOn', Sort.desc); + }); + } + + QueryBuilder sortByTimeElapsed() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timeElapsed', Sort.asc); + }); + } + + QueryBuilder sortByTimeElapsedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timeElapsed', Sort.desc); + }); + } + + QueryBuilder sortByTimerName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timerName', Sort.asc); + }); + } + + QueryBuilder sortByTimerNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timerName', Sort.desc); + }); + } + + QueryBuilder sortByTimerValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timerValue', Sort.asc); + }); + } + + QueryBuilder sortByTimerValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timerValue', Sort.desc); + }); + } +} + +extension TimerModelQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByIsPaused() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPaused', Sort.asc); + }); + } + + QueryBuilder thenByIsPausedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPaused', Sort.desc); + }); + } + + QueryBuilder thenByRingtoneName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ringtoneName', Sort.asc); + }); + } + + QueryBuilder thenByRingtoneNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ringtoneName', Sort.desc); + }); + } + + QueryBuilder thenByStartedOn() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startedOn', Sort.asc); + }); + } + + QueryBuilder thenByStartedOnDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startedOn', Sort.desc); + }); + } + + QueryBuilder thenByTimeElapsed() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timeElapsed', Sort.asc); + }); + } + + QueryBuilder thenByTimeElapsedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timeElapsed', Sort.desc); + }); + } + + QueryBuilder thenByTimerId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timerId', Sort.asc); + }); + } + + QueryBuilder thenByTimerIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timerId', Sort.desc); + }); + } + + QueryBuilder thenByTimerName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timerName', Sort.asc); + }); + } + + QueryBuilder thenByTimerNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timerName', Sort.desc); + }); + } + + QueryBuilder thenByTimerValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timerValue', Sort.asc); + }); + } + + QueryBuilder thenByTimerValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timerValue', Sort.desc); + }); + } +} + +extension TimerModelQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByIsPaused() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isPaused'); + }); + } + + QueryBuilder distinctByRingtoneName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'ringtoneName', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByStartedOn( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'startedOn', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByTimeElapsed() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'timeElapsed'); + }); + } + + QueryBuilder distinctByTimerName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'timerName', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByTimerValue() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'timerValue'); + }); + } +} + +extension TimerModelQueryProperty + on QueryBuilder { + QueryBuilder timerIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'timerId'); + }); + } + + QueryBuilder isPausedProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isPaused'); + }); + } + + QueryBuilder ringtoneNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'ringtoneName'); + }); + } + + QueryBuilder startedOnProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'startedOn'); + }); + } + + QueryBuilder timeElapsedProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'timeElapsed'); + }); + } + + QueryBuilder timerNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'timerName'); + }); + } + + QueryBuilder timerValueProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'timerValue'); + }); + } +} diff --git a/lib/app/data/providers/isar_provider.dart b/lib/app/data/providers/isar_provider.dart index f741cf38..9125e158 100644 --- a/lib/app/data/providers/isar_provider.dart +++ b/lib/app/data/providers/isar_provider.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:isar/isar.dart'; import 'package:path_provider/path_provider.dart'; @@ -19,16 +21,37 @@ class IsarDb { db = openDB(); } - Future getSQLiteDatabase() async { + Future getAlarmSQLiteDatabase() async { Database? db; final dir = await getDatabasesPath(); - final dbPath = '${dir}/alarms.db'; - print(dir); + final dbPath = '$dir/alarms.db'; db = await openDatabase(dbPath, version: 1, onCreate: _onCreate); return db; } + Future getTimerSQLiteDatabase() async { + Database? db; + final dir = await getDatabasesPath(); + db = await openDatabase( + '$dir/timer.db', + version: 1, + onCreate: (Database db, int version) async { + await db.execute(''' + create table timers ( + id integer primary key autoincrement, + startedOn text not null, + timerValue integer not null, + timeElapsed integer not null, + ringtoneName text not null, + timerName text not null, + isPaused integer not null) + '''); + }, + ); + return db; + } + void _onCreate(Database db, int version) async { // Create tables for alarms and ringtones (modify column types as needed) await db.execute(''' @@ -89,7 +112,7 @@ class IsarDb { final dir = await getApplicationDocumentsDirectory(); if (Isar.instanceNames.isEmpty) { return await Isar.open( - [AlarmModelSchema, RingtoneModelSchema], + [AlarmModelSchema, RingtoneModelSchema, TimerModelSchema], directory: dir.path, inspector: true, ); @@ -99,15 +122,13 @@ class IsarDb { static Future addAlarm(AlarmModel alarmRecord) async { final isarProvider = IsarDb(); - final sql = await IsarDb().getSQLiteDatabase(); + final sql = await IsarDb().getAlarmSQLiteDatabase(); final db = await isarProvider.db; await db.writeTxn(() async { await db.alarmModels.put(alarmRecord); }); final sqlmap = alarmRecord.toSQFliteMap(); - await sql! - .insert('alarms', sqlmap) - .then((value) => print("insert success")); + await sql!.insert('alarms', sqlmap); return alarmRecord; } @@ -217,7 +238,7 @@ class IsarDb { static Future updateAlarm(AlarmModel alarmRecord) async { final isarProvider = IsarDb(); - final sql = await IsarDb().getSQLiteDatabase(); + final sql = await IsarDb().getAlarmSQLiteDatabase(); final db = await isarProvider.db; await db.writeTxn(() async { await db.alarmModels.put(alarmRecord); @@ -243,21 +264,145 @@ class IsarDb { yield* db.alarmModels.where().watch(fireImmediately: true); } catch (e) { debugPrint(e.toString()); - throw e; + rethrow; } } static Future deleteAlarm(int id) async { final isarProvider = IsarDb(); final db = await isarProvider.db; - final sql = await IsarDb().getSQLiteDatabase(); - final tobedeleted = await db.alarmModels.get(id); + final sql = await IsarDb().getAlarmSQLiteDatabase(); + final tobedeleted = await db.alarmModels.get(id); await db.writeTxn(() async { await db.alarmModels.delete(id); }); - sql!.delete('alarms', where: 'alarmID = ?', whereArgs: [tobedeleted!.alarmID]); + await sql!.delete( + 'alarms', + where: 'alarmID = ?', + whereArgs: [tobedeleted!.alarmID], + ); + } + + // Timer Functions + + static Future insertTimer(TimerModel timer) async { + final isarProvider = IsarDb(); + final sql = await IsarDb().getTimerSQLiteDatabase(); + final db = await isarProvider.db; + await db.writeTxn(() async { + await db.timerModels.put(timer); + }); + + await sql!.insert('timers', timer.toMap()); + return timer; + } + + static Future updateTimer(TimerModel timer) async { + final sql = await IsarDb().getTimerSQLiteDatabase(); + return await sql!.update( + 'timers', + timer.toMap(), + where: 'id = ?', + whereArgs: [timer.timerId], + ); + } + + static Future updateTimerName(int id, String newTimerName) async { + final sql = await IsarDb().getTimerSQLiteDatabase(); + return await sql!.update( + 'timers', + {'timerName': newTimerName}, + where: 'id = ?', + whereArgs: [id], + ); + } + + static Future deleteTimer(int id) async { + final isarProvider = IsarDb(); + final sql = await IsarDb().getTimerSQLiteDatabase(); + final db = await isarProvider.db; + await db.writeTxn(() async { + await db.timerModels.delete(id); + }); + return await sql!.delete('timers', where: 'id = ?', whereArgs: [id]); + } + + static Future> getAllTimers() async { + final sql = await IsarDb().getTimerSQLiteDatabase(); + List> maps = await sql!.query('timers', columns: [ + 'id', + 'startedOn', + 'timerValue', + 'timeElapsed', + 'ringtoneName', + 'timerName', + 'isPaused', + ]); + if (maps.length > 0) { + return maps.map((timer) => TimerModel.fromMap(timer)).toList(); + } + return []; + } + + static Future updateTimerTick(TimerModel timer) async { + final isarProvider = IsarDb(); + final db = await isarProvider.db; + await db.writeTxn(() async { + await db.timerModels.put(timer); + }); + final sql = await IsarDb().getTimerSQLiteDatabase(); + await sql!.update( + 'timers', + {'timeElapsed': timer.timeElapsed}, + where: 'id = ?', + whereArgs: [timer.timerId], + ); + } + + static Stream> getTimers() { + final isarProvider = IsarDb(); + final controller = StreamController>.broadcast(); + + isarProvider.db.then((db) { + final stream = db.timerModels.where().watch(fireImmediately: true); + stream.listen( + (data) => controller.add(data), + onError: (error) => controller.addError(error), + onDone: () => controller.close(), + ); + }).catchError((error) { + debugPrint(error.toString()); + controller.addError(error); + }); + + return controller.stream; + } + + static Future updateTimerPauseStatus(TimerModel timer) async { + final isarProvider = IsarDb(); + final db = await isarProvider.db; + await db.writeTxn(() async { + await db.timerModels.put(timer); + }); + final sql = await IsarDb().getTimerSQLiteDatabase(); + await sql!.update( + 'timers', + {'isPaused': timer.isPaused}, + where: 'id = ?', + whereArgs: [timer.timerId], + ); + } + + static Future getNumberOfTimers() async { + final sql = await IsarDb().getTimerSQLiteDatabase(); + List> x = + await sql!.rawQuery('SELECT COUNT (*) from timers'); + sql.close(); + int result = Sqflite.firstIntValue(x)!; + return result; } +// Ringtone functions static Future addCustomRingtone( RingtoneModel customRingtone, ) async { diff --git a/lib/app/modules/bottomNavigationBar/controllers/bottom_navigation_bar_controller.dart b/lib/app/modules/bottomNavigationBar/controllers/bottom_navigation_bar_controller.dart index 4a8815db..ce4b548e 100644 --- a/lib/app/modules/bottomNavigationBar/controllers/bottom_navigation_bar_controller.dart +++ b/lib/app/modules/bottomNavigationBar/controllers/bottom_navigation_bar_controller.dart @@ -3,19 +3,15 @@ import 'package:get/get.dart'; import 'package:ultimate_alarm_clock/app/data/providers/secure_storage_provider.dart'; import 'package:ultimate_alarm_clock/app/modules/home/views/home_view.dart'; import 'package:ultimate_alarm_clock/app/modules/stopwatch/views/stopwatch_view.dart'; -import 'package:ultimate_alarm_clock/app/modules/timer/controllers/timer_controller.dart'; import 'package:ultimate_alarm_clock/app/modules/timer/views/timer_view.dart'; class BottomNavigationBarController extends GetxController with WidgetsBindingObserver { RxInt activeTabIndex = 0.obs; - RxBool isTimerRunning = false.obs; RxBool hasloaded = false.obs; final _secureStorageProvider = SecureStorageProvider(); - TimerController timerController = Get.find(); - List pages = [ HomeView(), StopwatchView(), @@ -51,9 +47,5 @@ class BottomNavigationBarController extends GetxController activeTabIndex.value = index; _saveState(); - if (index == 0 && - (timerController.isTimerRunning.value || isTimerRunning.value)) { - timerController.saveTimerStateToStorage(); - } } } diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart index 937850ec..27a27848 100644 --- a/lib/app/modules/home/controllers/home_controller.dart +++ b/lib/app/modules/home/controllers/home_controller.dart @@ -25,7 +25,7 @@ class Pair { Pair(this.first, this.second); } -class HomeController extends GetxController { +class HomeController extends GetxController { MethodChannel alarmChannel = const MethodChannel('ulticlock'); Stream? firestoreStreamAlarms; diff --git a/lib/app/modules/timer/controllers/timer_controller.dart b/lib/app/modules/timer/controllers/timer_controller.dart index c81392c0..17445a40 100644 --- a/lib/app/modules/timer/controllers/timer_controller.dart +++ b/lib/app/modules/timer/controllers/timer_controller.dart @@ -1,211 +1,141 @@ import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; -import 'package:ultimate_alarm_clock/app/data/models/alarm_model.dart'; import 'package:ultimate_alarm_clock/app/data/models/timer_model.dart'; import 'package:ultimate_alarm_clock/app/data/providers/isar_provider.dart'; -import 'package:ultimate_alarm_clock/app/data/providers/secure_storage_provider.dart'; import 'package:ultimate_alarm_clock/app/utils/utils.dart'; -import 'package:uuid/uuid.dart'; -class TimerController extends GetxController with WidgetsBindingObserver { +class TimerController extends FullLifeCycleController with FullLifeCycleMixin { MethodChannel timerChannel = const MethodChannel('timer'); - final initialTime = DateTime(0, 0, 0, 0, 1, 0).obs; final remainingTime = const Duration(hours: 0, minutes: 0, seconds: 0).obs; - final currentTime = const Duration(hours: 0, minutes: 0, seconds: 0).obs; RxInt startTime = 0.obs; RxBool isTimerPaused = false.obs; RxBool isTimerRunning = false.obs; - Rx countdownTimer = Rx(null); - TimerModel timerRecord = Utils.genFakeTimerModel(); + RxBool isbottom = false.obs; + Stream? isarTimers; + ScrollController scrollController = ScrollController(); + RxList timers = [].obs; + RxList isRinging = [].obs; + + + getFakeTimerModel() async { + TimerModel fakeTimer = await Utils.genFakeTimerModel(); + return fakeTimer; + } + + updateTimerInfo() async { + timerList.value = await IsarDb.getAllTimers(); + } + late int currentTimerIsarId; var hours = 0.obs, minutes = 1.obs, seconds = 0.obs; - final _secureStorageProvider = SecureStorageProvider(); - String strDigits(int n) => n.toString().padLeft(2, '0'); + final RxList timerList = [].obs; + @override - void onInit() { + Future onInit() async { super.onInit(); WidgetsBinding.instance.addObserver(this); - loadTimerStateFromStorage(); + isarTimers = IsarDb.getTimers(); + updateTimerInfo(); + scrollController.addListener(() { + if (scrollController.offset < scrollController.position.maxScrollExtent && + !scrollController.position.outOfRange) { + isbottom.value = true; + } else { + isbottom.value = false; + } + }); } @override - void onClose() { - WidgetsBinding.instance.removeObserver(this); + Future onClose() async { super.onClose(); } - void saveTimerStateToStorage() async { - await _secureStorageProvider.writeRemainingTimeInSeconds( - remainingTimeInSeconds: remainingTime.value.inSeconds, - ); - await _secureStorageProvider.writeStartTime( - startTime: startTime.value, - ); - await _secureStorageProvider.writeIsTimerRunning( - isTimerRunning: isTimerRunning.value, - ); - await _secureStorageProvider.writeIsTimerPaused( - isTimerPaused: isTimerPaused.value, - ); + void startRinger(int id) async { + try { + isRinging.value.add(id); + print(isRinging.value); + if (isRinging.value.length == 1) { + await timerChannel.invokeMethod('playDefaultAlarm'); + } + } on PlatformException catch (e) { + print('Failed to schedule alarm: ${e.message}'); + } } - void loadTimerStateFromStorage() async { - final storedRemainingTimeInSeconds = - await _secureStorageProvider.readRemainingTimeInSeconds(); - final storedStartTime = await _secureStorageProvider.readStartTime(); - isTimerRunning.value = await _secureStorageProvider.readIsTimerRunning(); - isTimerPaused.value = await _secureStorageProvider.readIsTimerPaused(); - - if (storedRemainingTimeInSeconds != -1 && storedStartTime != -1) { - if (!isTimerPaused.value) { - final elapsedMilliseconds = - DateTime.now().millisecondsSinceEpoch - storedStartTime; - final elapsedSeconds = (elapsedMilliseconds / 1000).round(); - final updatedRemainingTimeInSeconds = - storedRemainingTimeInSeconds - elapsedSeconds; - - if (updatedRemainingTimeInSeconds > 0) { - // Update remaining time and start timer from the correct point - int hours = updatedRemainingTimeInSeconds ~/ - 3600; // Calculate the number of hours - int remainingSeconds = updatedRemainingTimeInSeconds % - 3600; // Calculate the remaining seconds - int minutes = - remainingSeconds ~/ 60; // Calculate the number of minutes - int seconds = - remainingSeconds % 60; // Calculate the number of seconds - remainingTime.value = Duration( - hours: hours, - minutes: minutes, - seconds: seconds, - ); - startTimer(); - } else { - stopTimer(); - } - } else { - int hours = storedRemainingTimeInSeconds ~/ - 3600; // Calculate the number of hours - int remainingSeconds = storedRemainingTimeInSeconds % - 3600; // Calculate the remaining seconds - int minutes = remainingSeconds ~/ 60; // Calculate the number of minutes - int seconds = remainingSeconds % 60; // Calculate the number of seconds - remainingTime.value = Duration( - hours: hours, - minutes: minutes, - seconds: seconds, - ); + void stopRinger(int id) async { + try { + isRinging.value.remove(id); + print(isRinging.value); + if (isRinging.value.length == 0) { + await timerChannel.invokeMethod('stopDefaultAlarm'); } + } on PlatformException catch (e) { + print('Failed to schedule alarm: ${e.message}'); } } void createTimer() async { - timerRecord.timerTime = Utils.formatDateTimeToHHMMSS( - DateTime.now().add(remainingTime.value), - ); - timerRecord.mainTimerTime = Utils.formatDateTimeToHHMMSS( - DateTime.now().add(remainingTime.value), - ); - timerRecord.intervalToAlarm = Utils.getMillisecondsToAlarm( + TimerModel timerRecord = await getFakeTimerModel(); + + timerRecord.startedOn = DateTime.now().toString(); + ; + timerRecord.timerValue = Utils.getMillisecondsToAlarm( DateTime.now(), DateTime.now().add(remainingTime.value), ); timerRecord.ringtoneName = 'Default'; - scheduleTimer(timerRecord); - - await _secureStorageProvider.writeTimerId(timerId: timerRecord.isarId); - } - - void startTimer() async { - if (remainingTime.value.inSeconds > 0) { - final now = DateTime.now(); - startTime.value = now.millisecondsSinceEpoch; - isTimerRunning.value = true; - isTimerPaused.value = false; + timerRecord.timerName = + '${Utils.formatMilliseconds(timerRecord.timerValue)} Timer'; - saveTimerStateToStorage(); - - countdownTimer.value = Timer.periodic( - const Duration(seconds: 1), - (_) => setCountDown(), - ); - } + IsarDb.insertTimer(timerRecord).then((value) async { + updateTimerInfo(); + }); + Get.back(); } - scheduleTimer(TimerModel timerRecord) async { - DateTime? timerDateTime = Utils.stringToDateTime(timerRecord.timerTime); - if (timerDateTime != null) { - await timerChannel.invokeMethod('cancelTimer'); - int intervaltoTimer = Utils.getMillisecondsToTimer( - DateTime.now(), - timerDateTime, - ); - try { - await timerChannel - .invokeMethod('scheduleTimer', {'milliSeconds': intervaltoTimer}); - } on PlatformException catch (e) { - print("Failed to schedule alarm: ${e.message}"); - } - } + deleteTimer(int id) async { + await IsarDb.deleteTimer(id).then((value) => updateTimerInfo()); + updateTimerInfo(); } cancelTimer() async { await timerChannel.invokeMethod('cancelTimer'); } - void stopTimer() async { - countdownTimer.value?.cancel(); - isTimerPaused.value = false; - isTimerRunning.value = false; - initialTime.value = DateTime(0, 0, 0, 0, 1, 0); - remainingTime.value = Duration( - hours: initialTime.value.hour, - minutes: initialTime.value.minute, - seconds: initialTime.value.second, - ); - currentTime.value = const Duration(hours: 0, minutes: 0, seconds: 0); - startTime.value = 0; - await _secureStorageProvider.removeRemainingTimeInSeconds(); - await _secureStorageProvider.removeStartTime(); - await _secureStorageProvider.writeIsTimerRunning(isTimerRunning: false); - await _secureStorageProvider.writeIsTimerPaused(isTimerPaused: false); - } - - void pauseTimer() async { - countdownTimer.value?.cancel(); - isTimerPaused.value = true; + @override + void onDetached() {} - saveTimerStateToStorage(); - await cancelTimer(); - int timerId = await SecureStorageProvider().readTimerId(); - await IsarDb.deleteAlarm(timerId); + @override + Future onHidden() async { + try { + await timerChannel.invokeMethod('runtimerNotif'); + Get.back(); + } on PlatformException catch (e) { + print('Failed to schedule alarm: ${e.message}'); + Get.back(); + } } - void resumeTimer() async { - if (isTimerPaused.value) { - countdownTimer.value = Timer.periodic( - const Duration(seconds: 1), - (_) => setCountDown(), - ); - isTimerPaused.value = false; + @override + onInactive() {} - createTimer(); - } - } + @override + void onPaused() {} - void setCountDown() { - const reduceSecondsBy = 1; - final seconds = remainingTime.value.inSeconds - reduceSecondsBy; - if (seconds < 0) { - stopTimer(); - } else { - remainingTime.value = Duration(seconds: seconds); + @override + onResumed() async { + try { + await timerChannel.invokeMethod('clearTimerNotif'); + Get.back(); + } on PlatformException catch (e) { + print('Failed to schedule alarm: ${e.message}'); + Get.back(); } } } diff --git a/lib/app/modules/timer/views/timer_animation.dart b/lib/app/modules/timer/views/timer_animation.dart new file mode 100644 index 00000000..ca1001c3 --- /dev/null +++ b/lib/app/modules/timer/views/timer_animation.dart @@ -0,0 +1,297 @@ +import 'dart:async'; +import 'dart:ui'; +import 'dart:math' as math; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ultimate_alarm_clock/app/data/models/timer_model.dart'; +import 'package:ultimate_alarm_clock/app/data/providers/isar_provider.dart'; +import 'package:ultimate_alarm_clock/app/modules/timer/controllers/timer_controller.dart'; +import 'package:ultimate_alarm_clock/app/utils/constants.dart'; + +import '../../../utils/utils.dart'; +import '../../settings/controllers/theme_controller.dart'; + +class TimerAnimatedCard extends StatefulWidget { + final TimerModel timer; + final int index; + + const TimerAnimatedCard({ + super.key, + required this.index, + required this.timer, + }); + @override + _TimerAnimatedCardState createState() => _TimerAnimatedCardState(); +} + +class _TimerAnimatedCardState extends State + with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { + TimerController controller = Get.find(); + ThemeController themeController = Get.find(); + + Timer? _timerCounter; + void startTimer() { + _timerCounter = Timer.periodic(Duration(seconds: 1), (timer) { + print("${widget.timer.timerName}"); + if (widget.timer.timeElapsed < widget.timer.timerValue) { + setState(() { + widget.timer.timeElapsed += 1000; + IsarDb.updateTimerTick(widget.timer); + }); + } else { + stopTimer(); + controller.startRinger(widget.timer.timerId); + } + }); + } + + void stopTimer() { + _timerCounter!.cancel(); + } + + @override + void initState() { + super.initState(); + if (Utils.getDifferenceMillisFromNow( + widget.timer.startedOn, widget.timer.timerValue) <= + 0 && + widget.timer.isPaused == 0) { + widget.timer.isPaused = 1; + widget.timer.timeElapsed = 0; + IsarDb.updateTimerPauseStatus(widget.timer); + } else if (Utils.getDifferenceMillisFromNow( + widget.timer.startedOn, widget.timer.timerValue) < + widget.timer.timerValue && + widget.timer.isPaused == 0) { + widget.timer.timeElapsed =widget.timer.timerValue-Utils.getDifferenceMillisFromNow( + widget.timer.startedOn, widget.timer.timerValue); + IsarDb.updateTimerPauseStatus(widget.timer); + } + if (widget.timer.isPaused == 0) { + startTimer(); + } + } + + @override + void dispose() { + _timerCounter!.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10.0, + ), + child: Container( + height: context.height / 3.3, + width: context.width, + child: Card( + margin: const EdgeInsets.all(5), + color: widget.timer.timeElapsed < widget.timer.timerValue + ? themeController.isLightMode.value + ? kLightSecondaryBackgroundColor + : ksecondaryBackgroundColor + : themeController.isLightMode.value + ? kLightSecondaryColor + : ksecondaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 18, + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(18), + child: Stack( + children: [ + AnimatedContainer( + decoration: BoxDecoration( + color: kprimaryDisabledTextColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(18)), + duration: Duration(milliseconds: 1000), + height: context.height / 3.3, + width: context.width * + ((widget.timer.timeElapsed) / + (widget.timer.timerValue)), + ), + Center( + child: Padding( + padding: const EdgeInsets.only( + left: 25.0, + right: 20.0, + top: 20.0, + bottom: 20.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + flex: 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.timer.timerName, + overflow: TextOverflow.ellipsis, + // Set overflow property here + style: Theme.of( + context, + ).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w500, + color: kprimaryColor, + fontSize: 18), + ), + Spacer(), + Padding( + padding: const EdgeInsets.only(right: 16.0), + child: InkWell( + onTap: () { + setState(() { + if (_timerCounter != null && + widget.timer.isPaused == 0) { + stopTimer(); + } + widget.timer.timeElapsed = 0; + IsarDb.updateTimerTick(widget.timer); + if (_timerCounter != null && + widget.timer.isPaused == 0) { + widget.timer.startedOn = + DateTime.now().toString(); + IsarDb.updateTimerTick(widget.timer) + .then((value) => startTimer()); + } + }); + }, + child: Container( + decoration: BoxDecoration( + color: kprimaryBackgroundColor, + borderRadius: + BorderRadius.circular(20)), + child: const Padding( + padding: EdgeInsets.all(4.0), + child: Icon( + Icons.refresh, + size: 18, + ), + ), + ), + ), + ), + InkWell( + onTap: () { + controller + .stopRinger(widget.timer.timerId); + controller + .deleteTimer(widget.timer.timerId); + }, + child: Container( + decoration: BoxDecoration( + color: kprimaryBackgroundColor, + borderRadius: + BorderRadius.circular(20)), + child: const Padding( + padding: EdgeInsets.all(4.0), + child: Icon( + Icons.close, + size: 18, + ), + ), + ), + ) + ], + ), + Padding( + padding: + const EdgeInsets.symmetric(vertical: 20), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + AnimatedContainer( + duration: Duration(seconds: 1), + child: Text( + "${Utils.formatMilliseconds(widget.timer.timerValue - widget.timer.timeElapsed)}", + style: Theme.of( + context, + ).textTheme.displayLarge!.copyWith( + color: themeController + .isLightMode.value + ? kLightPrimaryTextColor + : kprimaryTextColor, + fontSize: 44, + ), + ), + ), + Padding( + padding: EdgeInsets.all(20), + child: GestureDetector( + onTap: () { + setState(() { + widget.timer.isPaused == 0 + ? stopTimer() + : startTimer(); + widget.timer.isPaused = + widget.timer.isPaused == 0 + ? 1 + : 0; + IsarDb.updateTimerPauseStatus( + widget.timer); + }); + if (widget.timer.timeElapsed >= + widget.timer.timerValue) { + controller.stopRinger( + widget.timer.timerId); + setState(() { + widget.timer.timeElapsed = 0; + IsarDb.updateTimerTick( + widget.timer) + .then((value) => IsarDb + .updateTimerPauseStatus( + widget.timer)); + widget.timer.isPaused = 1; + }); + } + }, + child: Container( + decoration: BoxDecoration( + color: kprimaryColor, + borderRadius: + BorderRadius.circular(80)), + width: 80, + height: 80, + child: Icon( + widget.timer.isPaused == 0 + ? Icons.pause + : Icons.play_arrow, + size: 30, + color: ksecondaryBackgroundColor, + ), + ), + )) + ], + ), + ), + ], + ), + ), + ], + ), + ), + ) + ], + ), + ), + ), + ), + ); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/lib/app/modules/timer/views/timer_view.dart b/lib/app/modules/timer/views/timer_view.dart index 049f95b7..3132dd95 100644 --- a/lib/app/modules/timer/views/timer_view.dart +++ b/lib/app/modules/timer/views/timer_view.dart @@ -1,34 +1,50 @@ import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; +import 'package:get_storage/get_storage.dart'; import 'package:numberpicker/numberpicker.dart'; import 'package:ultimate_alarm_clock/app/data/providers/isar_provider.dart'; -import 'package:ultimate_alarm_clock/app/data/providers/secure_storage_provider.dart'; import 'package:ultimate_alarm_clock/app/modules/addOrUpdateAlarm/controllers/input_time_controller.dart'; import 'package:ultimate_alarm_clock/app/modules/settings/controllers/theme_controller.dart'; import 'package:ultimate_alarm_clock/app/modules/timer/controllers/timer_controller.dart'; -import 'package:ultimate_alarm_clock/app/routes/app_pages.dart'; +import 'package:ultimate_alarm_clock/app/modules/timer/views/timer_animation.dart'; import 'package:ultimate_alarm_clock/app/utils/constants.dart'; import 'package:ultimate_alarm_clock/app/utils/end_drawer.dart'; import 'package:ultimate_alarm_clock/app/utils/utils.dart'; +import 'dart:math' as math; + +import '../../../data/models/timer_model.dart'; class TimerView extends GetView { TimerView({Key? key}) : super(key: key); - ThemeController themeController = Get.find(); - InputTimeController inputTimeController = Get.put(InputTimeController()); - + final ThemeController themeController = Get.find(); + final InputTimeController inputTimeController = + Get.put(InputTimeController()); + var width = Get.width; + var height = Get.height; @override Widget build(BuildContext context) { - var width = Get.width; - var height = Get.height; return Scaffold( + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, appBar: PreferredSize( - preferredSize: Size.fromHeight(height / 7.9), + preferredSize: Size.fromHeight(height / 8.9), child: AppBar( toolbarHeight: height / 7.9, elevation: 0.0, - centerTitle: true, + title: Text( + 'Timer', + style: Theme.of(context).textTheme.displaySmall!.copyWith( + color: themeController.isLightMode.value + ? kLightPrimaryTextColor.withOpacity( + 0.75, + ) + : kprimaryTextColor.withOpacity( + 0.75, + ), + fontSize: 26, + ), + ), actions: [ LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { @@ -51,621 +67,695 @@ class TimerView extends GetView { ], ), ), - body: Obx( - () => controller.isTimerRunning.value - ? Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Center( - child: Obx( - () { - final hours = controller.strDigits( - controller.remainingTime.value.inHours.remainder(24), - ); - final minutes = controller.strDigits( - controller.remainingTime.value.inMinutes - .remainder(60), - ); - final seconds = controller.strDigits( - controller.remainingTime.value.inSeconds - .remainder(60), - ); - return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Center( - child: Text( - hours, - style: const TextStyle( - fontSize: 50.0, - fontWeight: FontWeight.bold, - ), - ), + body: Obx(() => controller.timerList.value.length == 0 + ? addATimerSpace(context) + : StreamBuilder( + stream: IsarDb.getTimers(), + builder: (context, snapshot) { + if (!snapshot.hasData && snapshot.data != []) { + return const Center( + child: CircularProgressIndicator.adaptive( + backgroundColor: Colors.transparent, + valueColor: AlwaysStoppedAnimation( + kprimaryColor, + ), + ), + ); + } else { + // list of pause values of timers + List? listOfTimers = snapshot.data; + return ListView.builder( + controller: controller.scrollController, + shrinkWrap: true, + scrollDirection: Axis.vertical, + itemCount: snapshot.data!.length, + itemBuilder: (BuildContext context, int index) { + return Column( + children: [ + TimerAnimatedCard( + key: ValueKey(listOfTimers![index].timerId), + index: index, + timer: listOfTimers![index], + ), + if (index == snapshot.data!.length - 1) + Padding( + padding: const EdgeInsets.all(8.0), + child: addATimerSpace(context), + ) + ], + ); + }, + ); + } + }, + )), + floatingActionButton: Obx( + () => Visibility( + visible: controller.isbottom.value, + child: Container( + height: 85, + child: FittedBox( + child: FloatingActionButton( + onPressed: () { + Utils.hapticFeedback(); + TimerSelector(context); + }, + backgroundColor: kprimaryColor, + child: const Icon( + Icons.add_alarm, + color: ksecondaryBackgroundColor, + size: 26, + ), + ), + ), + ), + ), + ), + endDrawer: buildEndDrawer(context), + ); + } + + Widget addATimerSpace(BuildContext context) { + return InkWell( + onTap: () { + Utils.hapticFeedback(); + TimerSelector(context); + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + Icons.add_alarm_outlined, + color: themeController.isLightMode.value + ? kLightPrimaryTextColor.withOpacity( + 0.75, + ) + : kprimaryTextColor.withOpacity( + 0.75, + ), + size: 30, + ), + ), + ), + Text( + 'Tap here to add a timer', + style: Theme.of(context).textTheme.displaySmall!.copyWith( + fontWeight: FontWeight.bold, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), + ), + ], + ), + ); + } + + void TimerSelector(BuildContext context) { + Get.defaultDialog( + title: '', + titlePadding: const EdgeInsets.all(0), + backgroundColor: kprimaryBackgroundColor, + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(20.0, 0, 0, 20), + child: Row( + children: [ + const Icon( + Icons.timer, + size: 20, + ), + Text( + ' Add timer', + style: Theme.of(context).textTheme.displayMedium!.copyWith( + color: themeController.isLightMode.value + ? kLightPrimaryTextColor.withOpacity( + 0.5, + ) + : kprimaryTextColor.withOpacity( + 0.5, ), + fontSize: 15, + ), + ), + Spacer(), + Padding( + padding: const EdgeInsets.only(right: 20.0), + child: GestureDetector( + onTap: () { + inputTimeController.changeTimePickerTimer(); + }, + child: Icon( + Icons.keyboard, + color: themeController.isLightMode.value + ? kLightPrimaryTextColor.withOpacity( + 0.5, + ) + : kprimaryTextColor.withOpacity( + 0.5, ), - const Text( - ':', - style: TextStyle( - fontSize: 50.0, - fontWeight: FontWeight.bold, + size: 20, + ), + ), + ), + ], + ), + ), + InkWell( + onTap: () { + Utils.hapticFeedback(); + }, + child: Obx( + () => Container( + color: themeController.isLightMode.value + ? kLightPrimaryBackgroundColor + : kprimaryBackgroundColor, + width: width, + child: inputTimeController.isTimePickerTimer.value + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Hours', + style: Theme.of(context) + .textTheme + .displaySmall! + .copyWith( + fontWeight: FontWeight.bold, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), ), - ), - Expanded( - child: Center( - child: Text( - minutes, - style: const TextStyle( - fontSize: 50.0, - fontWeight: FontWeight.bold, + SizedBox( + height: height * 0.008, + ), + NumberPicker( + minValue: 0, + maxValue: 99, + value: controller.hours.value, + onChanged: (value) { + Utils.hapticFeedback(); + controller.hours.value = value; + inputTimeController.setTextFieldTimerTime(); + }, + infiniteLoop: true, + itemWidth: width * 0.17, + zeroPad: true, + selectedTextStyle: Theme.of(context) + .textTheme + .displayLarge! + .copyWith( + fontSize: 30, + fontWeight: FontWeight.bold, + color: kprimaryColor, + ), + textStyle: Theme.of(context) + .textTheme + .displayMedium! + .copyWith( + fontSize: 18, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: width * 0.005, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), + bottom: BorderSide( + width: width * 0.005, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), ), ), ), + ], + ), + Padding( + padding: EdgeInsets.only( + left: width * 0.02, + right: width * 0.02, + top: height * 0.035, ), - const Text( + child: Text( ':', - style: TextStyle( - fontSize: 50.0, - fontWeight: FontWeight.bold, - ), - ), - Expanded( - child: Center( - child: Text( - seconds, - style: const TextStyle( - fontSize: 50.0, + style: Theme.of(context) + .textTheme + .displayLarge! + .copyWith( fontWeight: FontWeight.bold, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, ), - ), - ), ), - ], - ); - }, - ), - ), - Obx( - () => Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FloatingActionButton.small( - heroTag: 'stop', - onPressed: () async { - Utils.hapticFeedback(); - controller.stopTimer(); - int timerId = - await SecureStorageProvider().readTimerId(); - await IsarDb.deleteAlarm(timerId); - }, - child: const Icon(Icons.close_rounded), - ), - SizedBox( - width: width * 0.11, - ), - FloatingActionButton( - heroTag: 'pause', - onPressed: () { - Utils.hapticFeedback(); - controller.isTimerPaused.value - ? controller.resumeTimer() - : controller.pauseTimer(); - }, - child: Icon( - controller.isTimerPaused.value - ? Icons.play_arrow_rounded - : Icons.pause_rounded, - size: 33, ), - ), - ], - ), - ), - ], - ) - : InkWell( - onTap: () { - Utils.hapticFeedback(); - inputTimeController.changeTimePickerTimer(); - }, - child: Obx( - () => Container( - color: themeController.isLightMode.value - ? kLightPrimaryBackgroundColor - : kprimaryBackgroundColor, - height: height * 0.32, - width: width, - child: inputTimeController.isTimePickerTimer.value - ? Row( + Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Hours', - style: Theme.of(context) - .textTheme - .displaySmall! - .copyWith( - fontWeight: FontWeight.bold, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - ), - SizedBox( - height: height * 0.008, - ), - NumberPicker( - minValue: 0, - maxValue: 23, - value: controller.hours.value, - onChanged: (value) { - Utils.hapticFeedback(); - controller.hours.value = value; - inputTimeController. - setTextFieldTimerTime(); - }, - infiniteLoop: true, - itemWidth: width * 0.17, - zeroPad: true, - selectedTextStyle: Theme.of(context) - .textTheme - .displayLarge! - .copyWith( - fontWeight: FontWeight.bold, - color: kprimaryColor, - ), - textStyle: Theme.of(context) - .textTheme - .displayMedium! - .copyWith( - fontSize: 20, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: width * 0.005, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - bottom: BorderSide( - width: width * 0.005, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - ), + Text( + 'Minutes', + style: Theme.of(context) + .textTheme + .displaySmall! + .copyWith( + fontWeight: FontWeight.bold, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, ), - ), - ], ), - Padding( - padding: EdgeInsets.only( - left: width * 0.02, - right: width * 0.02, - top: height * 0.035, - ), - child: Text( - ':', - style: Theme.of(context) - .textTheme - .displayLarge! - .copyWith( - fontWeight: FontWeight.bold, - color: themeController.isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - ), + SizedBox( + height: height * 0.008, ), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Minutes', - style: Theme.of(context) - .textTheme - .displaySmall! - .copyWith( - fontWeight: FontWeight.bold, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - ), - SizedBox( - height: height * 0.008, - ), - NumberPicker( - minValue: 0, - maxValue: 59, - value: controller.minutes.value, - onChanged: (value) { - controller.minutes.value = value; - inputTimeController. - setTextFieldTimerTime(); - }, - infiniteLoop: true, - itemWidth: width * 0.17, - zeroPad: true, - selectedTextStyle: Theme.of(context) - .textTheme - .displayLarge! - .copyWith( - fontWeight: FontWeight.bold, - color: kprimaryColor, - ), - textStyle: Theme.of(context) - .textTheme - .displayMedium! - .copyWith( - fontSize: 20, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: width * 0.005, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - bottom: BorderSide( - width: width * 0.005, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - ), + NumberPicker( + minValue: 0, + maxValue: 59, + value: controller.minutes.value, + onChanged: (value) { + controller.minutes.value = value; + inputTimeController.setTextFieldTimerTime(); + }, + infiniteLoop: true, + itemWidth: width * 0.17, + zeroPad: true, + selectedTextStyle: Theme.of(context) + .textTheme + .displayLarge! + .copyWith( + fontSize: 30, + fontWeight: FontWeight.bold, + color: kprimaryColor, + ), + textStyle: Theme.of(context) + .textTheme + .displayMedium! + .copyWith( + fontSize: 18, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: width * 0.005, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), + bottom: BorderSide( + width: width * 0.005, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, ), ), - ], - ), - Padding( - padding: EdgeInsets.only( - left: width * 0.02, - right: width * 0.02, - top: height * 0.035, - ), - child: Text( - ':', - style: Theme.of(context) - .textTheme - .displayLarge! - .copyWith( - fontWeight: FontWeight.bold, - color: themeController.isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), ), ), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Seconds', - style: Theme.of(context) - .textTheme - .displaySmall! - .copyWith( - fontWeight: FontWeight.bold, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - ), - SizedBox( - height: height * 0.008, + ], + ), + Padding( + padding: EdgeInsets.only( + left: width * 0.02, + right: width * 0.02, + top: height * 0.035, + ), + child: Text( + ':', + style: Theme.of(context) + .textTheme + .displayLarge! + .copyWith( + fontWeight: FontWeight.bold, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, ), - NumberPicker( - minValue: 0, - maxValue: 59, - value: controller.seconds.value, - onChanged: (value) { - controller.seconds.value = value; - inputTimeController. - setTextFieldTimerTime(); - }, - infiniteLoop: true, - itemWidth: width * 0.17, - zeroPad: true, - selectedTextStyle: Theme.of(context) - .textTheme - .displayLarge! - .copyWith( - fontWeight: FontWeight.bold, - color: kprimaryColor, - ), - textStyle: Theme.of(context) - .textTheme - .displayMedium! - .copyWith( - fontSize: 20, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: width * 0.005, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - bottom: BorderSide( - width: width * 0.005, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - ), + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Seconds', + style: Theme.of(context) + .textTheme + .displaySmall! + .copyWith( + fontWeight: FontWeight.bold, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), + ), + SizedBox( + height: height * 0.008, + ), + NumberPicker( + minValue: 0, + maxValue: 59, + value: controller.seconds.value, + onChanged: (value) { + controller.seconds.value = value; + inputTimeController.setTextFieldTimerTime(); + }, + infiniteLoop: true, + itemWidth: width * 0.17, + zeroPad: true, + selectedTextStyle: Theme.of(context) + .textTheme + .displayLarge! + .copyWith( + fontSize: 30, + fontWeight: FontWeight.bold, + color: kprimaryColor, + ), + textStyle: Theme.of(context) + .textTheme + .displayMedium! + .copyWith( + fontSize: 18, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: width * 0.005, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), + bottom: BorderSide( + width: width * 0.005, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, ), ), - ], + ), ), ], - ) - : Row( + ), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Hours', - style: Theme.of(context) - .textTheme - .displaySmall! - .copyWith( - fontWeight: FontWeight.bold, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - ), - SizedBox( - height: height * 0.008, - ), - SizedBox( - width: 80, - child: TextField( - onChanged: (_) { - inputTimeController.setTimerTime(); - }, - decoration: const InputDecoration( - hintText: 'HH', - border: InputBorder.none, - ), - textAlign: TextAlign.center, - controller: inputTimeController - .inputHoursControllerTimer, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.allow( - RegExp( - '[1,2,3,4,5,6,7,8,9,0]', - ), - ), - LengthLimitingTextInputFormatter( - 2, - ), - LimitRange( - 0, - 23, - ), - ], + Text( + 'Hours', + style: Theme.of(context) + .textTheme + .displaySmall! + .copyWith( + fontWeight: FontWeight.bold, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, ), - ), - ], ), - Padding( - padding: EdgeInsets.only( - left: width * 0.02, - right: width * 0.02, - top: height * 0.035, - ), - child: Text( - ':', - style: Theme.of(context) - .textTheme - .displayLarge! - .copyWith( - fontWeight: FontWeight.bold, - color: themeController.isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - ), + SizedBox( + height: height * 0.008, ), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Minutes', - style: Theme.of(context) - .textTheme - .displaySmall! - .copyWith( - fontWeight: FontWeight.bold, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - ), - SizedBox( - height: height * 0.008, + SizedBox( + width: 80, + child: TextField( + onChanged: (_) { + inputTimeController.setTimerTime(); + }, + decoration: const InputDecoration( + hintText: 'HH', + border: InputBorder.none, ), - SizedBox( - width: 80, - child: TextField( - onChanged: (_) { - inputTimeController.setTimerTime(); - }, - decoration: const InputDecoration( - hintText: 'MM', - border: InputBorder.none, + textAlign: TextAlign.center, + controller: inputTimeController + .inputHoursControllerTimer, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp( + '[1,2,3,4,5,6,7,8,9,0]', ), - textAlign: TextAlign.center, - controller: inputTimeController - .inputMinutesControllerTimer, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.allow( - RegExp( - '[1,2,3,4,5,6,7,8,9,0]', - ), - ), - LengthLimitingTextInputFormatter( - 2, - ), - LimitRange( - 0, - 59, - ), - ], ), + LengthLimitingTextInputFormatter( + 2, + ), + LimitRange( + 0, + 99, + ), + ], + ), + ), + ], + ), + Padding( + padding: EdgeInsets.only( + left: width * 0.02, + right: width * 0.02, + top: height * 0.035, + ), + child: Text( + ':', + style: Theme.of(context) + .textTheme + .displayLarge! + .copyWith( + fontWeight: FontWeight.bold, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, ), - ], + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Minutes', + style: Theme.of(context) + .textTheme + .displaySmall! + .copyWith( + fontWeight: FontWeight.bold, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), ), - Padding( - padding: EdgeInsets.only( - left: width * 0.02, - right: width * 0.02, - top: height * 0.035, - ), - child: Text( - ':', - style: Theme.of(context) - .textTheme - .displayLarge! - .copyWith( - fontWeight: FontWeight.bold, - color: themeController.isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, + SizedBox( + height: height * 0.008, + ), + SizedBox( + width: 80, + child: TextField( + onChanged: (_) { + inputTimeController.setTimerTime(); + }, + decoration: const InputDecoration( + hintText: 'MM', + border: InputBorder.none, + ), + textAlign: TextAlign.center, + controller: inputTimeController + .inputMinutesControllerTimer, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp( + '[1,2,3,4,5,6,7,8,9,0]', ), + ), + LengthLimitingTextInputFormatter( + 2, + ), + LimitRange( + 0, + 59, + ), + ], ), ), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Seconds', - style: Theme.of(context) - .textTheme - .displaySmall! - .copyWith( - fontWeight: FontWeight.bold, - color: themeController - .isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), + ], + ), + Padding( + padding: EdgeInsets.only( + left: width * 0.02, + right: width * 0.02, + top: height * 0.035, + ), + child: Text( + ':', + style: Theme.of(context) + .textTheme + .displayLarge! + .copyWith( + fontWeight: FontWeight.bold, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, ), - SizedBox( - height: height * 0.008, + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Seconds', + style: Theme.of(context) + .textTheme + .displaySmall! + .copyWith( + fontWeight: FontWeight.bold, + color: themeController.isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), + ), + SizedBox( + height: height * 0.008, + ), + SizedBox( + width: 80, + child: TextField( + onChanged: (_) { + inputTimeController.setTimerTime(); + }, + decoration: const InputDecoration( + hintText: 'SS', + border: InputBorder.none, ), - SizedBox( - width: 80, - child: TextField( - onChanged: (_) { - inputTimeController.setTimerTime(); - }, - decoration: const InputDecoration( - hintText: 'SS', - border: InputBorder.none, + textAlign: TextAlign.center, + controller: inputTimeController + .inputSecondsControllerTimer, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp( + '[1,2,3,4,5,6,7,8,9,0]', ), - textAlign: TextAlign.center, - controller: inputTimeController - .inputSecondsControllerTimer, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.allow( - RegExp( - '[1,2,3,4,5,6,7,8,9,0]', - ), - ), - LengthLimitingTextInputFormatter( - 2, - ), - LimitRange( - 0, - 59, - ), - ], ), - ), - ], + LengthLimitingTextInputFormatter( + 2, + ), + LimitRange( + 0, + 59, + ), + ], + ), ), ], ), + ], + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(0, 20.0, 20.00, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 20, 0), + child: InkWell( + borderRadius: BorderRadius.circular(18), + onTap: () { + Get.back(); + }, + child: Container( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Cancel', + style: Theme.of(context) + .textTheme + .displayMedium! + .copyWith( + color: themeController.isLightMode.value + ? kLightPrimaryTextColor.withOpacity( + 0.5, + ) + : kprimaryTextColor.withOpacity( + 0.5, + ), + fontSize: 15, + ), + ), + ), + ), ), ), - ), - ), - floatingActionButton: Obx( - () => controller.isTimerRunning.value - ? const SizedBox() - : Obx( - () => AbsorbPointer( - absorbing: controller.hours.value == 0 && - controller.minutes.value == 0 && - controller.seconds.value == 0 - ? true - : false, - child: FloatingActionButton( - onPressed: () { - Utils.hapticFeedback(); + Padding( + padding: const EdgeInsets.fromLTRB(20, 0, 0, 0), + child: InkWell( + borderRadius: BorderRadius.circular(18), + onTap: () { controller.remainingTime.value = Duration( hours: controller.hours.value, minutes: controller.minutes.value, seconds: controller.seconds.value, ); - controller.startTimer(); + if(controller.hours.value != 0 || + controller.minutes.value != 0|| + controller.seconds.value != 0) controller.createTimer(); + controller.hours.value = 0; + controller.minutes.value = 1; + controller.seconds.value = 0; }, - backgroundColor: controller.hours.value == 0 && - controller.minutes.value == 0 && - controller.seconds.value == 0 - ? kprimaryDisabledTextColor - : kprimaryColor, - child: const Icon( - Icons.play_arrow_rounded, - size: 33, + child: Container( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'OK', + style: Theme.of(context) + .textTheme + .displayMedium! + .copyWith( + color: themeController.isLightMode.value + ? kLightPrimaryTextColor.withOpacity( + 0.5, + ) + : kprimaryTextColor.withOpacity( + 0.5, + ), + fontSize: 15, + ), + ), + ), ), ), ), - ), + ], + ), + ), + ], ), - endDrawer: buildEndDrawer(context), ); } } diff --git a/lib/app/modules/timerRing/controllers/timer_ring_controller.dart b/lib/app/modules/timerRing/controllers/timer_ring_controller.dart index 332f6a0a..c9ff6c70 100644 --- a/lib/app/modules/timerRing/controllers/timer_ring_controller.dart +++ b/lib/app/modules/timerRing/controllers/timer_ring_controller.dart @@ -11,9 +11,12 @@ import 'package:vibration/vibration.dart'; class TimerRingController extends GetxController { MethodChannel timerChannel = const MethodChannel('timer'); - final Rx currentlyRingingAlarm = Utils.genFakeTimerModel().obs; Timer? vibrationTimer; late StreamSubscription _subscription; + getFakeTimerModel()async { + TimerModel fakeTimer = await Utils.genFakeTimerModel(); + return fakeTimer; + } @override void onInit() async { super.onInit(); @@ -28,17 +31,17 @@ class TimerRingController extends GetxController { Timer.periodic(const Duration(milliseconds: 3500), (Timer timer) { Vibration.vibrate(pattern: [500, 3000]); }); - AudioUtils.playTimer(alarmRecord: currentlyRingingAlarm.value); + AudioUtils.playTimer(alarmRecord: await getFakeTimerModel().value); await timerChannel.invokeMethod('cancelTimer'); } @override - void onClose() { + onClose() async { Vibration.cancel(); vibrationTimer!.cancel(); AudioUtils.stopTimer( - ringtoneName: currentlyRingingAlarm.value.ringtoneName, + ringtoneName: await getFakeTimerModel().ringtoneName, ); _subscription.cancel(); super.onClose(); diff --git a/lib/app/utils/utils.dart b/lib/app/utils/utils.dart index ff081514..58c4630f 100644 --- a/lib/app/utils/utils.dart +++ b/lib/app/utils/utils.dart @@ -10,6 +10,7 @@ import 'dart:math'; import 'package:ultimate_alarm_clock/app/data/models/alarm_model.dart'; import 'package:ultimate_alarm_clock/app/data/models/quote_model.dart'; import 'package:ultimate_alarm_clock/app/data/models/timer_model.dart'; +import 'package:ultimate_alarm_clock/app/data/providers/isar_provider.dart'; import 'package:ultimate_alarm_clock/app/data/providers/secure_storage_provider.dart'; import 'package:ultimate_alarm_clock/app/utils/quote_list.dart'; @@ -101,6 +102,27 @@ class Utils { return milliseconds; } + static String formatMilliseconds(int milliseconds) { + final Duration duration = Duration(milliseconds: milliseconds); + final int seconds = duration.inSeconds; + final int minutes = duration.inMinutes; + final int hours = duration.inHours; + + if (seconds < 10) { + return "${seconds}"; + } else if (seconds < 60) { + return "${seconds}"; + } else if (minutes < 10) { + return "${minutes}:${seconds % 60 < 10 ? '0' : ''}${seconds % 60}"; + } else if (minutes < 60) { + return "${minutes}:${seconds % 60 < 10 ? '0' : ''}${seconds % 60}"; + } else if (hours < 10) { + return "${hours}:${minutes % 60 < 10 ? '0' : ''}${minutes % 60}:${seconds % 60 < 10 ? '0' : ''}${seconds % 60}"; + } else { + return "${hours}:${minutes % 60 < 10 ? '0' : ''}${minutes % 60}:${seconds % 60 < 10 ? '0' : ''}${seconds % 60}"; + } + } + static List convertTo12HourFormat(String time) { int hour = int.parse(time.substring(0, 2)); String minute = time.substring(3); @@ -355,12 +377,13 @@ class Utils { } // Utility function to create a dummy model to pass to functions - static TimerModel genFakeTimerModel() { + static Future genFakeTimerModel() async { return TimerModel( - intervalToAlarm: 0, - mainTimerTime: '', + timerValue: 0, + timeElapsed :0, + startedOn: '', ringtoneName: '', - timerTime: Utils.timeOfDayToString(TimeOfDay.now()), + timerName: '', ); } @@ -643,4 +666,15 @@ class Utils { ); } } + static int getDifferenceMillisFromNow(String datetimeString, int milliseconds) { + try { + final providedDatetime = DateTime.parse(datetimeString); + final updatedDatetime = providedDatetime.add(Duration(milliseconds: milliseconds)); + final currentDatetime = DateTime.now(); + final difference = updatedDatetime.difference(currentDatetime); + return difference.inMilliseconds; + } catch (e) { + return 0; + } + } } diff --git a/lib/main.dart b/lib/main.dart index e57fbba8..96ad2c54 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:ultimate_alarm_clock/app/data/providers/get_storage_provider.dart'; import 'package:ultimate_alarm_clock/app/utils/language.dart'; @@ -14,7 +15,11 @@ Locale? loc; void main() async { WidgetsFlutterBinding.ensureInitialized(); - + await Permission.notification.isDenied.then((value) { + if (value) { + Permission.notification.request(); + } + }); await Firebase.initializeApp(); await Get.putAsync(() => GetStorageProvider().init()); diff --git a/pubspec.lock b/pubspec.lock index 7d2a3339..cd000dfb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -752,30 +752,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.8.2" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 - url: "https://pub.dev" - source: hosted - version: "2.0.1" lints: dependency: transitive description: @@ -804,26 +780,26 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.10.0" mgrs_dart: dependency: transitive description: @@ -868,10 +844,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.3" path_parsing: dependency: transitive description: @@ -1149,6 +1125,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 + url: "https://pub.dev" + source: hosted + version: "2.3.2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" stack_trace: dependency: transitive description: @@ -1365,14 +1357,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 - url: "https://pub.dev" - source: hosted - version: "13.0.0" watcher: dependency: transitive description: @@ -1462,4 +1446,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" \ No newline at end of file + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0"