Skip to content

Commit

Permalink
Add Update-Checker
Browse files Browse the repository at this point in the history
The app now notifies the user on the homescreen when a new version is
available.
  • Loading branch information
flofriday committed Aug 15, 2021
1 parent f2904a7 commit e696f8f
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 88 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ API, the app itself is not official!
- Search by matriculation number, or name
- Filter search results
- Optional login (needed to get student information)
- Dark Mode
- Dark and Light Mode

## Future Features

Things I should implement, but propably won't.
So, I currently implemented all features I can think of. However, I will still
maintain the app and update it to be compatible with future OS versions.

- Automatically check with GitHub if there is a newer version
If you have ideas for new features or bug reports, feel free to open an issue.

## Build it yourself

Expand Down
202 changes: 119 additions & 83 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tu_wien_addressbook/models/tiss_login_manager.dart';
import 'package:tu_wien_addressbook/models/update_manager.dart';
import 'package:tu_wien_addressbook/screens/login_screen.dart';
import 'package:tu_wien_addressbook/screens/person_search.dart';
import 'package:tu_wien_addressbook/screens/settings_screen.dart';
Expand Down Expand Up @@ -60,7 +61,7 @@ class MainPage extends StatefulWidget {

class _MainPageState extends State<MainPage> {
Future<bool> _isLoggedIn = TissLoginManager().isLoggedIn();

Future<bool> _hasNewerVersion = UpdateManager().hasNewerVersion();
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
Expand Down Expand Up @@ -90,98 +91,133 @@ class _MainPageState extends State<MainPage> {
)
],
),
body: SafeArea(
child: Column(mainAxisSize: MainAxisSize.min, children: [
//Padding(padding: EdgeInsets.only(top: 100)),
Expanded(child: Container()),
Center(
child: Text("Search", style: Theme.of(context).textTheme.headline3),
),
Padding(
padding: EdgeInsets.all(16),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(30.0),
)),
onPressed: () {
showSearch(context: context, delegate: PersonSearch());
},
child: Padding(
padding: EdgeInsets.all(8),
child: Row(
children: [
Icon(
Icons.search,
size: 24,
),
Padding(
padding: EdgeInsets.only(left: 10),
),
Text(
"Students, Employees",
style: TextStyle(fontSize: 20),
body: Stack(
children: [
SafeArea(
child: Column(mainAxisSize: MainAxisSize.min, children: [
Expanded(child: Container()),
Center(
child: Text("Search",
style: Theme.of(context).textTheme.headline3),
),
Padding(
padding: EdgeInsets.all(16),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(30.0),
)),
onPressed: () {
showSearch(context: context, delegate: PersonSearch());
},
child: Padding(
padding: EdgeInsets.all(8),
child: Row(
children: [
Icon(
Icons.search,
size: 24,
),
Padding(
padding: EdgeInsets.only(left: 10),
),
Text(
"Students, Employees",
style: TextStyle(fontSize: 20),
),
],
),
],
),
),
),
FutureBuilder(
future: _isLoggedIn,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData || snapshot.data == true)
return Text("");

return RichText(
text: TextSpan(children: [
TextSpan(
text: 'Login',
style:
TextStyle(decoration: TextDecoration.underline),
recognizer: TapGestureRecognizer()
..onTap = () async {
print('WTF');
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
);
print('NOW');
setState(() {
this._isLoggedIn =
TissLoginManager().isLoggedIn();
});
}),
TextSpan(text: ' to find students.')
]),
);
},
),
Expanded(
child: Container(),
flex: 2,
),
Padding(
padding: EdgeInsets.all(8),
child: RichText(
text: TextSpan(
style: TextStyle(
fontSize:
Theme.of(context).textTheme.caption!.fontSize),
children: [
TextSpan(
text: "Made with ❤️ by ",
),
TextSpan(
text: "flofriday",
style:
TextStyle(decoration: TextDecoration.underline),
//style: TextStyle(decoration: TextDecoration.underline),
recognizer: TapGestureRecognizer()
..onTap = () {
launchInBrowser("https://github.com/flofriday");
}),
]),
),
),
),
]),
),
FutureBuilder(
future: _isLoggedIn,
future: _hasNewerVersion,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData || snapshot.data == true) return Text("");
if (!snapshot.hasData || snapshot.data == false)
return Container();

return RichText(
text: TextSpan(children: [
TextSpan(
text: 'Login',
style: TextStyle(decoration: TextDecoration.underline),
recognizer: TapGestureRecognizer()
..onTap = () async {
print('WTF');
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
);
print('NOW');
setState(() {
this._isLoggedIn = TissLoginManager().isLoggedIn();
});
}),
TextSpan(text: ' to find students.')
]),
return Column(
children: [
MaterialBanner(
leading: Icon(Icons.celebration),
content: Text(
"A new version is available!\nUpdate and get all the new features."),
actions: [
TextButton(
onPressed: () {
launchInBrowser(
"https://github.com/flofriday/TU_Wien_Addressbook/releases/latest");
},
child: Text("UPDATE"),
),
],
)
],
);
},
),
Expanded(
child: Container(),
flex: 2,
),
Padding(
padding: EdgeInsets.all(8),
child: RichText(
text: TextSpan(
style: TextStyle(
fontSize: Theme.of(context).textTheme.caption!.fontSize),
children: [
TextSpan(
text: "Made with ❤️ by ",
),
TextSpan(
text: "flofriday",
style: TextStyle(decoration: TextDecoration.underline),
//style: TextStyle(decoration: TextDecoration.underline),
recognizer: TapGestureRecognizer()
..onTap = () {
launchInBrowser("https://github.com/flofriday");
}),
]),
),
),
]),
],
),
);
}
Expand Down
16 changes: 16 additions & 0 deletions lib/models/github_tag.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:json_annotation/json_annotation.dart';

part 'github_tag.g.dart';

// To build the code generateion run:
// flutter pub run build_runner build
@JsonSerializable()
class Tag {
String name;

Tag({required this.name});

factory Tag.fromJSON(Map<String, dynamic> json) => _$TagFromJson(json);

Map<String, dynamic> toJson() => _$TagToJson(this);
}
17 changes: 17 additions & 0 deletions lib/models/github_tag.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions lib/models/semver.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Model for the very popular semantic versioning
// https://semver.org/
class Semver implements Comparable {
int major;
int minor;
int patch;

Semver(this.major, this.minor, this.patch);

static Semver parse(String text) {
List<String> parts = text.split(".");

if (parts.length != 3)
throw "Semanticversining Validation Error (not enough numbers)";

return Semver(
int.parse(parts[0]),
int.parse(parts[1]),
int.parse(parts[2]),
);
}

@override
int compareTo(dynamic other) {
if (this.major < other.major) return -1;
if (this.major > other.major) return 1;

if (this.minor < other.minor) return -1;
if (this.minor > other.minor) return 1;

if (this.patch < other.patch) return -1;
if (this.patch > other.patch) return 1;

return 0;
}

@override
String toString() {
return "$major.$minor.$patch";
}
}
2 changes: 2 additions & 0 deletions lib/models/tiss.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:tu_wien_addressbook/models/person.dart';

part 'tiss.g.dart';

// To build the code generateion run:
// flutter pub run build_runner build
@JsonSerializable()
class Tiss {
String query;
Expand Down
47 changes: 47 additions & 0 deletions lib/models/update_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'dart:convert';

import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tu_wien_addressbook/models/github_tag.dart';
import 'package:tu_wien_addressbook/models/semver.dart';
import 'package:http/http.dart' as http;

/// This class is used to check with GitHub if there is a new version available.
/// To achive its goal the class gets the name of the latest
/// tag (semantic versioning) and compares it to its own tag. If the tag on
/// GitHub is higher, it means that there is a new version.
class UpdateManager {
UpdateManager();

Future<bool> hasNewerVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
Semver currentVersion = Semver.parse(packageInfo.version);

print("Downloading version from GitHub...");
Uri url = Uri.parse(
"https://api.github.com/repos/flofriday/TU_Wien_Addressbook/tags");
var response = await http.get(url);
if (response.statusCode != 200) {
// Ok the either the internet / service is currently not availabel, or
// we got ratelimited whatever. Just do not update the cache and
// say that there is no version available.
print(
'Requesting the version from GitHub returned ${response.statusCode}');
return false;
}

List<Tag> tags = (jsonDecode(response.body) as List)
.map((data) => Tag.fromJSON(data))
.toList();

// Check if there are maybe just no versions.
if (tags.isEmpty) return false;

// Parse the newest available version and add it to the cache
Semver updateVersion = Semver.parse(tags[0].name);
print("GitHub version: ${updateVersion.toString()}");
print("Current version: ${currentVersion.toString()}");

return currentVersion.compareTo(updateVersion) < 0;
}
}
2 changes: 0 additions & 2 deletions lib/screens/settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ class _SettingsScreenState extends State<SettingsScreen> {

@override
Widget build(BuildContext context) {
print("build");

return Scaffold(
appBar: AppBar(
title: Text("Settings"),
Expand Down

0 comments on commit e696f8f

Please sign in to comment.