diff --git a/lib/app/home/home_screen.dart b/lib/app/home/home_screen.dart index 63b97ed..18caa3b 100644 --- a/lib/app/home/home_screen.dart +++ b/lib/app/home/home_screen.dart @@ -10,6 +10,8 @@ import 'package:tubesync/app/app_theme.dart'; import 'package:tubesync/app/library/import_playlist_dialog.dart'; import 'package:tubesync/app/library/library_tab.dart'; import 'package:tubesync/app/more/more_tab.dart'; +import 'package:tubesync/clients/in_app_update_client.dart'; +import 'package:tubesync/model/preferences.dart'; import 'package:tubesync/provider/library_provider.dart'; import 'home_app_bar.dart'; @@ -77,19 +79,30 @@ class HomeTab extends StatefulWidget { class _HomeTabState extends State { final homeNavigator = GlobalKey(); StreamSubscription? shareHandler; + late final prefs = context.read().preferences; @override void initState() { super.initState(); + // TODO: Add IOS Support if (Platform.isAndroid) { - shareHandler = ReceiveSharingIntent.instance.getMediaStream().listen( - handleSharedData, - ); + shareHandler = ReceiveSharingIntent.instance + .getMediaStream() + .listen(handleSharedData); + ReceiveSharingIntent.instance .getInitialMedia() .then(handleSharedData) .whenComplete(ReceiveSharingIntent.instance.reset); } + + // Check for update + if (prefs.getValue(Preference.inAppUpdate, true)!) { + InAppUpdateClient.checkFromGitHub().then((changes) { + if (!mounted || changes == null) return; + InAppUpdateClient.showUpdateDialog(context, changes); + }).catchError((_) {}); + } } void handleSharedData(List value) { diff --git a/lib/app/more/about_screen.dart b/lib/app/more/about_screen.dart index 8c9adcd..3c093b2 100644 --- a/lib/app/more/about_screen.dart +++ b/lib/app/more/about_screen.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:package_info_plus/package_info_plus.dart"; +import "package:tubesync/clients/in_app_update_client.dart"; import "package:url_launcher/url_launcher_string.dart"; class AboutScreen extends StatelessWidget { @@ -23,6 +24,7 @@ class AboutScreen extends StatelessWidget { return Padding( padding: const EdgeInsets.all(12.0), child: Column( + spacing: 24, mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( @@ -30,27 +32,45 @@ class AboutScreen extends StatelessWidget { color: Theme.of(context).colorScheme.primary, height: 80, ), - const SizedBox(height: 20), - Text( - "TubeSync", - style: Theme.of(context).textTheme.headlineMedium, - ), - Text( - "v${snapshot.requireData.version}", - style: Theme.of(context).textTheme.bodyLarge, + Column( + spacing: 12, + children: [ + Text( + "TubeSync", + style: Theme.of(context).textTheme.headlineMedium, + ), + Text( + "v${snapshot.requireData.version}", + style: Theme.of(context).textTheme.bodyLarge, + ), + FilledButton( + onPressed: () { + InAppUpdateClient.checkFromGitHub().then((value) { + if (!context.mounted) return; + if (value == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("No new updates."), + ), + ); + return; + } + InAppUpdateClient.showUpdateDialog(context, value); + }); + }, + child: const Text("Check For Update"), + ) + ], ), - const SizedBox(height: 24), const Text( "Licenced under the GNU General Public Licence v3.0", textAlign: TextAlign.center, ), - const SizedBox(height: 24), TextButton.icon( onPressed: () => showLicensePage(context: context), icon: const Icon(Icons.chrome_reader_mode_rounded), label: const Text("Open Source Licences"), ), - const SizedBox(height: 24), Row( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/app/more/preferences/preference_screen.dart b/lib/app/more/preferences/preference_screen.dart index 232c2ce..3a4343e 100644 --- a/lib/app/more/preferences/preference_screen.dart +++ b/lib/app/more/preferences/preference_screen.dart @@ -128,6 +128,22 @@ class PreferenceScreen extends StatelessWidget { }), ), ), + _title(context, "Others"), + StreamBuilder( + stream: preferences(context).watch( + Preference.inAppUpdate, + ), + builder: (c, value) => SwitchListTile( + value: value.data?.get() != false, + onChanged: (value) => preferences(c).setValue( + Preference.inAppUpdate, + value, + ), + secondary: const Icon(Icons.shuffle_rounded), + title: const Text("Check app updates"), + subtitle: const Text("Notify when new version is available"), + ), + ), ], ), ); diff --git a/lib/clients/in_app_update_client.dart b/lib/clients/in_app_update_client.dart new file mode 100644 index 0000000..4fe89e6 --- /dev/null +++ b/lib/clients/in_app_update_client.dart @@ -0,0 +1,59 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:http/http.dart' as http; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +class InAppUpdateClient { + static const repo = "khaled-0/TubeSync"; + + /// Returns changelog if update available. else null + static Future checkFromGitHub() async { + final url = Uri.parse('https://api.github.com/repos/$repo/releases/latest'); + final response = await http.get(url); + // We're treating errors as no update. + if (response.statusCode != 200) return null; + + final Map releaseInfo = json.decode(response.body); + final String latestVersion = releaseInfo['tag_name']; + + final currentVersion = (await PackageInfo.fromPlatform()).version; + if (currentVersion.compareTo(latestVersion) >= 0) return null; + + final String changelog = releaseInfo["body"]; + return changelog; + } + + static void showUpdateDialog(BuildContext context, String changelog) { + showDialog( + useRootNavigator: true, + useSafeArea: true, + context: context, + builder: (context) => AlertDialog( + title: const Text("A new update!"), + content: SizedBox( + height: MediaQuery.of(context).size.height * 0.7, + width: MediaQuery.of(context).size.width * 0.8, + child: Markdown( + data: changelog, + shrinkWrap: true, + padding: EdgeInsets.zero, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text("Cancel"), + ), + TextButton( + onPressed: () => launchUrlString( + "https://github.com/$repo/releases/latest", + ), + child: const Text("Update"), + ), + ], + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 0910be4..f3b8edf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -85,6 +85,7 @@ void main() async { ), ), ); + // Ensure permissions Future.wait([ Permission.notification.isDenied, diff --git a/lib/model/preferences.dart b/lib/model/preferences.dart index 395ed9f..1936f9e 100644 --- a/lib/model/preferences.dart +++ b/lib/model/preferences.dart @@ -18,7 +18,9 @@ enum Preference { // Player Customization miniPlayerSecondaryAction, // Downloader - maxParallelDownload + maxParallelDownload, + // Others + inAppUpdate, } @Collection() diff --git a/pubspec.lock b/pubspec.lock index 56f560c..d114b50 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -355,6 +355,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4+6" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + sha256: "255b00afa1a7bad19727da6a7780cf3db6c3c12e68d302d85e0ff1fdf173db9e" + url: "https://pub.dev" + source: hosted + version: "0.7.4+3" flutter_test: dependency: "direct dev" description: flutter @@ -406,7 +414,7 @@ packages: source: hosted version: "0.15.5" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 @@ -581,6 +589,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.3-main.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 + url: "https://pub.dev" + source: hosted + version: "7.2.2" matcher: dependency: transitive description: @@ -881,7 +897,7 @@ packages: dependency: "direct main" description: path: "." - ref: "2cea396843cd3ab1b5ec4334be4233864637874e" + ref: HEAD resolved-ref: "2cea396843cd3ab1b5ec4334be4233864637874e" url: "https://github.com/KasemJaffer/receive_sharing_intent.git" source: git diff --git a/pubspec.yaml b/pubspec.yaml index e1974b5..0ffdad4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: git: https://github.com/Hexer10/youtube_explode_dart.git background_downloader: ^8.8.1 internet_connection_checker_plus: ^2.6.0 + http: ^1.2.2 # Data Management (Isar is too big in fs <3mb> Use ObjectBox/SqLite+Drift?) provider: ^6.1.2 @@ -67,12 +68,12 @@ dependencies: path_provider: ^2.1.5 url_launcher: ^6.3.1 permission_handler: ^11.3.1 + + # Extras + flutter_markdown: ^0.7.4+3 package_info_plus: ^8.1.2 - # https://github.com/KasemJaffer/receive_sharing_intent/pull/333 receive_sharing_intent: - git: - url: https://github.com/KasemJaffer/receive_sharing_intent.git - ref: 2cea396843cd3ab1b5ec4334be4233864637874e + git: https://github.com/KasemJaffer/receive_sharing_intent.git dev_dependencies: flutter_test: diff --git a/tests/basic.dart b/tests/basic.dart new file mode 100644 index 0000000..2c799da --- /dev/null +++ b/tests/basic.dart @@ -0,0 +1,7 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + WidgetsFlutterBinding.ensureInitialized(); +}