Compare commits

...

2 Commits

Author SHA1 Message Date
3ff136f758 Get alarm working 2023-04-11 13:14:26 +12:00
a8b6c9f8a3 Auto focus and select text for edit_set 2023-04-11 11:47:03 +12:00
13 changed files with 302 additions and 84 deletions

View File

@ -39,5 +39,14 @@
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
<activity
android:name=".StopAlarm"
android:exported="true"
android:process=":remote" />
<service
android:name=".AlarmService"
android:exported="false" />
</application> </application>
</manifest> </manifest>

View File

@ -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
}
}

View File

@ -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) {
}
}

View File

@ -19,16 +19,14 @@ import io.flutter.plugin.common.MethodChannel
import kotlin.math.floor import kotlin.math.floor
class MainActivity : FlutterActivity() { class MainActivity : FlutterActivity() {
private val CHANNEL = "com.massive/android"
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { MethodChannel(flutterEngine.dartExecutor.binaryMessenger, FLUTTER_CHANNEL).setMethodCallHandler {
call, result -> call, result ->
if (call.method == "timer") { if (call.method == "timer") {
val args = call.arguments as ArrayList<*> val args = call.arguments as ArrayList<*>
timer(args[0] as Long) timer(args[0] as Int)
} else { } else {
result.notImplemented() result.notImplemented()
} }
@ -42,7 +40,7 @@ class MainActivity : FlutterActivity() {
private val stopReceiver = object : BroadcastReceiver() { private val stopReceiver = object : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
Log.d("AlarmModule", "Received stop broadcast intent") Log.d("MainActivity", "Received stop broadcast intent")
stop() stop()
} }
} }
@ -59,59 +57,51 @@ class MainActivity : FlutterActivity() {
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
fun add() { fun add() {
Log.d("AlarmModule", "Add 1 min to alarm.") Log.d("MainActivity", "Add 1 min to alarm.")
countdownTimer?.cancel() countdownTimer?.cancel()
val newMs = if (running) currentMs.toInt().plus(60000) else 60000 val newMs = if (running) currentMs.toInt().plus(60000) else 60000
countdownTimer = getTimer(newMs.toLong()) countdownTimer = getTimer(newMs)
countdownTimer?.start() countdownTimer?.start()
running = true running = true
//val manager = getManager() val manager = getManager()
//manager.cancel(AlarmService.NOTIFICATION_ID_DONE) manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
//val intent = Intent(context, AlarmService::class.java) val intent = Intent(context, AlarmService::class.java)
//context.stopService(intent) context.stopService(intent)
} }
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
fun stop() { fun stop() {
Log.d("AlarmModule", "Stop alarm.") Log.d("MainActivity", "Stop alarm.")
countdownTimer?.cancel() countdownTimer?.cancel()
running = false running = false
//val intent = Intent(context, AlarmService::class.java) val intent = Intent(context, AlarmService::class.java)
//reactApplicationContext?.stopService(intent) context.stopService(intent)
//val manager = getManager() val manager = getManager()
//manager.cancel(AlarmService.NOTIFICATION_ID_DONE) manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
//manager.cancel(NOTIFICATION_ID_PENDING) 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)
} }
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
fun timer(milliseconds: Long) { fun timer(milliseconds: Int) {
context.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST)) context.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST))
context.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST)) context.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST))
Log.d("AlarmModule", "Queue alarm for $milliseconds delay") Log.d("MainActivity", "Queue alarm for $milliseconds delay")
//val manager = getManager() val manager = getManager()
//manager.cancel(AlarmService.NOTIFICATION_ID_DONE) manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
//val intent = Intent(reactApplicationContext, AlarmService::class.java) val intent = Intent(context, AlarmService::class.java)
//reactApplicationContext.stopService(intent) context.stopService(intent)
countdownTimer?.cancel() countdownTimer?.cancel()
countdownTimer = getTimer(milliseconds) countdownTimer = getTimer(milliseconds)
countdownTimer?.start() countdownTimer?.start()
running = true running = true
} }
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.O)
private fun getTimer( private fun getTimer(
endMs: Long, endMs: Int,
): CountDownTimer { ): CountDownTimer {
val builder = getBuilder() val builder = getBuilder()
return object : CountDownTimer(endMs.toLong(), 1000) { return object : CountDownTimer(endMs.toLong(), 1000) {
@RequiresApi(Build.VERSION_CODES.O)
override fun onTick(current: Long) { override fun onTick(current: Long) {
currentMs = current currentMs = current
val seconds = val seconds =
@ -123,27 +113,13 @@ class MainActivity : FlutterActivity() {
.setCategory(NotificationCompat.CATEGORY_PROGRESS).priority = .setCategory(NotificationCompat.CATEGORY_PROGRESS).priority =
NotificationCompat.PRIORITY_LOW NotificationCompat.PRIORITY_LOW
val manager = getManager() val manager = getManager()
Log.d("AlarmModule", "Notify $NOTIFICATION_ID_PENDING") Log.d("MainActivity", "current=$current")
manager.notify(NOTIFICATION_ID_PENDING, builder.build()) 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() { override fun onFinish() {
//val context = reactApplicationContext Log.d("MainActivity", "Finish")
//context.startForegroundService(Intent(context, AlarmService::class.java)) context.startForegroundService(Intent(context, AlarmService::class.java))
//context
// .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
// .emit("finish", Arguments.createMap().apply {
// putString("minutes", "00")
// putString("seconds", "00")
// })
} }
} }
} }
@ -190,5 +166,6 @@ class MainActivity : FlutterActivity() {
const val ADD_BROADCAST = "add-timer-event" const val ADD_BROADCAST = "add-timer-event"
const val CHANNEL_ID_PENDING = "Timer" const val CHANNEL_ID_PENDING = "Timer"
const val NOTIFICATION_ID_PENDING = 1 const val NOTIFICATION_ID_PENDING = 1
const val FLUTTER_CHANNEL = "com.massive/android"
} }
} }

View File

@ -0,0 +1,3 @@
package com.example.fmassive
class Settings(val sound: String?, val noSound: Boolean, val vibrate: Boolean)

View File

@ -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)
}
}

Binary file not shown.

View File

@ -1,10 +1,12 @@
import 'dart:io'; import 'dart:io';
import 'package:fmassive/gym_set.dart'; import 'package:fmassive/gym_set.dart';
import 'package:fmassive/main.dart';
import 'package:moor/ffi.dart'; import 'package:moor/ffi.dart';
import 'package:moor/moor.dart'; import 'package:moor/moor.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart';
import 'settings.dart'; import 'settings.dart';
part 'database.g.dart'; part 'database.g.dart';
@ -20,6 +22,8 @@ class MyDatabase extends _$MyDatabase {
MigrationStrategy get migration => MigrationStrategy( MigrationStrategy get migration => MigrationStrategy(
onCreate: (Migrator m) async { onCreate: (Migrator m) async {
await m.createAll(); 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 { onUpgrade: (Migrator m, int from, int to) async {
// no migrations yet // no migrations yet
@ -29,8 +33,9 @@ class MyDatabase extends _$MyDatabase {
LazyDatabase _openConnection() { LazyDatabase _openConnection() {
return LazyDatabase(() async { return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory(); final dbFolder = await getDatabasesPath();
final file = File(join(dbFolder.path, 'massive.db')); final file = File(join(dbFolder, 'massive.db'));
print('file.path=${file.path}');
return VmDatabase(file, logStatements: true); return VmDatabase(file, logStatements: true);
}); });
} }

View File

@ -10,7 +10,7 @@ part of 'database.dart';
class Setting extends DataClass implements Insertable<Setting> { class Setting extends DataClass implements Insertable<Setting> {
final bool alarm; final bool alarm;
final bool vibrate; final bool vibrate;
final String sound; final String? sound;
final bool notify; final bool notify;
final bool images; final bool images;
final bool showUnit; final bool showUnit;
@ -26,7 +26,7 @@ class Setting extends DataClass implements Insertable<Setting> {
Setting( Setting(
{required this.alarm, {required this.alarm,
required this.vibrate, required this.vibrate,
required this.sound, this.sound,
required this.notify, required this.notify,
required this.images, required this.images,
required this.showUnit, required this.showUnit,
@ -48,7 +48,7 @@ class Setting extends DataClass implements Insertable<Setting> {
vibrate: const BoolType() vibrate: const BoolType()
.mapFromDatabaseResponse(data['${effectivePrefix}vibrate'])!, .mapFromDatabaseResponse(data['${effectivePrefix}vibrate'])!,
sound: const StringType() sound: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}sound'])!, .mapFromDatabaseResponse(data['${effectivePrefix}sound']),
notify: const BoolType() notify: const BoolType()
.mapFromDatabaseResponse(data['${effectivePrefix}notify'])!, .mapFromDatabaseResponse(data['${effectivePrefix}notify'])!,
images: const BoolType() images: const BoolType()
@ -80,7 +80,9 @@ class Setting extends DataClass implements Insertable<Setting> {
final map = <String, Expression>{}; final map = <String, Expression>{};
map['alarm'] = Variable<bool>(alarm); map['alarm'] = Variable<bool>(alarm);
map['vibrate'] = Variable<bool>(vibrate); map['vibrate'] = Variable<bool>(vibrate);
map['sound'] = Variable<String>(sound); if (!nullToAbsent || sound != null) {
map['sound'] = Variable<String?>(sound);
}
map['notify'] = Variable<bool>(notify); map['notify'] = Variable<bool>(notify);
map['images'] = Variable<bool>(images); map['images'] = Variable<bool>(images);
map['show_unit'] = Variable<bool>(showUnit); map['show_unit'] = Variable<bool>(showUnit);
@ -104,7 +106,8 @@ class Setting extends DataClass implements Insertable<Setting> {
return SettingsCompanion( return SettingsCompanion(
alarm: Value(alarm), alarm: Value(alarm),
vibrate: Value(vibrate), vibrate: Value(vibrate),
sound: Value(sound), sound:
sound == null && nullToAbsent ? const Value.absent() : Value(sound),
notify: Value(notify), notify: Value(notify),
images: Value(images), images: Value(images),
showUnit: Value(showUnit), showUnit: Value(showUnit),
@ -130,7 +133,7 @@ class Setting extends DataClass implements Insertable<Setting> {
return Setting( return Setting(
alarm: serializer.fromJson<bool>(json['alarm']), alarm: serializer.fromJson<bool>(json['alarm']),
vibrate: serializer.fromJson<bool>(json['vibrate']), vibrate: serializer.fromJson<bool>(json['vibrate']),
sound: serializer.fromJson<String>(json['sound']), sound: serializer.fromJson<String?>(json['sound']),
notify: serializer.fromJson<bool>(json['notify']), notify: serializer.fromJson<bool>(json['notify']),
images: serializer.fromJson<bool>(json['images']), images: serializer.fromJson<bool>(json['images']),
showUnit: serializer.fromJson<bool>(json['showUnit']), showUnit: serializer.fromJson<bool>(json['showUnit']),
@ -151,7 +154,7 @@ class Setting extends DataClass implements Insertable<Setting> {
return <String, dynamic>{ return <String, dynamic>{
'alarm': serializer.toJson<bool>(alarm), 'alarm': serializer.toJson<bool>(alarm),
'vibrate': serializer.toJson<bool>(vibrate), 'vibrate': serializer.toJson<bool>(vibrate),
'sound': serializer.toJson<String>(sound), 'sound': serializer.toJson<String?>(sound),
'notify': serializer.toJson<bool>(notify), 'notify': serializer.toJson<bool>(notify),
'images': serializer.toJson<bool>(images), 'images': serializer.toJson<bool>(images),
'showUnit': serializer.toJson<bool>(showUnit), 'showUnit': serializer.toJson<bool>(showUnit),
@ -263,7 +266,7 @@ class Setting extends DataClass implements Insertable<Setting> {
class SettingsCompanion extends UpdateCompanion<Setting> { class SettingsCompanion extends UpdateCompanion<Setting> {
final Value<bool> alarm; final Value<bool> alarm;
final Value<bool> vibrate; final Value<bool> vibrate;
final Value<String> sound; final Value<String?> sound;
final Value<bool> notify; final Value<bool> notify;
final Value<bool> images; final Value<bool> images;
final Value<bool> showUnit; final Value<bool> showUnit;
@ -296,7 +299,7 @@ class SettingsCompanion extends UpdateCompanion<Setting> {
SettingsCompanion.insert({ SettingsCompanion.insert({
required bool alarm, required bool alarm,
required bool vibrate, required bool vibrate,
required String sound, this.sound = const Value.absent(),
required bool notify, required bool notify,
required bool images, required bool images,
required bool showUnit, required bool showUnit,
@ -311,7 +314,6 @@ class SettingsCompanion extends UpdateCompanion<Setting> {
required bool backup, required bool backup,
}) : alarm = Value(alarm), }) : alarm = Value(alarm),
vibrate = Value(vibrate), vibrate = Value(vibrate),
sound = Value(sound),
notify = Value(notify), notify = Value(notify),
images = Value(images), images = Value(images),
showUnit = Value(showUnit), showUnit = Value(showUnit),
@ -325,7 +327,7 @@ class SettingsCompanion extends UpdateCompanion<Setting> {
static Insertable<Setting> custom({ static Insertable<Setting> custom({
Expression<bool>? alarm, Expression<bool>? alarm,
Expression<bool>? vibrate, Expression<bool>? vibrate,
Expression<String>? sound, Expression<String?>? sound,
Expression<bool>? notify, Expression<bool>? notify,
Expression<bool>? images, Expression<bool>? images,
Expression<bool>? showUnit, Expression<bool>? showUnit,
@ -361,7 +363,7 @@ class SettingsCompanion extends UpdateCompanion<Setting> {
SettingsCompanion copyWith( SettingsCompanion copyWith(
{Value<bool>? alarm, {Value<bool>? alarm,
Value<bool>? vibrate, Value<bool>? vibrate,
Value<String>? sound, Value<String?>? sound,
Value<bool>? notify, Value<bool>? notify,
Value<bool>? images, Value<bool>? images,
Value<bool>? showUnit, Value<bool>? showUnit,
@ -403,7 +405,7 @@ class SettingsCompanion extends UpdateCompanion<Setting> {
map['vibrate'] = Variable<bool>(vibrate.value); map['vibrate'] = Variable<bool>(vibrate.value);
} }
if (sound.present) { if (sound.present) {
map['sound'] = Variable<String>(sound.value); map['sound'] = Variable<String?>(sound.value);
} }
if (notify.present) { if (notify.present) {
map['notify'] = Variable<bool>(notify.value); map['notify'] = Variable<bool>(notify.value);
@ -489,8 +491,8 @@ class $SettingsTable extends Settings with TableInfo<$SettingsTable, Setting> {
final VerificationMeta _soundMeta = const VerificationMeta('sound'); final VerificationMeta _soundMeta = const VerificationMeta('sound');
@override @override
late final GeneratedColumn<String?> sound = GeneratedColumn<String?>( late final GeneratedColumn<String?> sound = GeneratedColumn<String?>(
'sound', aliasedName, false, 'sound', aliasedName, true,
type: const StringType(), requiredDuringInsert: true); type: const StringType(), requiredDuringInsert: false);
final VerificationMeta _notifyMeta = const VerificationMeta('notify'); final VerificationMeta _notifyMeta = const VerificationMeta('notify');
@override @override
late final GeneratedColumn<bool?> notify = GeneratedColumn<bool?>( late final GeneratedColumn<bool?> notify = GeneratedColumn<bool?>(
@ -609,8 +611,6 @@ class $SettingsTable extends Settings with TableInfo<$SettingsTable, Setting> {
if (data.containsKey('sound')) { if (data.containsKey('sound')) {
context.handle( context.handle(
_soundMeta, sound.isAcceptableOrUnknown(data['sound']!, _soundMeta)); _soundMeta, sound.isAcceptableOrUnknown(data['sound']!, _soundMeta));
} else if (isInserting) {
context.missing(_soundMeta);
} }
if (data.containsKey('notify')) { if (data.containsKey('notify')) {
context.handle(_notifyMeta, context.handle(_notifyMeta,

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as material; import 'package:flutter/material.dart' as material;
import 'package:flutter/services.dart';
import 'package:fmassive/database.dart'; import 'package:fmassive/database.dart';
import 'package:fmassive/main.dart'; import 'package:fmassive/main.dart';
import 'package:moor_flutter/moor_flutter.dart'; import 'package:moor_flutter/moor_flutter.dart';
@ -18,6 +19,8 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
final TextEditingController _repsController = TextEditingController(); final TextEditingController _repsController = TextEditingController();
final TextEditingController _weightController = TextEditingController(); final TextEditingController _weightController = TextEditingController();
late GymSetsCompanion gymSet; late GymSetsCompanion gymSet;
final nameNode = FocusNode();
final repsNode = FocusNode();
@override @override
void initState() { void initState() {
@ -26,6 +29,17 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
_nameController.text = gymSet.name.value; _nameController.text = gymSet.name.value;
_repsController.text = gymSet.reps.value.toString(); _repsController.text = gymSet.reps.value.toString();
_weightController.text = gymSet.weight.value.toString(); _weightController.text = gymSet.weight.value.toString();
if (gymSet.id.present)
repsNode.requestFocus();
else
nameNode.requestFocus();
}
@override
dispose() {
nameNode.dispose();
repsNode.dispose();
super.dispose();
} }
@override @override
@ -49,7 +63,12 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
children: [ children: [
TextFormField( TextFormField(
controller: _nameController, controller: _nameController,
focusNode: nameNode,
decoration: const InputDecoration(labelText: 'Name'), decoration: const InputDecoration(labelText: 'Name'),
onTap: () {
_nameController.selection = TextSelection(
baseOffset: 0, extentOffset: _nameController.text.length);
},
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
gymSet = gymSet.copyWith(name: Value(value)); gymSet = gymSet.copyWith(name: Value(value));
@ -58,6 +77,11 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
), ),
TextFormField( TextFormField(
controller: _repsController, controller: _repsController,
focusNode: repsNode,
onTap: () {
_repsController.selection = TextSelection(
baseOffset: 0, extentOffset: _repsController.text.length);
},
decoration: const InputDecoration(labelText: 'Reps'), decoration: const InputDecoration(labelText: 'Reps'),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
onChanged: (value) { onChanged: (value) {
@ -71,6 +95,10 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
controller: _weightController, controller: _weightController,
decoration: const InputDecoration(labelText: 'Weight (kg)'), decoration: const InputDecoration(labelText: 'Weight (kg)'),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
onTap: () {
_weightController.selection = TextSelection(
baseOffset: 0, extentOffset: _weightController.text.length);
},
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
gymSet = gymSet =
@ -85,8 +113,11 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
onPressed: () async { onPressed: () async {
if (gymSet.id.present) if (gymSet.id.present)
await db.update(db.gymSets).write(gymSet); await db.update(db.gymSets).write(gymSet);
else else {
await db.into(db.gymSets).insert(gymSet); await db.into(db.gymSets).insert(gymSet);
const platform = MethodChannel('com.massive/android');
platform.invokeMethod('timer', [3000]);
}
if (!mounted) return; if (!mounted) return;
Navigator.pop(context); Navigator.pop(context);
}, },

View File

@ -33,6 +33,7 @@ class RootPage extends State<HomePage> {
void toggleSearch() { void toggleSearch() {
setState(() { setState(() {
if (showSearch) search = '';
showSearch = !showSearch; showSearch = !showSearch;
}); });
focusNode.requestFocus(); focusNode.requestFocus();

View File

@ -4,7 +4,7 @@ import 'package:moor/moor.dart';
Setting defaultSettings = Setting( Setting defaultSettings = Setting(
alarm: true, alarm: true,
vibrate: true, vibrate: true,
sound: 'default.mp3', sound: null,
notify: true, notify: true,
images: true, images: true,
showUnit: true, showUnit: true,
@ -22,7 +22,7 @@ Setting defaultSettings = Setting(
class Settings extends Table { class Settings extends Table {
BoolColumn get alarm => boolean()(); BoolColumn get alarm => boolean()();
BoolColumn get vibrate => boolean()(); BoolColumn get vibrate => boolean()();
TextColumn get sound => text()(); TextColumn get sound => text().nullable()();
BoolColumn get notify => boolean()(); BoolColumn get notify => boolean()();
BoolColumn get images => boolean()(); BoolColumn get images => boolean()();
BoolColumn get showUnit => boolean()(); BoolColumn get showUnit => boolean()();

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as material; import 'package:flutter/material.dart' as material;
import 'package:fmassive/database.dart'; import 'package:fmassive/database.dart';
import 'package:fmassive/main.dart'; import 'package:fmassive/main.dart';
import 'package:fmassive/settings.dart';
import 'package:moor/moor.dart'; import 'package:moor/moor.dart';
class SettingsPage extends StatelessWidget { class SettingsPage extends StatelessWidget {
@ -30,15 +29,9 @@ class _SettingsPageState extends State<_SettingsPage> {
final TextEditingController searchController = TextEditingController(); 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 @override
void initState() { void initState() {
super.initState(); super.initState();
reset();
stream = db.select(db.settings).watchSingle(); stream = db.select(db.settings).watchSingle();
} }