mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-02 06:46:22 +01:00
Add update checker
This commit is contained in:
parent
41e9b6214a
commit
5e1d7be6a3
4 changed files with 282 additions and 0 deletions
53
app/lib/update_checker.dart
Normal file
53
app/lib/update_checker.dart
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
|
||||||
|
enum UpdateCheckerResult {
|
||||||
|
updateAvailable,
|
||||||
|
alreadyLatest,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateChecker {
|
||||||
|
Future<UpdateCheckerResult> call() async {
|
||||||
|
try {
|
||||||
|
final uri = Uri.https("bit.ly", "3pb2oG9");
|
||||||
|
final req = http.Request("GET", uri);
|
||||||
|
final response =
|
||||||
|
await http.Response.fromStream(await http.Client().send(req));
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
_log.severe("[call] Failed GETing URL: ${response.statusCode}");
|
||||||
|
return UpdateCheckerResult.error;
|
||||||
|
}
|
||||||
|
final body = response.body;
|
||||||
|
final json = jsonDecode(body) as Map;
|
||||||
|
final data = json[_buildVariant] as Map;
|
||||||
|
_log.info("[call] Update data: ${jsonEncode(data)}");
|
||||||
|
final latest = data["v"];
|
||||||
|
_versionStr = data["vStr"];
|
||||||
|
_updateUrl = data["url"];
|
||||||
|
if (latest > k.version) {
|
||||||
|
return UpdateCheckerResult.updateAvailable;
|
||||||
|
} else {
|
||||||
|
return UpdateCheckerResult.alreadyLatest;
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe("[call] Exception", e, stackTrace);
|
||||||
|
return UpdateCheckerResult.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the binary url (if available) after [call] returned with
|
||||||
|
/// [UpdateCheckerResult.updateAvailable]
|
||||||
|
String? get updateUrl => _updateUrl;
|
||||||
|
String? get versionStr => _versionStr;
|
||||||
|
|
||||||
|
String? _updateUrl;
|
||||||
|
String? _versionStr;
|
||||||
|
|
||||||
|
static const _buildVariant = "";
|
||||||
|
|
||||||
|
static final _log = Logger("update_checker.UpdateChecker");
|
||||||
|
}
|
|
@ -56,6 +56,7 @@ import 'package:nc_photos/widget/splash.dart';
|
||||||
import 'package:nc_photos/widget/trashbin_browser.dart';
|
import 'package:nc_photos/widget/trashbin_browser.dart';
|
||||||
import 'package:nc_photos/widget/trashbin_viewer.dart';
|
import 'package:nc_photos/widget/trashbin_viewer.dart';
|
||||||
import 'package:nc_photos/widget/trusted_cert_manager.dart';
|
import 'package:nc_photos/widget/trusted_cert_manager.dart';
|
||||||
|
import 'package:nc_photos/widget/update_checker.dart';
|
||||||
import 'package:nc_photos/widget/viewer.dart';
|
import 'package:nc_photos/widget/viewer.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_db/np_db.dart';
|
import 'package:np_db/np_db.dart';
|
||||||
|
@ -242,6 +243,7 @@ class _WrappedAppState extends State<_WrappedApp>
|
||||||
route ??= _handleCollectionBrowserRoute(settings);
|
route ??= _handleCollectionBrowserRoute(settings);
|
||||||
route ??= _handleAccountSettingsRoute(settings);
|
route ??= _handleAccountSettingsRoute(settings);
|
||||||
route ??= _handlePlacePickerRoute(settings);
|
route ??= _handlePlacePickerRoute(settings);
|
||||||
|
route ??= _handleUpdateCheckerRoute(settings);
|
||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,6 +565,17 @@ class _WrappedAppState extends State<_WrappedApp>
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Route<dynamic>? _handleUpdateCheckerRoute(RouteSettings settings) {
|
||||||
|
try {
|
||||||
|
if (settings.name == UpdateChecker.routeName) {
|
||||||
|
return UpdateChecker.buildRoute();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_log.severe("[_handleUpdateCheckerRoute] Failed while handling route", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
late final _bloc = context.read<_Bloc>();
|
late final _bloc = context.read<_Bloc>();
|
||||||
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||||
final _navigatorKey = GlobalKey<NavigatorState>();
|
final _navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
|
@ -27,6 +27,7 @@ import 'package:nc_photos/widget/settings/photos_settings.dart';
|
||||||
import 'package:nc_photos/widget/settings/settings_list_caption.dart';
|
import 'package:nc_photos/widget/settings/settings_list_caption.dart';
|
||||||
import 'package:nc_photos/widget/settings/theme_settings.dart';
|
import 'package:nc_photos/widget/settings/theme_settings.dart';
|
||||||
import 'package:nc_photos/widget/settings/viewer_settings.dart';
|
import 'package:nc_photos/widget/settings/viewer_settings.dart';
|
||||||
|
import 'package:nc_photos/widget/update_checker.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
|
||||||
part 'settings.g.dart';
|
part 'settings.g.dart';
|
||||||
|
@ -146,6 +147,12 @@ class _SettingsState extends State<Settings> {
|
||||||
launch(help_util.donateUrl);
|
launch(help_util.donateUrl);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text("Check for updates"),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pushNamed(UpdateChecker.routeName);
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10n.global().settingsSourceCodeTitle),
|
title: Text(L10n.global().settingsSourceCodeTitle),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
209
app/lib/widget/update_checker.dart
Normal file
209
app/lib/widget/update_checker.dart
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/help_utils.dart' as help_util;
|
||||||
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
import 'package:nc_photos/update_checker.dart' as checker;
|
||||||
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
|
|
||||||
|
class UpdateChecker extends StatefulWidget {
|
||||||
|
static const routeName = "/update-checker";
|
||||||
|
|
||||||
|
static Route buildRoute() => MaterialPageRoute(
|
||||||
|
builder: (context) => const UpdateChecker(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const UpdateChecker({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
createState() => _UpdateCheckerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UpdateCheckerState extends State<UpdateChecker> {
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
_work();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
body: Builder(
|
||||||
|
builder: _buildContent,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContent(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
const Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
"Photos ${k.versionStr}",
|
||||||
|
style: TextStyle(fontSize: 24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: _getStatusWidget(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
_getStatusText(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (_result == checker.UpdateCheckerResult.updateAvailable)
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (_updateUrl != null) {
|
||||||
|
launch(_updateUrl!);
|
||||||
|
} else {
|
||||||
|
// fallback
|
||||||
|
launch("https://bit.ly/3wNLHFo");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text("GET UPDATE"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const SizedBox(height: 48),
|
||||||
|
const SizedBox(height: 56),
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
L10n.global().donationShortMessage,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
launch(help_util.donateUrl);
|
||||||
|
},
|
||||||
|
child: Text(L10n.global().donationButtonLabel),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
const Expanded(
|
||||||
|
child: Text("Having problems?"),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
launch("https://bit.ly/3wNLHFo");
|
||||||
|
},
|
||||||
|
child: const Text("CHECK MANUALLY"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _work() async {
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
final uc = checker.UpdateChecker();
|
||||||
|
final result = await uc();
|
||||||
|
if (result == checker.UpdateCheckerResult.updateAvailable) {
|
||||||
|
_updateUrl = uc.updateUrl;
|
||||||
|
_versionStr = uc.versionStr;
|
||||||
|
}
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_result = result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _getStatusWidget() {
|
||||||
|
if (_result == null) {
|
||||||
|
return const SizedBox(
|
||||||
|
width: _statusIconSize,
|
||||||
|
height: _statusIconSize,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
switch (_result!) {
|
||||||
|
case checker.UpdateCheckerResult.updateAvailable:
|
||||||
|
return Icon(
|
||||||
|
Icons.upload,
|
||||||
|
color: Colors.orange[700],
|
||||||
|
size: _statusIconSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
case checker.UpdateCheckerResult.alreadyLatest:
|
||||||
|
return Icon(
|
||||||
|
Icons.done,
|
||||||
|
color: Colors.green[600],
|
||||||
|
size: _statusIconSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
case checker.UpdateCheckerResult.error:
|
||||||
|
return Icon(
|
||||||
|
Icons.warning,
|
||||||
|
color: Colors.red[700],
|
||||||
|
size: _statusIconSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getStatusText() {
|
||||||
|
if (_result == null) {
|
||||||
|
return "Checking...";
|
||||||
|
} else {
|
||||||
|
switch (_result!) {
|
||||||
|
case checker.UpdateCheckerResult.updateAvailable:
|
||||||
|
return "Update available ($_versionStr)";
|
||||||
|
|
||||||
|
case checker.UpdateCheckerResult.alreadyLatest:
|
||||||
|
return "You are running the latest version";
|
||||||
|
|
||||||
|
case checker.UpdateCheckerResult.error:
|
||||||
|
return "Failed checking for updates";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checker.UpdateCheckerResult? _result;
|
||||||
|
String? _updateUrl;
|
||||||
|
String? _versionStr;
|
||||||
|
|
||||||
|
static const _statusIconSize = 72.0;
|
||||||
|
}
|
Loading…
Reference in a new issue