Compare commits

..

No commits in common. "master" and "v1.0.0" have entirely different histories.

12 changed files with 187 additions and 493 deletions

View File

@ -1,33 +1,13 @@
# Yamete Kudasai # Yamete Kudasai
<p align="center"> Cute anime girl moaning when something is plugged in.
<a href="https://smartrelease.bytedream.org/github/ByteDream/Yamete-Kudasai/yamete_kudasai-{tag}.apk">
<img src="https://img.shields.io/github/downloads/ByteDream/Yamete-Kudasai/total?style=flat-square" alt="Download Badge">
</a>
<a href="https://github.com/ByteDream/Yamete-Kudasai/releases/latest">
<img src="https://img.shields.io/github/v/release/ByteDream/Yamete-Kudasai?style=flat-square" alt="Latest release">
</a>
<a href="https://github.com/ByteDream/Yamete-Kudasai/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/ByteDream/Yamete-Kudasai?style=flat-square" alt="License">
</a>
<a href="#">
<img src="https://img.shields.io/github/languages/top/ByteDream/Yamete-Kudasai?style=flat-square" alt="Top language">
</a>
</p>
<h5 align="center">Cute anime girls moaning when something is plugged in.</h5>
## Getting Started ## Getting Started
> Android only > Android only
**Download the latest release [here](https://smartrelease.bytedream.org/github/ByteDream/Yamete-Kudasai/yamete_kudasai-{tag}.apk).**
I think the sentence above says all about the app... I think the sentence above says all about the app...
It basically plays a sound whenever a event from the supported events below gets fired.
The sounds are predefined within the app and can easily be changed.
<img src="ext/preview_1.png" width=30%> <img src="ext/preview_2.png" width=30%> <img src="ext/preview_3.png" width=30%> <img src="ext/preview_1.png" width=30%> <img src="ext/preview_2.png" width=30%> <img src="ext/preview_3.png" width=30%>
@ -38,8 +18,8 @@ and a new window pops up where you can set the sound which should be played on t
Supported events: Supported events:
- **Battery** - **Battery**
- Battery charging (cable plugged in) - Battery charging
- Battery discharging (cable plugged out) - Battery discharging
- Battery full - Battery full
- **Headphone** - **Headphone**
- Headphone connected - Headphone connected
@ -49,4 +29,3 @@ Supported events:
This project is licensed under the Do What The F*ck You Want To Public License (WTFPL) - see the [LICENSE](LICENSE) file for more details. This project is licensed under the Do What The F*ck You Want To Public License (WTFPL) - see the [LICENSE](LICENSE) file for more details.
All rights for the audio and image files in [assets/audio](assets/audio), [assets/icon](assets/icon) and [android/app/src/main/res](android/app/src/main/res) are reserved to their artist and copyright holders.

View File

@ -32,7 +32,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 31 compileSdkVersion 30
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -43,7 +43,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "org.bytedream.yamete_kudasai" applicationId "org.bytedream.yamete_kudasai"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 31 targetSdkVersion 30
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
multiDexEnabled true multiDexEnabled true

View File

@ -1,9 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.bytedream.yamete_kudasai"> package="org.bytedream.yamete_kudasai">
<application <application
android:label="Yamete Kudasai" android:label="Yamete Kudasai"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
@ -15,18 +15,18 @@
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. --> to determine the Window background behind the Flutter UI. -->
<meta-data <meta-data
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme"
/> />
<!-- Displays an Android View that continues showing the launch screen <!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of gap between the end of Android's launch screen and the painting of
Flutter's first frame. --> Flutter's first frame. -->
<meta-data <meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable" android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" android:resource="@drawable/launch_background"
/> />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
@ -38,15 +38,22 @@
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
</application> </application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<queries> <queries>
<!-- If your app opens https URLs --> <!-- If your app opens https URLs -->
<intent> <intent>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<data android:scheme="https" /> <data android:scheme="https" />
</intent> </intent>
<!-- If your app makes calls -->
<intent>
<action android:name="android.intent.action.DIAL" />
<data android:scheme="tel" />
</intent>
<!-- If your app emails -->
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="*/*" />
</intent>
</queries> </queries>
</manifest> </manifest>

View File

@ -1,26 +0,0 @@
{
"assets/audio/sanas_first_tutoring_lesson.mp3": {
"alias": "Sana's first tutoring lesson",
"name": "Oni ChiChi",
"season": "1",
"episode": "1",
"from": "0:03",
"to": "0:06"
},
"assets/audio/the_helpful_pharmacist.mp3": {
"alias": "The helpful pharmacist",
"name": "Rune's Pharmacy: Tiarajima no Okusuriya-san",
"season": null,
"episode": "2",
"from": "5:55",
"to": "5:59"
},
"assets/audio/yamete_kudasai.mp3": {
"alias": "Yamete Kudasai",
"name": "Yamete Kudasai",
"season": null,
"episode": null,
"from": null,
"to": null
}
}

View File

@ -1,24 +0,0 @@
{
"1.0.0": {
"summary": "Initial release",
"details": [
"Initial release"
]
},
"1.1.0": {
"summary": "Update check & bug fixing",
"details": [
"Update check",
"Permanent notification changes",
"No sound is played anymore when starting the app"
]
},
"1.2.0": {
"summary": "Sauce & update list",
"details": [
"Added sauce information",
"Added notification after updated",
"Renamed one audio"
]
}
}

View File

@ -38,13 +38,25 @@ void initBackground() {
); );
Map<UpdateAction, String>? data; Map<UpdateAction, String>? data;
int running = 0;
StreamSubscription<UpdateAction?>? sub = _portUpdate.stream.listen((event) async { StreamSubscription<UpdateAction?>? sub = _portUpdate.stream.listen((event) async {
data ??= (jsonDecode(generateEventData(await SharedPreferences.getInstance())) as Map<String, dynamic>) data ??= (jsonDecode(generateEventData(await SharedPreferences.getInstance())) as Map<String, dynamic>)
.map((key, value) => MapEntry(UpdateAction.values.elementAt(int.parse(key)), value as String)); .map((key, value) => MapEntry(UpdateAction.values.elementAt(int.parse(key)), value as String));
if (data!.containsKey(event!)) { if (data!.containsKey(event!)) {
final player = await _player.play(data![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; await player.onPlayerCompletion.first;
if (--running == 0) {
FlutterBackgroundService().setNotificationInfo(
title: 'Yamete Kudasai',
content: 'Running'
);
}
} }
}); });

View File

@ -1,14 +1,16 @@
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:yamete_kudasai/utils.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 { class ChooseAudio extends StatefulWidget {
String _before; String _before;
final Map<String, Sauce> _sauce;
ChooseAudio(this._before, this._sauce, {Key? key}) : super(key: key); ChooseAudio(this._before, {Key? key}) : super(key: key);
@override @override
State<StatefulWidget> createState() => _ChooseAudioState(); State<StatefulWidget> createState() => _ChooseAudioState();
@ -31,44 +33,36 @@ class _ChooseAudioState extends State<ChooseAudio> {
title: const Text('Choose audio') title: const Text('Choose audio')
), ),
body: ListView.separated( body: ListView.separated(
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
MapEntry<String, Sauce> item = widget._sauce.entries.elementAt(index); MapEntry<String, String> item = audio.entries.elementAt(index);
if (index == _playIndex) { if (index == _playIndex) {
if (_playing == null) { if (_playing == null) {
play(item.key); play(item.value);
} else { } else {
_playing!.stop().then((value) => play(item.key)); _playing!.stop().then((value) => play(item.value));
}
} }
return ListTile( }
leading: Radio( return ListTile(
activeColor: Theme.of(context).colorScheme.secondary, leading: Radio(
value: item.value.filepath, activeColor: Theme.of(context).colorScheme.secondary,
groupValue: widget._before, value: item.value,
onChanged: (String? value) { groupValue: widget._before,
setState(() { onChanged: (String? value) {
widget._before = value!;
});
}),
title: Text(item.value.alias),
trailing: IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) => _buildSauceInfo(context, item.value)
);
},
icon: Icon(Icons.info)
),
onTap: () {
setState(() { setState(() {
_playIndex = index; widget._before = value!;
}); });
}, }),
); title: Text(item.key),
}, trailing: Icon(_playIndex == index ? Icons.stop_outlined : Icons.play_arrow_outlined),
separatorBuilder: (BuildContext context, int index) => const Divider(), onTap: () {
itemCount: widget._sauce.length setState(() {
_playIndex = index;
});
},
);
},
separatorBuilder: (BuildContext context, int index) => const Divider(),
itemCount: audio.length
), ),
), ),
); );
@ -82,40 +76,6 @@ class _ChooseAudioState extends State<ChooseAudio> {
super.dispose(); super.dispose();
} }
Widget _buildSauceInfo(BuildContext context, Sauce sauce) {
return AlertDialog(
backgroundColor: Colors.black,
title: Text('${sauce.alias} sauce'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Name: ${sauce.name}'),
Text('Season: ${sauce.season ?? "?"}'),
Text('Episode: ${sauce.episode ?? "?"}'),
Text('Audio time: ${sauce.from ?? "?"} - ${sauce.to ?? "?"}'),
],
),
actions: [
TextButton(
onPressed: () async {
final search = 'https://hentaihaven.com/?s=${sauce.name.replaceAll(" ", "+")}';
if (await canLaunch(search)) {
await launch(search);
}
},
child: const Text('Search online')
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Ok')
)
],
);
}
void play(String path) async { void play(String path) async {
_playing = await _player.play(path); _playing = await _player.play(path);
_playing!.onPlayerCompletion.listen((event) { _playing!.onPlayerCompletion.listen((event) {

View File

@ -1,40 +1,21 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_background_service/flutter_background_service.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:http/http.dart' as http;
import 'package:open_file/open_file.dart';
import 'package:package_info_plus/package_info_plus.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:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:yamete_kudasai/background.dart'; import 'package:yamete_kudasai/background.dart';
import 'package:yamete_kudasai/utils.dart';
import 'choose_audio.dart'; import 'choose_audio.dart';
void main() { void main() {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
runApp(MaterialApp( runApp(YameteKudasai());
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 { class YameteKudasai extends StatefulWidget {
@ -45,107 +26,103 @@ class YameteKudasai extends StatefulWidget {
class _YameteKudasaiState extends State<YameteKudasai> { class _YameteKudasaiState extends State<YameteKudasai> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
FlutterBackgroundService.initialize(initBackground, foreground: false); http.get(Uri.https('api.github.com', 'repos/ByteDream/yamete_kudasai/releases/latest'))
checkUpdate(context).then((value) => { .timeout(const Duration(seconds: 5), onTimeout: () => http.Response.bytes([], 504)).then((response) async {
if (!value) { if (response.statusCode == 200) {
checkFirstLaunch(context) 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 Scaffold( return MaterialApp(
appBar: AppBar( title: "Yamete Kudasai",
title: const Text('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,
),
), ),
body: Center( home: Scaffold(
child: Column( appBar: AppBar(
children: [ title: const Text('Yamete Kudasai'),
Padding( ),
padding: const EdgeInsets.symmetric(vertical: 20), body: Center(
child: Row( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
children: [ Padding(
ElevatedButton( padding: const EdgeInsets.symmetric(vertical: 20),
child: Row( child: Row(
children: const [ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
Text("Start"), children: [
Icon(Icons.play_arrow_outlined) 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);
},
), ),
onPressed: () async { ElevatedButton(
WidgetsFlutterBinding.ensureInitialized(); child: Row(
FlutterBackgroundService().sendData({'action': 'stop'}); children: const [
while (await isRunning()) {} Text("Stop"),
FlutterBackgroundService.initialize(initBackground); Icon(Icons.stop_outlined)
}, ],
), ),
ElevatedButton( onPressed: () {
child: Row( FlutterBackgroundService().sendData({'action': 'stop'});
children: const [ },
Text("Stop"), )
Icon(Icons.stop_outlined) ],
], ),
),
onPressed: () {
FlutterBackgroundService().sendData({'action': 'stop'});
},
)
],
), ),
), const Divider(color: Colors.white),
const Divider(color: Colors.white), _buildAudioSettings()
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) { Widget _buildUpdateNotification(BuildContext context, String tag) {
return AlertDialog( return AlertDialog(
backgroundColor: Colors.black,
title: Text('Newer version is available ($tag)'), title: Text('Newer version is available ($tag)'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
await updateAPK(context, apkUrl); if (await canLaunch('https://github.com/ByteDream/yamete_kudasai/releases/tag/$tag')) {
await showDialog( await launch('https://github.com/ByteDream/yamete_kudasai/releases/tag/$tag');
context: context, }
builder: (BuildContext context) => FutureProgressDialog( },
updateAPK(context, apkUrl), child: const Text('Show new release')
decoration: const BoxDecoration(
color: Colors.transparent
),
message: const Text('Downloading update...'),
)
);
},
child: const Text('Update')
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () => Navigator.of(context).pop(),
if (await canLaunch('https://github.com/ByteDream/Yamete-Kudasai/releases/tag/$tag')) { child: const Text('No thanks :)')
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) { Widget _buildAudioSettings() {
return FutureBuilder( return FutureBuilder(
future: SharedPreferences.getInstance(), future: SharedPreferences.getInstance(),
builder: (BuildContext context, AsyncSnapshot<SharedPreferences> snapshot) { builder: (BuildContext context, AsyncSnapshot<SharedPreferences> snapshot) {
@ -157,170 +134,56 @@ class _YameteKudasaiState extends State<YameteKudasai> {
final entries = actions.entries; final entries = actions.entries;
return ListView.builder( return ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: actions.length, itemCount: actions.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final item = entries.elementAt(index); final item = entries.elementAt(index);
final activatedKey = "${item.key.index}.activated"; final activatedKey = "${item.key.index}.activated";
final targetKey = "${item.key.index}.target"; final targetKey = "${item.key.index}.target";
final activatedAudio = prefs.getBool(activatedKey) ?? true; final activatedAudio = prefs.getBool(activatedKey) ?? true;
final targetAudio = prefs.getString(targetKey) ?? "assets/audio/yamete_kudasai.mp3"; final targetAudio = prefs.getString(targetKey) ?? "assets/audio/yamete_kudasai.mp3";
return ListTile( return ListTile(
title: Text(item.value), title: Text(item.value),
subtitle: Text(sauceIndex.entries.firstWhere((element) => element.key == targetAudio).value.alias), subtitle: Text(audio.entries.firstWhere((element) => element.value == targetAudio).key),
trailing: Switch( trailing: Switch(
activeColor: Theme.of(context).colorScheme.secondary, activeColor: Theme.of(context).colorScheme.secondary,
value: prefs.getBool(activatedKey) ?? true, value: prefs.getBool(activatedKey) ?? true,
onChanged: (bool newValue) async { onChanged: (bool newValue) async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool(activatedKey, !activatedAudio); prefs.setBool(activatedKey, !activatedAudio);
FlutterBackgroundService().sendData({'action': 'data', 'value': generateEventData(prefs)}); FlutterBackgroundService().sendData({'action': 'data', 'value': generateEventData(prefs)});
setState(() {}); setState(() {});
}), }),
onTap: () async { onTap: () async {
final sauce = await Sauce.sauceIndex(); final audioFile = await Navigator.push<String>(
final audioFile = await Navigator.push<String>( context,
context, MaterialPageRoute(
MaterialPageRoute( builder: (BuildContext context) => ChooseAudio(targetAudio)
builder: (BuildContext context) => ChooseAudio(targetAudio, sauce) )
) );
); if (audioFile != null && audioFile != targetAudio) {
if (audioFile != null && audioFile != targetAudio) { SharedPreferences prefs = await SharedPreferences.getInstance();
SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.setString(targetKey, audioFile);
prefs.setString(targetKey, audioFile); FlutterBackgroundService().sendData({'action': 'data', 'value': generateEventData(prefs)});
FlutterBackgroundService().sendData({'action': 'data', 'value': generateEventData(prefs)}); setState(() {});
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 { Future<bool> isRunning() async {
try { try {
FlutterBackgroundService().sendData({'action': 'ping'}); FlutterBackgroundService().sendData({'action': 'ping'});
await FlutterBackgroundService().onDataReceived.first.timeout(const Duration(milliseconds: 500)); await FlutterBackgroundService().onDataReceived.first.timeout(const Duration(milliseconds: 500));
return true; return true;
} on Exception { } on Exception catch (e) {
return false; return false;
} }
} }

View File

@ -1,71 +0,0 @@
import 'dart:convert';
import 'package:flutter/services.dart';
class Sauce {
final String filepath;
final String alias;
final String name;
final String? season;
final String? episode;
final String? from;
final String? to;
const Sauce(this.filepath,
this.alias,
this.name,
this.season,
this.episode,
this.from,
this.to);
static Future<Map<String, Sauce>> sauceIndex() async {
final sauceJson = await rootBundle.loadString('assets/sauce.json');
Map<String, dynamic> sauce = jsonDecode(sauceJson);
Map<String, Sauce> sauceIndex = new Map();
for (MapEntry<String, dynamic> entry in sauce.entries) {
final value = entry.value as Map<String, dynamic>;
sauceIndex[entry.key] = Sauce(
entry.key,
value['alias'],
value['name'],
value['season'],
value['episode'],
value['from'],
value['to']
);
}
return sauceIndex;
}
}
class Update {
final String version;
final String summary;
final List<String> details;
const Update(this.version,
this.summary,
this.details);
static Future<Map<String, Update>> updatesIndex() async {
final updatesJson = await rootBundle.loadString('assets/updates.json');
Map<String, dynamic> updates = jsonDecode(updatesJson);
Map<String, Update> updatesIndex = new Map();
for (MapEntry<String, dynamic> entry in updates.entries) {
final value = entry.value as Map<String, dynamic>;
updatesIndex[entry.key] = Update(
entry.key,
value['summary'],
(value['details'] as List).cast<String>()
);
}
return updatesIndex;
}
}

View File

@ -45,11 +45,11 @@ public class PortUpdatePlugin implements FlutterPlugin, StreamHandler {
batteryReceiver = createBatteryReceiver(eventSink); batteryReceiver = createBatteryReceiver(eventSink);
headphoneReceiver = createHeadphoneReceiver(eventSink); headphoneReceiver = createHeadphoneReceiver(eventSink);
Intent batteryIntent = context.registerReceiver(batteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); context.registerReceiver(batteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Intent headphoneIntent = context.registerReceiver(headphoneReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); context.registerReceiver(headphoneReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
if (batteryIntent != null) lastBatteryStatus = getBatteryStatus(batteryIntent); // lastBatteryStatus = getBatteryStatus(intent);
if (headphoneIntent != null) lastHeadphoneStatus = getHeadphoneStatus(headphoneIntent); // lastHeadphoneStatus = getHeadphoneStatus(intent);
} }
@Override @Override

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.2.0 version: 1.0.0
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
@ -36,12 +36,8 @@ dependencies:
audioplayers: ^0.20.1 audioplayers: ^0.20.1
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
flutter_background_service: ^0.1.5 flutter_background_service: ^0.1.5
future_progress_dialog: ^0.2.0
http: ^0.13.4 http: ^0.13.4
open_file: ^3.2.1
package_info_plus: ^1.3.0 package_info_plus: ^1.3.0
path_provider: ^2.0.7
permission_handler: ^8.3.0
shared_preferences: ^2.0.8 shared_preferences: ^2.0.8
url_launcher: ^6.0.13 url_launcher: ^6.0.13
@ -73,8 +69,6 @@ flutter:
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
assets: assets:
- assets/audio/ - assets/audio/
- assets/sauce.json
- assets/updates.json
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware. # https://flutter.dev/assets-and-images/#resolution-aware.