Yamete-Kudasai/lib/main.dart
2021-11-20 02:27:48 +01:00

328 lines
11 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:future_progress_dialog/future_progress_dialog.dart';
import 'package:http/http.dart' as http;
import 'package:open_file/open_file.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:yamete_kudasai/background.dart';
import 'package:yamete_kudasai/utils.dart';
import 'choose_audio.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
runApp(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.black12,
onPrimary: Colors.white,
),
),
home: YameteKudasai(),
));
}
class YameteKudasai extends StatefulWidget {
@override
_YameteKudasaiState createState() => _YameteKudasaiState();
}
class _YameteKudasaiState extends State<YameteKudasai> {
@override
Widget build(BuildContext context) {
FlutterBackgroundService.initialize(initBackground, foreground: false);
checkUpdate(context).then((value) => {
if (!value) {
checkFirstLaunch(context)
}
});
return 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),
FutureBuilder(
future: Sauce.sauceIndex(),
builder: (BuildContext context, AsyncSnapshot<Map<String, Sauce>> snapshot) {
if (!snapshot.hasData) {
return SizedBox.shrink();
}
return _buildAudioSettings(snapshot.data!);
}
)
],
),
),
);
}
Widget _buildUpdateNotification(BuildContext context, String tag, String apkUrl) {
return AlertDialog(
backgroundColor: Colors.black,
title: Text('Newer version is available ($tag)'),
actions: [
TextButton(
onPressed: () async {
await updateAPK(context, apkUrl);
await showDialog(
context: context,
builder: (BuildContext context) => FutureProgressDialog(
updateAPK(context, apkUrl),
decoration: const BoxDecoration(
color: Colors.transparent
),
message: const Text('Downloading update...'),
)
);
},
child: const Text('Update')
),
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(Map<String, Sauce> sauceIndex) {
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(sauceIndex.entries.firstWhere((element) => element.key == targetAudio).value.alias),
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 sauce = await Sauce.sauceIndex();
final audioFile = await Navigator.push<String>(
context,
MaterialPageRoute(
builder: (BuildContext context) => ChooseAudio(targetAudio, sauce)
)
);
if (audioFile != null && audioFile != targetAudio) {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(targetKey, audioFile);
FlutterBackgroundService().sendData({'action': 'data', 'value': generateEventData(prefs)});
setState(() {});
}
},
);
}
);
}
);
}
Future<bool> checkUpdate(BuildContext context) async {
final response = await http.get(Uri.https('api.github.com', 'repos/ByteDream/Yamete-Kudasai/releases/latest'))
.timeout(const Duration(seconds: 5), onTimeout: () => http.Response.bytes([], 504));
if (response.statusCode == 200) {
final packageInfo = await PackageInfo.fromPlatform();
final json = (jsonDecode(response.body) as Map<String, dynamic>);
final tag = json['tag_name'] as String;
final apkUrl = json['assets'][0]['browser_download_url'] as String;
if (int.parse(tag.substring(1).replaceAll('.', '')) > int.parse(packageInfo.version.replaceAll('.', ''))) {
await showDialog(
context: context,
builder: (BuildContext context) => _buildUpdateNotification(context, tag, apkUrl)
);
return true;
}
}
return false;
}
Future<void> updateAPK(BuildContext context, String apkUrl) async {
ResultType result;
if ((await Permission.storage.request()).isGranted) {
final file = File('/storage/emulated/0/Download/${apkUrl.split('/').last}');
final completer = Completer<void>();
showDialog(
context: context,
builder: (BuildContext context) {
return FutureProgressDialog(
completer.future,
decoration: const BoxDecoration(
color: Colors.transparent
),
message: const Text('Downloading update...'),
);
}
);
if (!(await file.exists())) {
final resp = await http.get(Uri.parse(apkUrl));
await file.writeAsBytes(resp.bodyBytes);
}
result = (await OpenFile.open(file.path)).type;
completer.complete();
} else {
result = ResultType.error;
}
if (result != ResultType.done) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.black,
title: const Text('Failed to install update'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Ok'),
)
],
);
}
);
}
Navigator.pop(context);
// await file.delete();
}
Future<void> checkFirstLaunch(BuildContext context) async {
final prefs = await SharedPreferences.getInstance();
final packageInfo = await PackageInfo.fromPlatform();
final lastVersion = prefs.getString("version");
final currentVersion = packageInfo.version;
if ((lastVersion ?? "") != currentVersion) {
final updateIndex = await Update.updatesIndex();
await showDialog(
context: context,
builder: (BuildContext context) => _buildUpdateNotice(context, updateIndex[currentVersion]!)
);
await prefs.setString("version", currentVersion);
}
}
Widget _buildUpdateNotice(BuildContext context, Update update) {
return AlertDialog(
backgroundColor: Colors.black,
title: Text('Updated to ${update.version}'),
actions: [
TextButton(
onPressed: () async {
final url = 'https://github.com/ByteDream/Yamete-Kudasai/releases/tag/v${update.version}';
if (await canLaunch(url)) {
await launch(url);
}
},
child: const Text('See more...')
),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Ok'),
)
],
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(update.summary),
Text(' » ${update.details.join("\n » ")}')
],
)
);
}
Future<bool> isRunning() async {
try {
FlutterBackgroundService().sendData({'action': 'ping'});
await FlutterBackgroundService().onDataReceived.first.timeout(const Duration(milliseconds: 500));
return true;
} on Exception {
return false;
}
}
}