Compare commits

..

8 Commits

Author SHA1 Message Date
LittleSheep
2049ee4401 🔀 Merge pull request #238 from BluewhaleYF/v3
彻底完成文言界面翻译
2026-01-18 23:53:00 +08:00
18c071826f 💄 Play sfx on confirm dialog as well 2026-01-18 18:17:47 +08:00
5bfc301088 🗑️ Clean unused code 2026-01-18 14:25:11 +08:00
a39853ba5a :drunk: Wrote some useless code 2026-01-18 14:16:29 +08:00
639417e952 :drunk: Werid miniapp runtime 2026-01-18 13:31:45 +08:00
Flint Scophire
9de4def4d4 Add files via upload
彻底完成文言界面翻译
2026-01-18 11:27:44 +08:00
fa0051b606 Sound effects on alert 2026-01-18 01:25:07 +08:00
9d867fd888 🍱 Update audio assets 2026-01-18 01:14:06 +08:00
16 changed files with 626 additions and 744 deletions

Binary file not shown.

BIN
assets/audio/alert.wav Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/audio/messages.wav Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -85,7 +85,7 @@ final class AppSettingsNotifierProvider
}
String _$appSettingsNotifierHash() =>
r'0a7f75bd95850b0c564b29c57912ec8fcac53f09';
r'fc474771ced89ec8637c0f773a9c6bc392f0df60';
abstract class _$AppSettingsNotifier extends $Notifier<AppSettings> {
AppSettings build();

View File

@@ -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',
),
);
}

View File

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

View File

@@ -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',
);
},
),

View File

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

View File

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

View File

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