mirror of
https://github.com/bytedream/Yamete-Kudasai.git
synced 2025-06-27 08:40:33 +02:00
Initial commit
This commit is contained in:
84
lib/background.dart
Normal file
84
lib/background.dart
Normal file
@ -0,0 +1,84 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||
import 'package:port_update/port_update.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
final actions = {
|
||||
UpdateAction.batteryCharging: "Battery charging",
|
||||
UpdateAction.batteryDischarging: "Battery discharging",
|
||||
UpdateAction.batteryFull: "Battery full",
|
||||
UpdateAction.headphoneConnected: "Headphone connected",
|
||||
UpdateAction.headphoneDisconnected: "Headphone disconnected",
|
||||
};
|
||||
|
||||
final _player = AudioCache(prefix: '');
|
||||
final _portUpdate = PortUpdate();
|
||||
|
||||
String generateEventData(SharedPreferences sharedPreferences) {
|
||||
Map<String, String> data = {};
|
||||
|
||||
for (var action in UpdateAction.values) {
|
||||
if (sharedPreferences.getBool("${action.index}.activated") ?? true) {
|
||||
data[action.index.toString()] = sharedPreferences.getString("${action.index}.target") ?? "assets/audio/yamete_kudasai.mp3";
|
||||
}
|
||||
}
|
||||
return jsonEncode(data);
|
||||
}
|
||||
|
||||
void initBackground() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
FlutterBackgroundService().setNotificationInfo(
|
||||
title: "Yamete Kudasai",
|
||||
content: "Preparing",
|
||||
);
|
||||
|
||||
Map<UpdateAction, String>? data;
|
||||
int running = 0;
|
||||
|
||||
StreamSubscription<UpdateAction?>? sub = _portUpdate.stream.listen((event) async {
|
||||
data ??= (jsonDecode(generateEventData(await SharedPreferences.getInstance())) as Map<String, dynamic>)
|
||||
.map((key, value) => MapEntry(UpdateAction.values.elementAt(int.parse(key)), value as String));
|
||||
if (data!.containsKey(event!)) {
|
||||
final player = await _player.play(data![event]!);
|
||||
running++;
|
||||
FlutterBackgroundService().setNotificationInfo(
|
||||
title: 'Yamete Kudasai',
|
||||
content: 'Dispatching ${actions.values.elementAt(event.index).toLowerCase()} event'
|
||||
);
|
||||
await player.onPlayerCompletion.first;
|
||||
if (--running == 0) {
|
||||
FlutterBackgroundService().setNotificationInfo(
|
||||
title: 'Yamete Kudasai',
|
||||
content: 'Running'
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
FlutterBackgroundService().setNotificationInfo(
|
||||
title: "Yamete Kudasai",
|
||||
content: "Running",
|
||||
);
|
||||
|
||||
FlutterBackgroundService().onDataReceived.listen((event) async {
|
||||
switch (event!['action']) {
|
||||
case 'stop':
|
||||
await sub.cancel();
|
||||
FlutterBackgroundService().stopBackgroundService();
|
||||
break;
|
||||
case 'data':
|
||||
data = (jsonDecode(event['value']) as Map<String, dynamic>).map((key, value) {
|
||||
return MapEntry(UpdateAction.values.elementAt(int.parse(key)), value.toString());
|
||||
});
|
||||
break;
|
||||
case 'ping':
|
||||
FlutterBackgroundService().sendData({'action': 'pong'});
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
87
lib/choose_audio.dart
Normal file
87
lib/choose_audio.dart
Normal file
@ -0,0 +1,87 @@
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final audio = {
|
||||
'Airi\'s first tutoring lesson': 'assets/audio/airis_first_tutoring_lesson.mp3',
|
||||
'The helpful pharmacist': 'assets/audio/the_helpful_pharmacist.mp3',
|
||||
'Yamete Kudasai': 'assets/audio/yamete_kudasai.mp3'
|
||||
};
|
||||
|
||||
class ChooseAudio extends StatefulWidget {
|
||||
String _before;
|
||||
|
||||
ChooseAudio(this._before, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ChooseAudioState();
|
||||
}
|
||||
|
||||
class _ChooseAudioState extends State<ChooseAudio> {
|
||||
final _player = AudioCache(prefix: '');
|
||||
int _playIndex = -1;
|
||||
AudioPlayer? _playing;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
Navigator.pop(context, widget._before);
|
||||
return true;
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Choose audio')
|
||||
),
|
||||
body: ListView.separated(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
MapEntry<String, String> item = audio.entries.elementAt(index);
|
||||
if (index == _playIndex) {
|
||||
if (_playing == null) {
|
||||
play(item.value);
|
||||
} else {
|
||||
_playing!.stop().then((value) => play(item.value));
|
||||
}
|
||||
}
|
||||
return ListTile(
|
||||
leading: Radio(
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
value: item.value,
|
||||
groupValue: widget._before,
|
||||
onChanged: (String? value) {
|
||||
setState(() {
|
||||
widget._before = value!;
|
||||
});
|
||||
}),
|
||||
title: Text(item.key),
|
||||
trailing: Icon(_playIndex == index ? Icons.stop_outlined : Icons.play_arrow_outlined),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_playIndex = index;
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) => const Divider(),
|
||||
itemCount: audio.length
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_playing != null) {
|
||||
_playing!.stop();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void play(String path) async {
|
||||
_playing = await _player.play(path);
|
||||
_playing!.onPlayerCompletion.listen((event) {
|
||||
setState(() {
|
||||
_playIndex = -1;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
190
lib/main.dart
Normal file
190
lib/main.dart
Normal file
@ -0,0 +1,190 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:yamete_kudasai/background.dart';
|
||||
|
||||
import 'choose_audio.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
runApp(YameteKudasai());
|
||||
}
|
||||
|
||||
class YameteKudasai extends StatefulWidget {
|
||||
@override
|
||||
_YameteKudasaiState createState() => _YameteKudasaiState();
|
||||
}
|
||||
|
||||
class _YameteKudasaiState extends State<YameteKudasai> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
http.get(Uri.https('api.github.com', 'repos/ByteDream/yamete_kudasai/releases/latest'))
|
||||
.timeout(const Duration(seconds: 5), onTimeout: () => http.Response.bytes([], 504)).then((response) async {
|
||||
if (response.statusCode == 200) {
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final tag = (jsonDecode(response.body) as Map<String, dynamic>)['tag_name'] as String;
|
||||
if (int.parse(tag.substring(1).replaceAll('.', '')) > int.parse(packageInfo.version.replaceAll('.', ''))) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) => _buildUpdateNotification(context, tag)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
FlutterBackgroundService.initialize(initBackground, foreground: false);
|
||||
|
||||
return MaterialApp(
|
||||
title: "Yamete Kudasai",
|
||||
theme: ThemeData.from(
|
||||
colorScheme: const ColorScheme.highContrastDark(
|
||||
primary: Color(0xFFFF0000),
|
||||
primaryVariant: Color(0xFFC20000),
|
||||
secondary: Colors.purple,
|
||||
surface: Colors.black,
|
||||
background: Colors.black26,
|
||||
onPrimary: Colors.white,
|
||||
),
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Yamete Kudasai'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
child: Row(
|
||||
children: const [
|
||||
Text("Start"),
|
||||
Icon(Icons.play_arrow_outlined)
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
FlutterBackgroundService().sendData({'action': 'stop'});
|
||||
while (await isRunning()) {}
|
||||
FlutterBackgroundService.initialize(initBackground);
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
child: Row(
|
||||
children: const [
|
||||
Text("Stop"),
|
||||
Icon(Icons.stop_outlined)
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
FlutterBackgroundService().sendData({'action': 'stop'});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(color: Colors.white),
|
||||
_buildAudioSettings()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUpdateNotification(BuildContext context, String tag) {
|
||||
return AlertDialog(
|
||||
title: Text('Newer version is available ($tag)'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (await canLaunch('https://github.com/ByteDream/yamete_kudasai/releases/tag/$tag')) {
|
||||
await launch('https://github.com/ByteDream/yamete_kudasai/releases/tag/$tag');
|
||||
}
|
||||
},
|
||||
child: const Text('Show new release')
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('No thanks :)')
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAudioSettings() {
|
||||
return FutureBuilder(
|
||||
future: SharedPreferences.getInstance(),
|
||||
builder: (BuildContext context, AsyncSnapshot<SharedPreferences> snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final prefs = snapshot.data!;
|
||||
final entries = actions.entries;
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: actions.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = entries.elementAt(index);
|
||||
|
||||
final activatedKey = "${item.key.index}.activated";
|
||||
final targetKey = "${item.key.index}.target";
|
||||
|
||||
final activatedAudio = prefs.getBool(activatedKey) ?? true;
|
||||
final targetAudio = prefs.getString(targetKey) ?? "assets/audio/yamete_kudasai.mp3";
|
||||
|
||||
return ListTile(
|
||||
title: Text(item.value),
|
||||
subtitle: Text(audio.entries.firstWhere((element) => element.value == targetAudio).key),
|
||||
trailing: Switch(
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
value: prefs.getBool(activatedKey) ?? true,
|
||||
onChanged: (bool newValue) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setBool(activatedKey, !activatedAudio);
|
||||
FlutterBackgroundService().sendData({'action': 'data', 'value': generateEventData(prefs)});
|
||||
setState(() {});
|
||||
}),
|
||||
onTap: () async {
|
||||
final audioFile = await Navigator.push<String>(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => ChooseAudio(targetAudio)
|
||||
)
|
||||
);
|
||||
if (audioFile != null && audioFile != targetAudio) {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString(targetKey, audioFile);
|
||||
FlutterBackgroundService().sendData({'action': 'data', 'value': generateEventData(prefs)});
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> isRunning() async {
|
||||
try {
|
||||
FlutterBackgroundService().sendData({'action': 'ping'});
|
||||
await FlutterBackgroundService().onDataReceived.first.timeout(const Duration(milliseconds: 500));
|
||||
return true;
|
||||
} on Exception catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user