Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2049ee4401 | ||
|
18c071826f
|
|||
|
5bfc301088
|
|||
|
a39853ba5a
|
|||
|
639417e952
|
|||
|
|
9de4def4d4 | ||
|
fa0051b606
|
|||
|
9d867fd888
|
BIN
assets/audio/alert.reversed.wav
Normal file
BIN
assets/audio/alert.reversed.wav
Normal file
Binary file not shown.
BIN
assets/audio/alert.wav
Normal file
BIN
assets/audio/alert.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/audio/messages.wav
Normal file
BIN
assets/audio/messages.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/audio/notification.wav
Normal file
BIN
assets/audio/notification.wav
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -188,7 +188,7 @@ void main() async {
|
||||
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
FlutterNativeSplash.remove();
|
||||
talker.info("[SplashScreen] Now hiding the splash screen...");
|
||||
talker.info("[SplashScreen] Now hiding splash screen...");
|
||||
}
|
||||
|
||||
runApp(
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:audio_session/audio_session.dart';
|
||||
import 'package:dart_midi_pro/dart_midi_pro.dart';
|
||||
|
||||
final sfxPlayerProvider = Provider<AudioPlayer>((ref) {
|
||||
final player = AudioPlayer();
|
||||
@@ -35,7 +31,7 @@ final notificationSfxProvider = FutureProvider<void>((ref) async {
|
||||
final player = ref.watch(sfxPlayerProvider);
|
||||
await player.setVolume(0.75);
|
||||
await player.setAudioSource(
|
||||
AudioSource.asset('assets/audio/notification.mp3'),
|
||||
AudioSource.asset('assets/audio/notification.wav'),
|
||||
preload: true,
|
||||
);
|
||||
});
|
||||
@@ -43,7 +39,7 @@ final notificationSfxProvider = FutureProvider<void>((ref) async {
|
||||
final messageSfxProvider = FutureProvider<void>((ref) async {
|
||||
final player = ref.watch(sfxPlayerProvider);
|
||||
await player.setAudioSource(
|
||||
AudioSource.asset('assets/audio/messages.mp3'),
|
||||
AudioSource.asset('assets/audio/messages.wav'),
|
||||
preload: true,
|
||||
);
|
||||
});
|
||||
@@ -67,110 +63,3 @@ void playMessageSfx(WidgetRef ref) {
|
||||
if (!settings.soundEffects) return;
|
||||
_playSfx('assets/audio/messages.mp3', 0.75);
|
||||
}
|
||||
|
||||
class MiniSampleSynth {
|
||||
final String sampleAsset;
|
||||
final int baseNote; // MIDI note of the sample (usually 72 = C5 for lower pitch playback)
|
||||
|
||||
AudioPlayer? currentPlayer;
|
||||
|
||||
MiniSampleSynth({required this.sampleAsset, this.baseNote = 72});
|
||||
|
||||
Future<void> playMidiAsset(String midiAsset) async {
|
||||
final data = await rootBundle.load(midiAsset);
|
||||
final midi = MidiParser().parseMidiFromBuffer(data.buffer.asUint8List());
|
||||
|
||||
for (final track in midi.tracks) {
|
||||
int currentTick = 0;
|
||||
|
||||
for (final event in track) {
|
||||
currentTick += event.deltaTime;
|
||||
|
||||
if (event is NoteOnEvent && event.velocity > 0) {
|
||||
final note = event.noteNumber;
|
||||
final durationTicks = _estimateDuration(track, event);
|
||||
final durationMs = _ticksToMs(durationTicks, midi);
|
||||
|
||||
_scheduleNote(
|
||||
note: note,
|
||||
startMs: _ticksToMs(currentTick, midi),
|
||||
durationMs: durationMs,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _scheduleNote({
|
||||
required int note,
|
||||
required int startMs,
|
||||
required int durationMs,
|
||||
}) {
|
||||
Future.delayed(Duration(milliseconds: startMs), () async {
|
||||
// Stop any currently playing note
|
||||
if (currentPlayer != null) {
|
||||
await currentPlayer!.stop();
|
||||
await currentPlayer!.dispose();
|
||||
currentPlayer = null;
|
||||
}
|
||||
|
||||
final player = AudioPlayer();
|
||||
currentPlayer = player;
|
||||
|
||||
await player.setAudioSource(AudioSource.asset(sampleAsset));
|
||||
final speed = _noteToSpeed(note);
|
||||
await player.setSpeed(speed);
|
||||
await player.play();
|
||||
|
||||
Future.delayed(Duration(milliseconds: durationMs), () async {
|
||||
if (currentPlayer == player) {
|
||||
await player.stop();
|
||||
await player.dispose();
|
||||
currentPlayer = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
double _noteToSpeed(int note) {
|
||||
return math.pow(2, (note - baseNote) / 12).toDouble();
|
||||
}
|
||||
|
||||
int _getTempo(MidiFile midi) {
|
||||
for (var track in midi.tracks) {
|
||||
for (var event in track) {
|
||||
if (event is SetTempoEvent) {
|
||||
return event.microsecondsPerBeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 500000; // default 120 BPM
|
||||
}
|
||||
|
||||
int _ticksToMs(int ticks, MidiFile midi) {
|
||||
final tempo = _getTempo(midi);
|
||||
final ticksPerBeat = midi.header.ticksPerBeat ?? 480;
|
||||
|
||||
return ((ticks * tempo) / ticksPerBeat / 1000).round();
|
||||
}
|
||||
|
||||
int _estimateDuration(List<MidiEvent> track, NoteOnEvent on) {
|
||||
int ticks = 0;
|
||||
bool started = false;
|
||||
|
||||
for (final e in track) {
|
||||
if (e == on) {
|
||||
started = true;
|
||||
continue;
|
||||
}
|
||||
if (!started) continue;
|
||||
|
||||
ticks += e.deltaTime;
|
||||
|
||||
if (e is NoteOffEvent && e.noteNumber == on.noteNumber) {
|
||||
return ticks;
|
||||
}
|
||||
}
|
||||
return 200; // fallback
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ final class AppSettingsNotifierProvider
|
||||
}
|
||||
|
||||
String _$appSettingsNotifierHash() =>
|
||||
r'0a7f75bd95850b0c564b29c57912ec8fcac53f09';
|
||||
r'fc474771ced89ec8637c0f773a9c6bc392f0df60';
|
||||
|
||||
abstract class _$AppSettingsNotifier extends $Notifier<AppSettings> {
|
||||
AppSettings build();
|
||||
|
||||
@@ -261,11 +261,11 @@ class FileListScreen extends HookConsumerWidget {
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => SheetScaffold(
|
||||
titleText: 'Usage Overview',
|
||||
child: UsageOverviewWidget(
|
||||
usage: usage,
|
||||
quota: quota,
|
||||
).padding(horizontal: 8, vertical: 16),
|
||||
titleText: 'Usage Overview',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@ import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/main.dart';
|
||||
import 'package:island/models/account.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/notification.dart';
|
||||
import 'package:island/talker.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:top_snackbar_flutter/top_snack_bar.dart';
|
||||
@@ -241,7 +243,22 @@ bool closeTopmostOverlayDialog() {
|
||||
|
||||
const kDialogMaxWidth = 480.0;
|
||||
|
||||
Future<void> _playSfx(String assetPath, double volume) async {
|
||||
final player = AudioPlayer();
|
||||
await player.setVolume(volume);
|
||||
await player.setAudioSource(AudioSource.asset(assetPath));
|
||||
await player.play();
|
||||
await player.dispose();
|
||||
}
|
||||
|
||||
void showErrorAlert(dynamic err, {IconData? icon}) {
|
||||
final context = globalOverlay.currentState!.context;
|
||||
final ref = ProviderScope.containerOf(context);
|
||||
final settings = ref.read(appSettingsProvider);
|
||||
if (settings.soundEffects) {
|
||||
unawaited(_playSfx('assets/audio/alert.reversed.wav', 0.75));
|
||||
}
|
||||
|
||||
if (err is Error) {
|
||||
talker.error('Something went wrong...', err, err.stackTrace);
|
||||
}
|
||||
@@ -292,6 +309,13 @@ void showErrorAlert(dynamic err, {IconData? icon}) {
|
||||
}
|
||||
|
||||
void showInfoAlert(String message, String title, {IconData? icon}) {
|
||||
final context = globalOverlay.currentState!.context;
|
||||
final ref = ProviderScope.containerOf(context);
|
||||
final settings = ref.read(appSettingsProvider);
|
||||
if (settings.soundEffects) {
|
||||
unawaited(_playSfx('assets/audio/alert.wav', 0.75));
|
||||
}
|
||||
|
||||
showOverlayDialog<void>(
|
||||
builder: (context, close) => ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: kDialogMaxWidth),
|
||||
@@ -333,6 +357,13 @@ Future<bool> showConfirmAlert(
|
||||
IconData? icon,
|
||||
bool isDanger = false,
|
||||
}) async {
|
||||
final context = globalOverlay.currentState!.context;
|
||||
final ref = ProviderScope.containerOf(context);
|
||||
final settings = ref.read(appSettingsProvider);
|
||||
if (settings.soundEffects) {
|
||||
unawaited(_playSfx('assets/audio/alert.wav', 0.75));
|
||||
}
|
||||
|
||||
final result = await showOverlayDialog<bool>(
|
||||
builder: (context, close) => ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: kDialogMaxWidth),
|
||||
@@ -422,3 +453,4 @@ Future<void> openExternalLink(Uri url, WidgetRef ref) async {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/audio.dart';
|
||||
import 'package:island/pods/message.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/services/update_service.dart';
|
||||
@@ -23,7 +22,7 @@ Future<void> _showSetTokenDialog(BuildContext context, WidgetRef ref) async {
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Set Access Token'),
|
||||
title: const Text('Set access token'),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
decoration: const InputDecoration(
|
||||
@@ -131,17 +130,26 @@ class DebugSheet extends HookConsumerWidget {
|
||||
const Divider(height: 8),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.play_arrow),
|
||||
leading: const Icon(Symbols.error),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('Play untitled'),
|
||||
onTap: () async {
|
||||
final synth = MiniSampleSynth(
|
||||
sampleAsset: 'assets/audio/messages.mp3',
|
||||
baseNote: 60,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
title: const Text('Test error alert'),
|
||||
onTap: () {
|
||||
showErrorAlert(
|
||||
'This is a test error message for debugging purposes.',
|
||||
);
|
||||
await synth.playMidiAsset(
|
||||
'assets/midi/never-gonna-give-you-up.mid',
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.info),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
title: const Text('Test info alert'),
|
||||
onTap: () {
|
||||
showInfoAlert(
|
||||
'This is a test info message for debugging purposes.',
|
||||
'Test Alert',
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -102,16 +102,16 @@ class FileListView extends HookConsumerWidget {
|
||||
useEffect(() {
|
||||
// Sync query, order, and orderDesc filters
|
||||
if (mode.value == FileListMode.unindexed) {
|
||||
unindexedNotifier.setQuery(this.query.value);
|
||||
unindexedNotifier.setQuery(query.value);
|
||||
unindexedNotifier.setOrder(order.value);
|
||||
unindexedNotifier.setOrderDesc(orderDesc.value);
|
||||
} else {
|
||||
cloudNotifier.setQuery(this.query.value);
|
||||
cloudNotifier.setQuery(query.value);
|
||||
cloudNotifier.setOrder(order.value);
|
||||
cloudNotifier.setOrderDesc(orderDesc.value);
|
||||
}
|
||||
return null;
|
||||
}, [this.query.value, order.value, orderDesc.value, mode.value]);
|
||||
}, [query.value, order.value, orderDesc.value, mode.value]);
|
||||
|
||||
final isRefreshing = ref.watch(
|
||||
mode.value == FileListMode.normal
|
||||
|
||||
32
pubspec.lock
32
pubspec.lock
@@ -313,14 +313,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
console:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: console
|
||||
sha256: e04e7824384c5b39389acdd6dc7d33f3efe6b232f6f16d7626f194f6a01ad69a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
convert:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -417,14 +409,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
dart_midi_pro:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dart_midi_pro
|
||||
sha256: "9a0273c92c0336e5694c7318fd936f64f06e938a936dd5fac6563c39954b7f6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4+2"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1253,14 +1237,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
get_it:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: get_it
|
||||
sha256: ae78de7c3f2304b8d81f2bb6e320833e5e81de942188542328f074978cc0efa9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.3.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1861,14 +1837,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
msix:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: msix
|
||||
sha256: f88033fcb9e0dd8de5b18897cbebbd28ea30596810f4a7c86b12b0c03ace87e5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.16.12"
|
||||
native_exif:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
15
pubspec.yaml
15
pubspec.yaml
@@ -19,7 +19,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
version: 3.5.0+164
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.0
|
||||
sdk: ">3.10.0"
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
@@ -174,7 +174,6 @@ dependencies:
|
||||
video_thumbnail: ^0.5.6
|
||||
just_audio: ^0.10.5
|
||||
audio_session: ^0.2.2
|
||||
dart_midi_pro: ^1.0.4+2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -194,7 +193,6 @@ dev_dependencies:
|
||||
riverpod_lint: ^3.1.0
|
||||
drift_dev: ^2.30.1
|
||||
flutter_launcher_icons: ^0.14.4
|
||||
msix: ^3.16.12
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
@@ -208,7 +206,6 @@ flutter:
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/midi/
|
||||
- assets/i18n/
|
||||
- assets/images/
|
||||
- assets/images/oidc/
|
||||
@@ -267,13 +264,3 @@ flutter_native_splash:
|
||||
image_dark: "assets/icons/icon-dark.png"
|
||||
color: "#ffffff"
|
||||
color_dark: "#121212"
|
||||
|
||||
msix_config:
|
||||
display_name: Solian
|
||||
publisher_display_name: Solsynth LLC
|
||||
identity_name: dev.solian.app
|
||||
msix_version: 3.2.0.0
|
||||
logo_path: .\assets\icons\icon.png
|
||||
protocol_activation: solian, https
|
||||
app_uri_handler_hosts: solian.app
|
||||
capabilities: internetClientServer, location, microphone, webcam
|
||||
|
||||
Reference in New Issue
Block a user