mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +01:00
Add update checker
This commit is contained in:
parent
e3b1c5ed30
commit
e17532eea1
4 changed files with 285 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");
|
||||
}
|
|
@ -39,6 +39,7 @@ import 'package:nc_photos/widget/splash.dart';
|
|||
import 'package:nc_photos/widget/tag_browser.dart';
|
||||
import 'package:nc_photos/widget/trashbin_browser.dart';
|
||||
import 'package:nc_photos/widget/trashbin_viewer.dart';
|
||||
import 'package:nc_photos/widget/update_checker.dart';
|
||||
import 'package:nc_photos/widget/viewer.dart';
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
|
@ -173,6 +174,7 @@ class _MyAppState extends State<MyApp>
|
|||
route ??= _handlePlaceBrowserRoute(settings);
|
||||
route ??= _handlePlacesBrowserRoute(settings);
|
||||
route ??= _handleResultViewerRoute(settings);
|
||||
route ??= _handleUpdateCheckerRoute(settings);
|
||||
return route;
|
||||
}
|
||||
|
||||
|
@ -621,6 +623,17 @@ class _MyAppState extends State<MyApp>
|
|||
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;
|
||||
}
|
||||
|
||||
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
final _navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import 'package:nc_photos/widget/root_picker.dart';
|
|||
import 'package:nc_photos/widget/share_folder_picker.dart';
|
||||
import 'package:nc_photos/widget/simple_input_dialog.dart';
|
||||
import 'package:nc_photos/widget/stateful_slider.dart';
|
||||
import 'package:nc_photos/widget/update_checker.dart';
|
||||
import 'package:screen_brightness/screen_brightness.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
|
@ -244,6 +245,12 @@ class _SettingsState extends State<Settings> {
|
|||
launch(help_util.donateUrl);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text("Check for updates"),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(UpdateChecker.routeName);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(L10n.global().settingsSourceCodeTitle),
|
||||
onTap: () {
|
||||
|
|
212
app/lib/widget/update_checker.dart
Normal file
212
app/lib/widget/update_checker.dart
Normal file
|
@ -0,0 +1,212 @@
|
|||
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/theme.dart';
|
||||
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({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
createState() => _UpdateCheckerState();
|
||||
}
|
||||
|
||||
class _UpdateCheckerState extends State<UpdateChecker> {
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
_work();
|
||||
}
|
||||
|
||||
@override
|
||||
build(BuildContext context) {
|
||||
return AppTheme(
|
||||
child: 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