Skip to content

Commit 95bba7a

Browse files
feat: check for updates on windows
1 parent 7fbba4e commit 95bba7a

File tree

14 files changed

+323
-92
lines changed

14 files changed

+323
-92
lines changed

lib/bloc/process_bloc/process_bloc.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ class ProcessBloc extends Bloc<ProcessEvent, ProcessState> {
160160
);
161161
} else if (event is GetCCExtractorVersion) {
162162
String ccxVersion = await _extractor.getCCExtractorVersion;
163-
print(ccxVersion);
164163
yield state.copyWith(
165164
current: state.current,
166165
version: ccxVersion,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import 'dart:async';
2+
import 'dart:convert';
3+
4+
import 'package:bloc/bloc.dart';
5+
import 'package:equatable/equatable.dart';
6+
import 'package:http/http.dart' as http;
7+
import 'package:url_launcher/url_launcher.dart';
8+
part 'updater_event.dart';
9+
part 'updater_state.dart';
10+
11+
class UpdaterBloc extends Bloc<UpdaterEvent, UpdaterState> {
12+
UpdaterBloc() : super(UpdaterState('0.0', '0.0', false, '', ''));
13+
static const URL =
14+
'https://api.github.com/repos/CCExtractor/ccextractor/releases';
15+
@override
16+
Stream<UpdaterState> mapEventToState(
17+
UpdaterEvent event,
18+
) async* {
19+
if (event is CheckForUpdates) {
20+
var url = Uri.parse(URL);
21+
http.Response response = await http.get(url, headers: {
22+
'Authorization': String.fromEnvironment('TOKEN'),
23+
});
24+
var data = jsonDecode(response.body);
25+
String latestVersion = data[0]['tag_name'].toString().substring(1);
26+
String downloadURL =
27+
data[0]['assets'][0]['browser_download_url'].toString();
28+
String changelog = data[0]['body'].toString();
29+
bool updateAvailable =
30+
double.parse(latestVersion) > double.parse(event.currentVersion);
31+
print(latestVersion);
32+
yield state.copyWith(
33+
currentVersion: event.currentVersion,
34+
latestVersion: latestVersion,
35+
updateAvailable: updateAvailable,
36+
downloadURL: downloadURL,
37+
changelog: changelog,
38+
);
39+
}
40+
if (event is DownloadUpdate) {
41+
await launch(event.downloadURl);
42+
}
43+
}
44+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
part of 'updater_bloc.dart';
2+
3+
abstract class UpdaterEvent extends Equatable {
4+
const UpdaterEvent();
5+
6+
@override
7+
List<Object> get props => [];
8+
}
9+
10+
class CheckForUpdates extends UpdaterEvent {
11+
final String currentVersion;
12+
13+
CheckForUpdates(this.currentVersion);
14+
15+
@override
16+
List<Object> get props => [currentVersion];
17+
}
18+
19+
class DownloadUpdate extends UpdaterEvent {
20+
final String downloadURl;
21+
22+
DownloadUpdate(this.downloadURl);
23+
@override
24+
List<Object> get props => [downloadURl];
25+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
part of 'updater_bloc.dart';
2+
3+
class UpdaterState {
4+
final String currentVersion;
5+
final String latestVersion;
6+
final bool updateAvailable;
7+
final String changelog;
8+
final String downloadURL;
9+
UpdaterState(this.currentVersion, this.latestVersion, this.updateAvailable,
10+
this.changelog, this.downloadURL);
11+
12+
UpdaterState copyWith({
13+
String? currentVersion,
14+
String? latestVersion,
15+
bool? updateAvailable,
16+
String? changelog,
17+
String? downloadURL,
18+
}) {
19+
return UpdaterState(
20+
currentVersion ?? this.currentVersion,
21+
latestVersion ?? this.latestVersion,
22+
updateAvailable ?? this.updateAvailable,
23+
changelog ?? this.changelog,
24+
downloadURL ?? this.downloadURL,
25+
);
26+
}
27+
}

lib/main.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:io';
22

3+
import 'package:ccxgui/bloc/updater_bloc/updater_bloc.dart';
34
import 'package:flutter/material.dart';
45

56
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -47,6 +48,9 @@ class MyApp extends StatelessWidget {
4748
create: (context) =>
4849
SettingsBloc(SettingsRepository())..add(CheckSettingsEvent()),
4950
),
51+
BlocProvider<UpdaterBloc>(
52+
create: (context) => UpdaterBloc(),
53+
),
5054
],
5155
child: MaterialApp(
5256
theme: theme.copyWith(

lib/repositories/ccextractor.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class CCExtractor {
7777
await Process.run(ccextractor, ['--version']).then((value) {
7878
ccxStdOut = value.stdout
7979
.toString()
80-
.substring(value.stdout.toString().indexOf('Version:'),
80+
.substring(value.stdout.toString().indexOf('Version:') + 8,
8181
value.stdout.toString().indexOf('Version:') + 15)
8282
.trim();
8383
});

lib/screens/home.dart

Lines changed: 149 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import 'dart:io';
2+
3+
import 'package:ccxgui/bloc/updater_bloc/updater_bloc.dart';
4+
import 'package:ccxgui/screens/dashboard/components/custom_snackbar.dart';
15
import 'package:flutter/material.dart';
26

37
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -10,6 +14,7 @@ import 'package:ccxgui/screens/settings/basic_settings.dart';
1014
import 'package:ccxgui/screens/settings/input_settings.dart';
1115
import 'package:ccxgui/screens/settings/obscure_settings.dart';
1216
import 'package:ccxgui/screens/settings/output_settings.dart';
17+
import 'package:flutter_markdown/flutter_markdown.dart';
1318

1419
class Home extends StatefulWidget {
1520
@override
@@ -20,99 +25,157 @@ class _HomeState extends State<Home> {
2025
final navigatorKey = GlobalKey<NavigatorState>();
2126
int _currentIndex = 0;
2227
final String logo = 'assets/ccx.svg';
28+
final ScrollController scrollController = ScrollController();
2329
@override
2430
Widget build(BuildContext context) {
25-
return NavRail(
26-
desktopBreakpoint: 1150,
27-
hideTitleBar: true,
28-
drawerHeaderBuilder: (context) {
29-
return Column(
30-
children: <Widget>[
31-
DrawerHeader(
32-
child: SvgPicture.asset(
33-
logo,
34-
semanticsLabel: 'CCExtractor Logo',
35-
),
36-
),
37-
BlocBuilder<ProcessBloc, ProcessState>(
38-
builder: (context, state) {
39-
return Text(
40-
state.version!.trim(),
41-
style: TextStyle(
42-
fontSize: 12,
43-
color: Theme.of(context)
44-
.bottomNavigationBarTheme
45-
.backgroundColor),
46-
);
47-
},
48-
),
49-
],
50-
);
51-
},
52-
drawerFooterBuilder: (context) {
53-
return Padding(
54-
padding: const EdgeInsets.only(left: 20.0, bottom: 16),
55-
child: MaterialButton(
56-
hoverColor: Colors.transparent,
57-
onPressed: () {}, // TODO: implement check for updates
58-
child: Row(
59-
children: [
60-
Icon(
61-
Icons.update,
62-
color: Colors.white54,
63-
),
64-
SizedBox(
65-
width: 20,
66-
),
67-
Text(
68-
'Check for updates',
69-
style: TextStyle(color: Colors.white60, fontSize: 14),
31+
return BlocListener<UpdaterBloc, UpdaterState>(
32+
listener: (context, state) {
33+
if (state.updateAvailable) {
34+
showDialog(
35+
context: context,
36+
builder: (context) {
37+
return AlertDialog(
38+
title: Text('Update available\n\nChangelog:'),
39+
content: Container(
40+
width: 800,
41+
child: Markdown(
42+
controller: scrollController,
43+
selectable: true,
44+
shrinkWrap: true,
45+
data: state.changelog),
7046
),
71-
],
72-
),
73-
),
74-
);
75-
},
76-
currentIndex: _currentIndex,
77-
onTap: (val) {
78-
if (mounted && _currentIndex != val) {
79-
setState(() {
80-
_currentIndex = val;
81-
});
47+
actions: <Widget>[
48+
Padding(
49+
padding: const EdgeInsets.all(8.0),
50+
child: TextButton(
51+
onPressed: () => Navigator.pop(context),
52+
child: const Text('Cancel'),
53+
),
54+
),
55+
Padding(
56+
padding: const EdgeInsets.all(8.0),
57+
child: TextButton(
58+
onPressed: () {
59+
Navigator.pop(context);
60+
context
61+
.read<UpdaterBloc>()
62+
.add(DownloadUpdate(state.downloadURL));
63+
},
64+
child: const Text('Download'),
65+
),
66+
),
67+
],
68+
);
69+
},
70+
);
71+
} else {
72+
CustomSnackBarMessage.show(
73+
context, 'You are already on the latest version');
8274
}
8375
},
84-
body: IndexedStack(
85-
index: _currentIndex,
86-
children: <Widget>[
87-
Dashboard(),
88-
BasicSettingsScreen(),
89-
InputSettingsScreen(),
90-
OutputSettingsScreen(),
91-
ObscureSettingsScreen(),
76+
child: NavRail(
77+
desktopBreakpoint: 1150,
78+
hideTitleBar: true,
79+
drawerHeaderBuilder: (context) {
80+
return Column(
81+
children: <Widget>[
82+
DrawerHeader(
83+
child: SvgPicture.asset(
84+
logo,
85+
semanticsLabel: 'CCExtractor Logo',
86+
),
87+
),
88+
BlocBuilder<ProcessBloc, ProcessState>(
89+
builder: (context, state) {
90+
return Text(
91+
'Version: ' + state.version!.trim(),
92+
style: TextStyle(
93+
fontSize: 12,
94+
color: Theme.of(context)
95+
.bottomNavigationBarTheme
96+
.backgroundColor),
97+
);
98+
},
99+
),
100+
],
101+
);
102+
},
103+
drawerFooterBuilder: (context) {
104+
return Platform.isWindows
105+
? Padding(
106+
padding: const EdgeInsets.only(left: 20.0, bottom: 16),
107+
child: BlocBuilder<ProcessBloc, ProcessState>(
108+
builder: (context, processState) {
109+
return MaterialButton(
110+
hoverColor: Colors.transparent,
111+
onPressed: () {
112+
context
113+
.read<UpdaterBloc>()
114+
.add(CheckForUpdates(processState.version!));
115+
},
116+
child: Row(
117+
children: [
118+
Icon(
119+
Icons.update,
120+
color: Colors.white54,
121+
),
122+
SizedBox(
123+
width: 20,
124+
),
125+
Text(
126+
'Check for updates',
127+
style: TextStyle(
128+
color: Colors.white60, fontSize: 14),
129+
),
130+
],
131+
),
132+
);
133+
},
134+
),
135+
)
136+
: Container();
137+
},
138+
currentIndex: _currentIndex,
139+
onTap: (val) {
140+
if (mounted && _currentIndex != val) {
141+
setState(() {
142+
_currentIndex = val;
143+
});
144+
}
145+
},
146+
body: IndexedStack(
147+
index: _currentIndex,
148+
children: <Widget>[
149+
Dashboard(),
150+
BasicSettingsScreen(),
151+
InputSettingsScreen(),
152+
OutputSettingsScreen(),
153+
ObscureSettingsScreen(),
154+
],
155+
),
156+
tabs: <BottomNavigationBarItem>[
157+
BottomNavigationBarItem(
158+
label: 'Dashboard',
159+
icon: Icon(Icons.dashboard),
160+
),
161+
BottomNavigationBarItem(
162+
label: 'Basic Settings',
163+
icon: Icon(Icons.settings),
164+
),
165+
BottomNavigationBarItem(
166+
label: 'Input Settings',
167+
icon: Icon(Icons.input),
168+
),
169+
BottomNavigationBarItem(
170+
label: 'Output Settings',
171+
icon: Icon(Icons.dvr_outlined),
172+
),
173+
BottomNavigationBarItem(
174+
label: 'Obscure Settings',
175+
icon: Icon(Icons.do_disturb_alt_rounded),
176+
),
92177
],
93178
),
94-
tabs: <BottomNavigationBarItem>[
95-
BottomNavigationBarItem(
96-
label: 'Dashboard',
97-
icon: Icon(Icons.dashboard),
98-
),
99-
BottomNavigationBarItem(
100-
label: 'Basic Settings',
101-
icon: Icon(Icons.settings),
102-
),
103-
BottomNavigationBarItem(
104-
label: 'Input Settings',
105-
icon: Icon(Icons.input),
106-
),
107-
BottomNavigationBarItem(
108-
label: 'Output Settings',
109-
icon: Icon(Icons.dvr_outlined),
110-
),
111-
BottomNavigationBarItem(
112-
label: 'Obscure Settings',
113-
icon: Icon(Icons.do_disturb_alt_rounded),
114-
),
115-
],
116179
);
117180
}
118181
}

0 commit comments

Comments
 (0)