Add update notifier
This commit is contained in:
parent
b2b45c7a3f
commit
16305542e3
3 changed files with 164 additions and 31 deletions
50
lib/apis/github_api.dart
Normal file
50
lib/apis/github_api.dart
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
class SemVer {
|
||||||
|
final int major;
|
||||||
|
final int minor;
|
||||||
|
final int patch;
|
||||||
|
|
||||||
|
SemVer({required this.major, required this.minor, required this.patch});
|
||||||
|
|
||||||
|
factory SemVer.fromString(String str) {
|
||||||
|
final split = str.split(".");
|
||||||
|
if (split.length != 3) {
|
||||||
|
throw "Invalid version format";
|
||||||
|
}
|
||||||
|
return SemVer(major: int.parse(split[0]), minor: int.parse(split[1]), patch: int.parse(split[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is SemVer &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
major == other.major &&
|
||||||
|
minor == other.minor &&
|
||||||
|
patch == other.patch;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => major.hashCode ^ minor.hashCode ^ patch.hashCode;
|
||||||
|
|
||||||
|
bool operator >(SemVer other) {
|
||||||
|
if (major > other.major || (major == other.major && minor > other.minor) || (major == other.major && minor == other.minor && patch > other.patch)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GithubApi {
|
||||||
|
static const baseUrl = "https://api.github.com";
|
||||||
|
|
||||||
|
static Future<String> getLatestTagName() async {
|
||||||
|
final response = await http.get(Uri.parse("$baseUrl/repos/Nutcake/contacts-plus-plus/releases/latest"));
|
||||||
|
if (response.statusCode != 200) return "";
|
||||||
|
final body = jsonDecode(response.body);
|
||||||
|
return body["tag_name"] ?? "";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,18 @@
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
|
import 'package:contacts_plus_plus/apis/github_api.dart';
|
||||||
import 'package:contacts_plus_plus/client_holder.dart';
|
import 'package:contacts_plus_plus/client_holder.dart';
|
||||||
import 'package:contacts_plus_plus/clients/messaging_client.dart';
|
import 'package:contacts_plus_plus/clients/messaging_client.dart';
|
||||||
import 'package:contacts_plus_plus/clients/settings_client.dart';
|
import 'package:contacts_plus_plus/clients/settings_client.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/friends/friends_list.dart';
|
import 'package:contacts_plus_plus/widgets/friends/friends_list.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/login_screen.dart';
|
import 'package:contacts_plus_plus/widgets/login_screen.dart';
|
||||||
|
import 'package:contacts_plus_plus/widgets/update_notifier.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:workmanager/workmanager.dart';
|
import 'package:workmanager/workmanager.dart';
|
||||||
import 'models/authentication_data.dart';
|
import 'models/authentication_data.dart';
|
||||||
|
@ -51,43 +55,76 @@ class ContactsPlusPlus extends StatefulWidget {
|
||||||
class _ContactsPlusPlusState extends State<ContactsPlusPlus> {
|
class _ContactsPlusPlusState extends State<ContactsPlusPlus> {
|
||||||
final Typography _typography = Typography.material2021(platform: TargetPlatform.android);
|
final Typography _typography = Typography.material2021(platform: TargetPlatform.android);
|
||||||
AuthenticationData _authData = AuthenticationData.unauthenticated();
|
AuthenticationData _authData = AuthenticationData.unauthenticated();
|
||||||
|
bool _checkedForUpdate = false;
|
||||||
|
|
||||||
|
void showUpdateDialogOnFirstBuild(BuildContext context) {
|
||||||
|
final navigator = Navigator.of(context);
|
||||||
|
if (_checkedForUpdate || kDebugMode) return;
|
||||||
|
_checkedForUpdate = true;
|
||||||
|
GithubApi.getLatestTagName().then((value) async {
|
||||||
|
final currentVer = (await PackageInfo.fromPlatform()).version;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final currentSem = SemVer.fromString(currentVer);
|
||||||
|
final remoteSem = SemVer.fromString(value);
|
||||||
|
if (remoteSem > currentSem && navigator.overlay?.context != null) {
|
||||||
|
showDialog(
|
||||||
|
context: navigator.overlay!.context,
|
||||||
|
builder: (context) {
|
||||||
|
return const UpdateNotifier();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (currentVer != value && navigator.overlay?.context != null) {
|
||||||
|
showDialog(
|
||||||
|
context: navigator.overlay!.context,
|
||||||
|
builder: (context) {
|
||||||
|
return const UpdateNotifier();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ClientHolder(
|
return ClientHolder(
|
||||||
settingsClient: widget.settingsClient,
|
settingsClient: widget.settingsClient,
|
||||||
authenticationData: _authData,
|
authenticationData: _authData,
|
||||||
child: Builder(
|
child: MaterialApp(
|
||||||
builder: (context) {
|
debugShowCheckedModeBanner: false,
|
||||||
final clientHolder = ClientHolder.of(context);
|
title: 'Contacts++',
|
||||||
return MaterialApp(
|
theme: ThemeData(
|
||||||
debugShowCheckedModeBanner: false,
|
useMaterial3: true,
|
||||||
title: 'Contacts++',
|
textTheme: _typography.white,
|
||||||
theme: ThemeData(
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.dark)
|
||||||
useMaterial3: true,
|
),
|
||||||
textTheme: _typography.white,
|
home: Builder(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.dark)
|
builder: (context) {
|
||||||
),
|
showUpdateDialogOnFirstBuild(context);
|
||||||
home: _authData.isAuthenticated ?
|
final clientHolder = ClientHolder.of(context);
|
||||||
ChangeNotifierProvider( // This doesn't need to be a proxy provider since the arguments should never change during it's lifetime.
|
return _authData.isAuthenticated ?
|
||||||
create: (context) =>
|
ChangeNotifierProvider( // This doesn't need to be a proxy provider since the arguments should never change during it's lifetime.
|
||||||
MessagingClient(
|
create: (context) =>
|
||||||
apiClient: clientHolder.apiClient,
|
MessagingClient(
|
||||||
notificationClient: clientHolder.notificationClient,
|
apiClient: clientHolder.apiClient,
|
||||||
),
|
notificationClient: clientHolder.notificationClient,
|
||||||
child: const FriendsList(),
|
),
|
||||||
) :
|
child: const FriendsList(),
|
||||||
LoginScreen(
|
) :
|
||||||
onLoginSuccessful: (AuthenticationData authData) async {
|
LoginScreen(
|
||||||
if (authData.isAuthenticated) {
|
onLoginSuccessful: (AuthenticationData authData) async {
|
||||||
setState(() {
|
if (authData.isAuthenticated) {
|
||||||
_authData = authData;
|
setState(() {
|
||||||
});
|
_authData = authData;
|
||||||
}
|
});
|
||||||
},
|
}
|
||||||
)
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
46
lib/widgets/update_notifier.dart
Normal file
46
lib/widgets/update_notifier.dart
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
class UpdateNotifier extends StatelessWidget {
|
||||||
|
const UpdateNotifier({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text("Update Available", style: Theme
|
||||||
|
.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleLarge),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text("There is a new version available for download!"),
|
||||||
|
const SizedBox(height: 24,),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
launchUrl(Uri.parse("https://github.com/Nutcake/contacts-plus-plus/releases/latest"),
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24),
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onSecondary,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondary
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.download),
|
||||||
|
label: const Text("Get it on Github"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("I'll do it later."))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue