diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4618ecc..bd10ce1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,9 +1,9 @@ - - - + + + @@ -39,5 +39,14 @@ - + + + + + diff --git a/android/app/src/main/kotlin/com/example/fmassive/AlarmService.kt b/android/app/src/main/kotlin/com/example/fmassive/AlarmService.kt new file mode 100644 index 0000000..0686bd8 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/fmassive/AlarmService.kt @@ -0,0 +1,155 @@ +package com.example.fmassive + +import android.annotation.SuppressLint +import android.app.* +import android.content.Context +import android.content.Intent +import android.media.AudioAttributes +import android.media.MediaPlayer +import android.media.MediaPlayer.OnPreparedListener +import android.net.Uri +import android.os.* +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat + +@RequiresApi(Build.VERSION_CODES.O) +class AlarmService : Service(), OnPreparedListener { + private var mediaPlayer: MediaPlayer? = null + private var vibrator: Vibrator? = null + + private fun getBuilder(): NotificationCompat.Builder { + val context = applicationContext + val contentIntent = Intent(context, MainActivity::class.java) + val pendingContent = + PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE) + val addBroadcast = Intent(MainActivity.ADD_BROADCAST).apply { + setPackage(context.packageName) + } + val pendingAdd = + PendingIntent.getBroadcast(context, 0, addBroadcast, PendingIntent.FLAG_MUTABLE) + val stopBroadcast = Intent(MainActivity.STOP_BROADCAST) + stopBroadcast.setPackage(context.packageName) + val pendingStop = + PendingIntent.getBroadcast(context, 0, stopBroadcast, PendingIntent.FLAG_IMMUTABLE) + return NotificationCompat.Builder(context, MainActivity.CHANNEL_ID_PENDING) + .setSmallIcon(R.drawable.ic_baseline_hourglass_bottom_24).setContentTitle("Resting") + .setContentIntent(pendingContent) + .addAction(R.drawable.ic_baseline_stop_24, "Stop", pendingStop) + .addAction(R.drawable.ic_baseline_stop_24, "Add 1 min", pendingAdd) + .setDeleteIntent(pendingStop) + } + + + @SuppressLint("Range") + private fun getSettings(): Settings { + val db = DatabaseHelper(applicationContext).readableDatabase + val cursor = db.rawQuery("SELECT sound, no_sound, vibrate FROM settings", null) + cursor.moveToFirst() + val sound = cursor.getString(cursor.getColumnIndex("sound")) + val noSound = cursor.getInt(cursor.getColumnIndex("no_sound")) == 1 + val vibrate = cursor.getInt(cursor.getColumnIndex("vibrate")) == 1 + Log.d("AlarmService", "vibrate=$vibrate") + cursor.close() + return Settings(sound, noSound, vibrate) + } + + private fun playSound(settings: Settings) { + if (settings.sound == null && !settings.noSound) { + Log.d("AlarmService", "Playing default alarm sound...") + mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon) + mediaPlayer?.start() + mediaPlayer?.setOnCompletionListener { vibrator?.cancel() } + } else if (settings.sound != null && !settings.noSound) { + Log.d("AlarmService", "Playing custom alarm sound ${settings.sound}...") + mediaPlayer = MediaPlayer().apply { + setAudioAttributes( + AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setUsage(AudioAttributes.USAGE_MEDIA) + .build() + ) + setDataSource(applicationContext, Uri.parse(settings.sound)) + prepare() + start() + setOnCompletionListener { vibrator?.cancel() } + } + } + } + + private fun doNotify(): Notification { + Log.d("AlarmService", "doNotify") + val alarmsChannel = NotificationChannel( + CHANNEL_ID_DONE, + CHANNEL_ID_DONE, + NotificationManager.IMPORTANCE_HIGH + ) + alarmsChannel.description = "Alarms for rest timers." + alarmsChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC + alarmsChannel.setSound(null, null) + val manager = applicationContext.getSystemService( + NotificationManager::class.java + ) + manager.createNotificationChannel(alarmsChannel) + val builder = getBuilder() + val context = applicationContext + val finishIntent = Intent(context, StopAlarm::class.java) + val finishPending = PendingIntent.getActivity( + context, 0, finishIntent, PendingIntent.FLAG_IMMUTABLE + ) + builder.setContentText("Timer finished.").setProgress(0, 0, false) + .setAutoCancel(true).setOngoing(true) + .setContentIntent(finishPending).setChannelId(CHANNEL_ID_DONE) + .setCategory(NotificationCompat.CATEGORY_ALARM).priority = + NotificationCompat.PRIORITY_HIGH + val notification = builder.build() + manager.notify(NOTIFICATION_ID_DONE, notification) + manager.cancel(MainActivity.NOTIFICATION_ID_PENDING) + return notification + } + + @SuppressLint("Recycle") + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + Log.d("AlarmService", "onStartCommand") + val notification = doNotify() + startForeground(NOTIFICATION_ID_DONE, notification) + val settings = getSettings() + playSound(settings) + if (!settings.vibrate) return START_STICKY + val pattern = longArrayOf(0, 300, 1300, 300, 1300, 300) + vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val vibratorManager = + getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager + vibratorManager.defaultVibrator + } else { + @Suppress("DEPRECATION") + getSystemService(VIBRATOR_SERVICE) as Vibrator + } + val audioAttributes = AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ALARM) + .build() + vibrator!!.vibrate(VibrationEffect.createWaveform(pattern, 1), audioAttributes) + return START_STICKY + } + + override fun onBind(intent: Intent): IBinder? { + return null + } + + override fun onPrepared(player: MediaPlayer) { + player.start() + } + + override fun onDestroy() { + super.onDestroy() + mediaPlayer?.stop() + mediaPlayer?.release() + vibrator?.cancel() + } + + companion object { + const val CHANNEL_ID_DONE = "Alarm" + const val NOTIFICATION_ID_DONE = 2 + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/fmassive/DatabaseHelper.kt b/android/app/src/main/kotlin/com/example/fmassive/DatabaseHelper.kt new file mode 100644 index 0000000..899a421 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/fmassive/DatabaseHelper.kt @@ -0,0 +1,22 @@ +package com.example.fmassive + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper + +class DatabaseHelper(context: Context) : + SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { + companion object { + private const val DATABASE_NAME = "massive.db" + private const val DATABASE_VERSION = 1 + } + + override fun onCreate(db: SQLiteDatabase) { + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + } + + override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + } +} diff --git a/android/app/src/main/kotlin/com/example/fmassive/MainActivity.kt b/android/app/src/main/kotlin/com/example/fmassive/MainActivity.kt index 8a70ed0..4f0e697 100644 --- a/android/app/src/main/kotlin/com/example/fmassive/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/fmassive/MainActivity.kt @@ -19,16 +19,14 @@ import io.flutter.plugin.common.MethodChannel import kotlin.math.floor class MainActivity : FlutterActivity() { - private val CHANNEL = "com.massive/android" - @RequiresApi(Build.VERSION_CODES.O) override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, FLUTTER_CHANNEL).setMethodCallHandler { call, result -> if (call.method == "timer") { val args = call.arguments as ArrayList<*> - timer(args[0] as Long) + timer(args[0] as Int) } else { result.notImplemented() } @@ -42,7 +40,7 @@ class MainActivity : FlutterActivity() { private val stopReceiver = object : BroadcastReceiver() { @RequiresApi(Build.VERSION_CODES.O) override fun onReceive(context: Context?, intent: Intent?) { - Log.d("AlarmModule", "Received stop broadcast intent") + Log.d("MainActivity", "Received stop broadcast intent") stop() } } @@ -59,59 +57,51 @@ class MainActivity : FlutterActivity() { @RequiresApi(api = Build.VERSION_CODES.O) fun add() { - Log.d("AlarmModule", "Add 1 min to alarm.") + Log.d("MainActivity", "Add 1 min to alarm.") countdownTimer?.cancel() val newMs = if (running) currentMs.toInt().plus(60000) else 60000 - countdownTimer = getTimer(newMs.toLong()) + countdownTimer = getTimer(newMs) countdownTimer?.start() running = true - //val manager = getManager() - //manager.cancel(AlarmService.NOTIFICATION_ID_DONE) - //val intent = Intent(context, AlarmService::class.java) - //context.stopService(intent) + val manager = getManager() + manager.cancel(AlarmService.NOTIFICATION_ID_DONE) + val intent = Intent(context, AlarmService::class.java) + context.stopService(intent) } @RequiresApi(api = Build.VERSION_CODES.O) fun stop() { - Log.d("AlarmModule", "Stop alarm.") + Log.d("MainActivity", "Stop alarm.") countdownTimer?.cancel() running = false - //val intent = Intent(context, AlarmService::class.java) - //reactApplicationContext?.stopService(intent) - //val manager = getManager() - //manager.cancel(AlarmService.NOTIFICATION_ID_DONE) - //manager.cancel(NOTIFICATION_ID_PENDING) - //val params = Arguments.createMap().apply { - // putString("minutes", "00") - // putString("seconds", "00") - //} - //reactApplicationContext - // .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) - // .emit("tick", params) + val intent = Intent(context, AlarmService::class.java) + context.stopService(intent) + val manager = getManager() + manager.cancel(AlarmService.NOTIFICATION_ID_DONE) + manager.cancel(NOTIFICATION_ID_PENDING) } @RequiresApi(api = Build.VERSION_CODES.O) - fun timer(milliseconds: Long) { + fun timer(milliseconds: Int) { context.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST)) context.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST)) - Log.d("AlarmModule", "Queue alarm for $milliseconds delay") - //val manager = getManager() - //manager.cancel(AlarmService.NOTIFICATION_ID_DONE) - //val intent = Intent(reactApplicationContext, AlarmService::class.java) - //reactApplicationContext.stopService(intent) + Log.d("MainActivity", "Queue alarm for $milliseconds delay") + val manager = getManager() + manager.cancel(AlarmService.NOTIFICATION_ID_DONE) + val intent = Intent(context, AlarmService::class.java) + context.stopService(intent) countdownTimer?.cancel() countdownTimer = getTimer(milliseconds) countdownTimer?.start() running = true } - @RequiresApi(Build.VERSION_CODES.M) + @RequiresApi(Build.VERSION_CODES.O) private fun getTimer( - endMs: Long, + endMs: Int, ): CountDownTimer { val builder = getBuilder() return object : CountDownTimer(endMs.toLong(), 1000) { - @RequiresApi(Build.VERSION_CODES.O) override fun onTick(current: Long) { currentMs = current val seconds = @@ -123,27 +113,13 @@ class MainActivity : FlutterActivity() { .setCategory(NotificationCompat.CATEGORY_PROGRESS).priority = NotificationCompat.PRIORITY_LOW val manager = getManager() - Log.d("AlarmModule", "Notify $NOTIFICATION_ID_PENDING") + Log.d("MainActivity", "current=$current") manager.notify(NOTIFICATION_ID_PENDING, builder.build()) - //val params = Arguments.createMap().apply { - // putString("minutes", minutes) - // putString("seconds", seconds) - //} - //reactApplicationContext - // .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) - // .emit("tick", params) } - @RequiresApi(Build.VERSION_CODES.O) override fun onFinish() { - //val context = reactApplicationContext - //context.startForegroundService(Intent(context, AlarmService::class.java)) - //context - // .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) - // .emit("finish", Arguments.createMap().apply { - // putString("minutes", "00") - // putString("seconds", "00") - // }) + Log.d("MainActivity", "Finish") + context.startForegroundService(Intent(context, AlarmService::class.java)) } } } @@ -190,5 +166,6 @@ class MainActivity : FlutterActivity() { const val ADD_BROADCAST = "add-timer-event" const val CHANNEL_ID_PENDING = "Timer" const val NOTIFICATION_ID_PENDING = 1 + const val FLUTTER_CHANNEL = "com.massive/android" } } diff --git a/android/app/src/main/kotlin/com/example/fmassive/Settings.kt b/android/app/src/main/kotlin/com/example/fmassive/Settings.kt new file mode 100644 index 0000000..9a72904 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/fmassive/Settings.kt @@ -0,0 +1,3 @@ +package com.example.fmassive + +class Settings(val sound: String?, val noSound: Boolean, val vibrate: Boolean) \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/fmassive/StopAlarm.kt b/android/app/src/main/kotlin/com/example/fmassive/StopAlarm.kt new file mode 100644 index 0000000..ac4d1ff --- /dev/null +++ b/android/app/src/main/kotlin/com/example/fmassive/StopAlarm.kt @@ -0,0 +1,22 @@ +package com.example.fmassive + +import android.app.Activity +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.annotation.RequiresApi + +class StopAlarm : Activity() { + @RequiresApi(Build.VERSION_CODES.O_MR1) + override fun onCreate(savedInstanceState: Bundle?) { + Log.d("AlarmActivity", "Call to AlarmActivity") + super.onCreate(savedInstanceState) + val context = applicationContext + context.stopService(Intent(context, AlarmService::class.java)) + savedInstanceState.apply { setShowWhenLocked(true) } + val intent = Intent(context, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + context.startActivity(intent) + } +} \ No newline at end of file diff --git a/android/app/src/main/res/raw/argon.mp3 b/android/app/src/main/res/raw/argon.mp3 new file mode 100644 index 0000000..d1f0065 Binary files /dev/null and b/android/app/src/main/res/raw/argon.mp3 differ diff --git a/lib/database.dart b/lib/database.dart index 5cfe49b..9ef6587 100644 --- a/lib/database.dart +++ b/lib/database.dart @@ -1,10 +1,12 @@ import 'dart:io'; import 'package:fmassive/gym_set.dart'; +import 'package:fmassive/main.dart'; import 'package:moor/ffi.dart'; import 'package:moor/moor.dart'; import 'package:path/path.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:sqflite/sqflite.dart'; + import 'settings.dart'; part 'database.g.dart'; @@ -20,6 +22,8 @@ class MyDatabase extends _$MyDatabase { MigrationStrategy get migration => MigrationStrategy( onCreate: (Migrator m) async { await m.createAll(); + var data = await db.select(db.settings).get(); + if (data.isEmpty) await db.into(db.settings).insert(defaultSettings); }, onUpgrade: (Migrator m, int from, int to) async { // no migrations yet @@ -29,8 +33,9 @@ class MyDatabase extends _$MyDatabase { LazyDatabase _openConnection() { return LazyDatabase(() async { - final dbFolder = await getApplicationDocumentsDirectory(); - final file = File(join(dbFolder.path, 'massive.db')); + final dbFolder = await getDatabasesPath(); + final file = File(join(dbFolder, 'massive.db')); + print('file.path=${file.path}'); return VmDatabase(file, logStatements: true); }); } diff --git a/lib/database.g.dart b/lib/database.g.dart index 3337bd6..2f9739c 100644 --- a/lib/database.g.dart +++ b/lib/database.g.dart @@ -10,7 +10,7 @@ part of 'database.dart'; class Setting extends DataClass implements Insertable { final bool alarm; final bool vibrate; - final String sound; + final String? sound; final bool notify; final bool images; final bool showUnit; @@ -26,7 +26,7 @@ class Setting extends DataClass implements Insertable { Setting( {required this.alarm, required this.vibrate, - required this.sound, + this.sound, required this.notify, required this.images, required this.showUnit, @@ -48,7 +48,7 @@ class Setting extends DataClass implements Insertable { vibrate: const BoolType() .mapFromDatabaseResponse(data['${effectivePrefix}vibrate'])!, sound: const StringType() - .mapFromDatabaseResponse(data['${effectivePrefix}sound'])!, + .mapFromDatabaseResponse(data['${effectivePrefix}sound']), notify: const BoolType() .mapFromDatabaseResponse(data['${effectivePrefix}notify'])!, images: const BoolType() @@ -80,7 +80,9 @@ class Setting extends DataClass implements Insertable { final map = {}; map['alarm'] = Variable(alarm); map['vibrate'] = Variable(vibrate); - map['sound'] = Variable(sound); + if (!nullToAbsent || sound != null) { + map['sound'] = Variable(sound); + } map['notify'] = Variable(notify); map['images'] = Variable(images); map['show_unit'] = Variable(showUnit); @@ -104,7 +106,8 @@ class Setting extends DataClass implements Insertable { return SettingsCompanion( alarm: Value(alarm), vibrate: Value(vibrate), - sound: Value(sound), + sound: + sound == null && nullToAbsent ? const Value.absent() : Value(sound), notify: Value(notify), images: Value(images), showUnit: Value(showUnit), @@ -130,7 +133,7 @@ class Setting extends DataClass implements Insertable { return Setting( alarm: serializer.fromJson(json['alarm']), vibrate: serializer.fromJson(json['vibrate']), - sound: serializer.fromJson(json['sound']), + sound: serializer.fromJson(json['sound']), notify: serializer.fromJson(json['notify']), images: serializer.fromJson(json['images']), showUnit: serializer.fromJson(json['showUnit']), @@ -151,7 +154,7 @@ class Setting extends DataClass implements Insertable { return { 'alarm': serializer.toJson(alarm), 'vibrate': serializer.toJson(vibrate), - 'sound': serializer.toJson(sound), + 'sound': serializer.toJson(sound), 'notify': serializer.toJson(notify), 'images': serializer.toJson(images), 'showUnit': serializer.toJson(showUnit), @@ -263,7 +266,7 @@ class Setting extends DataClass implements Insertable { class SettingsCompanion extends UpdateCompanion { final Value alarm; final Value vibrate; - final Value sound; + final Value sound; final Value notify; final Value images; final Value showUnit; @@ -296,7 +299,7 @@ class SettingsCompanion extends UpdateCompanion { SettingsCompanion.insert({ required bool alarm, required bool vibrate, - required String sound, + this.sound = const Value.absent(), required bool notify, required bool images, required bool showUnit, @@ -311,7 +314,6 @@ class SettingsCompanion extends UpdateCompanion { required bool backup, }) : alarm = Value(alarm), vibrate = Value(vibrate), - sound = Value(sound), notify = Value(notify), images = Value(images), showUnit = Value(showUnit), @@ -325,7 +327,7 @@ class SettingsCompanion extends UpdateCompanion { static Insertable custom({ Expression? alarm, Expression? vibrate, - Expression? sound, + Expression? sound, Expression? notify, Expression? images, Expression? showUnit, @@ -361,7 +363,7 @@ class SettingsCompanion extends UpdateCompanion { SettingsCompanion copyWith( {Value? alarm, Value? vibrate, - Value? sound, + Value? sound, Value? notify, Value? images, Value? showUnit, @@ -403,7 +405,7 @@ class SettingsCompanion extends UpdateCompanion { map['vibrate'] = Variable(vibrate.value); } if (sound.present) { - map['sound'] = Variable(sound.value); + map['sound'] = Variable(sound.value); } if (notify.present) { map['notify'] = Variable(notify.value); @@ -489,8 +491,8 @@ class $SettingsTable extends Settings with TableInfo<$SettingsTable, Setting> { final VerificationMeta _soundMeta = const VerificationMeta('sound'); @override late final GeneratedColumn sound = GeneratedColumn( - 'sound', aliasedName, false, - type: const StringType(), requiredDuringInsert: true); + 'sound', aliasedName, true, + type: const StringType(), requiredDuringInsert: false); final VerificationMeta _notifyMeta = const VerificationMeta('notify'); @override late final GeneratedColumn notify = GeneratedColumn( @@ -609,8 +611,6 @@ class $SettingsTable extends Settings with TableInfo<$SettingsTable, Setting> { if (data.containsKey('sound')) { context.handle( _soundMeta, sound.isAcceptableOrUnknown(data['sound']!, _soundMeta)); - } else if (isInserting) { - context.missing(_soundMeta); } if (data.containsKey('notify')) { context.handle(_notifyMeta, diff --git a/lib/edit_set.dart b/lib/edit_set.dart index 8752ad9..012d2a5 100644 --- a/lib/edit_set.dart +++ b/lib/edit_set.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/material.dart' as material; +import 'package:flutter/services.dart'; import 'package:fmassive/database.dart'; import 'package:fmassive/main.dart'; import 'package:moor_flutter/moor_flutter.dart'; @@ -112,8 +113,11 @@ class _EditGymSetPageState extends State { onPressed: () async { if (gymSet.id.present) await db.update(db.gymSets).write(gymSet); - else + else { await db.into(db.gymSets).insert(gymSet); + const platform = MethodChannel('com.massive/android'); + platform.invokeMethod('timer', [3000]); + } if (!mounted) return; Navigator.pop(context); }, diff --git a/lib/home_page.dart b/lib/home_page.dart index ca068cc..b2c5422 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -33,6 +33,7 @@ class RootPage extends State { void toggleSearch() { setState(() { + if (showSearch) search = ''; showSearch = !showSearch; }); focusNode.requestFocus(); diff --git a/lib/settings.dart b/lib/settings.dart index 72a8658..c528810 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -4,7 +4,7 @@ import 'package:moor/moor.dart'; Setting defaultSettings = Setting( alarm: true, vibrate: true, - sound: 'default.mp3', + sound: null, notify: true, images: true, showUnit: true, @@ -22,7 +22,7 @@ Setting defaultSettings = Setting( class Settings extends Table { BoolColumn get alarm => boolean()(); BoolColumn get vibrate => boolean()(); - TextColumn get sound => text()(); + TextColumn get sound => text().nullable()(); BoolColumn get notify => boolean()(); BoolColumn get images => boolean()(); BoolColumn get showUnit => boolean()(); diff --git a/lib/settings_page.dart b/lib/settings_page.dart index f586634..2e906d5 100644 --- a/lib/settings_page.dart +++ b/lib/settings_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/material.dart' as material; import 'package:fmassive/database.dart'; import 'package:fmassive/main.dart'; -import 'package:fmassive/settings.dart'; import 'package:moor/moor.dart'; class SettingsPage extends StatelessWidget { @@ -30,15 +29,9 @@ class _SettingsPageState extends State<_SettingsPage> { final TextEditingController searchController = TextEditingController(); - void reset() async { - var data = await db.select(db.settings).get(); - if (data.isEmpty) await db.into(db.settings).insert(defaultSettings); - } - @override void initState() { super.initState(); - reset(); stream = db.select(db.settings).watchSingle(); }