Add about page, in-app changelog, Android download
This commit is contained in:
parent
966c02b0e6
commit
bf654ea837
16 changed files with 636 additions and 21 deletions
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
|
@ -7,7 +7,11 @@
|
||||||
{
|
{
|
||||||
"name": "Current Device",
|
"name": "Current Device",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart"
|
"type": "dart",
|
||||||
|
"args": [
|
||||||
|
"--dart-define",
|
||||||
|
"DOWNLOAD=apk"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "info_tren (profile mode)",
|
"name": "info_tren (profile mode)",
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
v2.7.5
|
||||||
|
Added about page and in-app changelog.
|
||||||
|
On Android, added download buttons.
|
||||||
|
|
||||||
v2.7.4
|
v2.7.4
|
||||||
Addressed Android 12 component exporting rule.
|
Addressed Android 12 component exporting rule.
|
||||||
See: https://developer.android.com/about/versions/12/behavior-changes-12#exported
|
See: https://developer.android.com/about/versions/12/behavior-changes-12#exported
|
||||||
|
@ -51,7 +55,6 @@ Tweaks
|
||||||
v2.0.6
|
v2.0.6
|
||||||
Brought feature parity with iOS _(except for v2.0.2, which is iOS specific)_.
|
Brought feature parity with iOS _(except for v2.0.2, which is iOS specific)_.
|
||||||
|
|
||||||
|
|
||||||
v2.0.5
|
v2.0.5
|
||||||
- increased font weight on iOS
|
- increased font weight on iOS
|
||||||
- added support for system bolt font request on iOS
|
- added support for system bolt font request on iOS
|
16
lib/api/releases.dart
Normal file
16
lib/api/releases.dart
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:info_tren/models/changelog_entry.dart';
|
||||||
|
import 'package:info_tren/utils/iterable_extensions.dart';
|
||||||
|
|
||||||
|
Future<List<ChangelogEntry>> getRemoteReleases() async {
|
||||||
|
final Uri uri = Uri.parse('https://gitea.dcdev.ro/api/v1/repos/kbruen/info_tren/releases');
|
||||||
|
final response = await http.get(uri);
|
||||||
|
final json = jsonDecode(response.body) as List<dynamic>;
|
||||||
|
return json.map((e) => ChangelogEntry(
|
||||||
|
version: ChangelogVersion.parse(e['tag_name']),
|
||||||
|
description: e['body'],
|
||||||
|
apkLink: (e['assets'] as List<dynamic>).where((e) => (e['name'] as String).contains('.apk')).map((e) => Uri.parse(e['browser_download_url'] as String)).firstOrNull,
|
||||||
|
)).toList();
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
// import 'package:flutter_redux/flutter_redux.dart';
|
// import 'package:flutter_redux/flutter_redux.dart';
|
||||||
import 'package:info_tren/models/ui_design.dart';
|
import 'package:info_tren/models/ui_design.dart';
|
||||||
|
import 'package:info_tren/pages/about/about_page.dart';
|
||||||
import 'package:info_tren/pages/main/main_page.dart';
|
import 'package:info_tren/pages/main/main_page.dart';
|
||||||
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
|
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
|
||||||
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart';
|
import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart';
|
||||||
|
@ -28,10 +29,19 @@ void main() {
|
||||||
|
|
||||||
Map<String, WidgetBuilder> routesByUiDesign(UiDesign uiDesign) => {
|
Map<String, WidgetBuilder> routesByUiDesign(UiDesign uiDesign) => {
|
||||||
Navigator.defaultRouteName: (context) {
|
Navigator.defaultRouteName: (context) {
|
||||||
return MainPage(uiDesign: uiDesign,);
|
return MainPage(
|
||||||
|
uiDesign: uiDesign,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
AboutPage.routeName: (context) {
|
||||||
|
return AboutPage(
|
||||||
|
uiDesign: uiDesign,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
SelectTrainPage.routeName: (context) {
|
SelectTrainPage.routeName: (context) {
|
||||||
return SelectTrainPage(uiDesign: uiDesign);
|
return SelectTrainPage(
|
||||||
|
uiDesign: uiDesign,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
TrainInfo.routeName: (context) {
|
TrainInfo.routeName: (context) {
|
||||||
return TrainInfo(
|
return TrainInfo(
|
||||||
|
@ -40,7 +50,9 @@ Map<String, WidgetBuilder> routesByUiDesign(UiDesign uiDesign) => {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
SelectStationPage.routeName: (context) {
|
SelectStationPage.routeName: (context) {
|
||||||
return SelectStationPage(uiDesign: uiDesign,);
|
return SelectStationPage(
|
||||||
|
uiDesign: uiDesign,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
ViewStationPage.routeName: (context) {
|
ViewStationPage.routeName: (context) {
|
||||||
return ViewStationPage(
|
return ViewStationPage(
|
||||||
|
@ -80,9 +92,11 @@ class StartPoint extends StatelessWidget {
|
||||||
title: appTitle,
|
title: appTitle,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
primarySwatch: Colors.blue,
|
primarySwatch: Colors.blue,
|
||||||
brightness: Brightness.dark,
|
colorScheme: ColorScheme.fromSwatch(
|
||||||
primaryColor: Colors.blue.shade600,
|
brightness: Brightness.dark,
|
||||||
accentColor: Colors.blue.shade700,
|
primarySwatch: Colors.blue,
|
||||||
|
accentColor: Colors.blue.shade700,
|
||||||
|
),
|
||||||
// fontFamily: 'Atkinson Hyperlegible',
|
// fontFamily: 'Atkinson Hyperlegible',
|
||||||
),
|
),
|
||||||
routes: routesByUiDesign(UiDesign.MATERIAL),
|
routes: routesByUiDesign(UiDesign.MATERIAL),
|
||||||
|
|
75
lib/models/changelog_entry.dart
Normal file
75
lib/models/changelog_entry.dart
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import 'package:quiver/core.dart';
|
||||||
|
|
||||||
|
class ChangelogEntry {
|
||||||
|
final ChangelogVersion version;
|
||||||
|
final String description;
|
||||||
|
final Uri? apkLink;
|
||||||
|
|
||||||
|
ChangelogEntry({required this.version, required this.description, this.apkLink});
|
||||||
|
|
||||||
|
factory ChangelogEntry.fromTextBlock(String text) {
|
||||||
|
final lines = text.split(RegExp(r'(\r?\n)+'));
|
||||||
|
return ChangelogEntry(
|
||||||
|
version: ChangelogVersion.parse(lines.first),
|
||||||
|
description: lines.skip(1).join('\n'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<ChangelogEntry> fromTextFile(String text) {
|
||||||
|
final blocks = text.split(RegExp(r'(\r?\n){2}'));
|
||||||
|
return blocks.map(ChangelogEntry.fromTextBlock).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangelogVersion implements Comparable<ChangelogVersion> {
|
||||||
|
final int major;
|
||||||
|
final int minor;
|
||||||
|
final int patch;
|
||||||
|
final String? prerelease;
|
||||||
|
|
||||||
|
ChangelogVersion(this.major, this.minor, this.patch, [this.prerelease]);
|
||||||
|
|
||||||
|
factory ChangelogVersion.parse(String version) {
|
||||||
|
if (version.startsWith('v')) {
|
||||||
|
version = version.substring(1);
|
||||||
|
}
|
||||||
|
String? prerelease;
|
||||||
|
if (version.contains('-')) {
|
||||||
|
final index = version.indexOf('-');
|
||||||
|
prerelease = version.substring(index + 1);
|
||||||
|
version = version.substring(0, index);
|
||||||
|
}
|
||||||
|
final splitted = version.split('.').map(int.parse).toList();
|
||||||
|
return ChangelogVersion(splitted[0], splitted[1], splitted[2], prerelease);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
final vString = 'v$major.$minor.$patch';
|
||||||
|
return prerelease == null ? vString : '$vString-$prerelease';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator==(dynamic other) {
|
||||||
|
if (other is! ChangelogVersion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return major == other.major && minor == other.minor && patch == other.patch && prerelease == other.prerelease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return hash3(major.hashCode, minor.hashCode, patch.hashCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int compareTo(ChangelogVersion other) {
|
||||||
|
if (major != other.major) {
|
||||||
|
return major.compareTo(other.major);
|
||||||
|
}
|
||||||
|
if (minor != other.minor) {
|
||||||
|
return minor.compareTo(other.minor);
|
||||||
|
}
|
||||||
|
return patch.compareTo(other.patch);
|
||||||
|
}
|
||||||
|
}
|
75
lib/pages/about/about_page.dart
Normal file
75
lib/pages/about/about_page.dart
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:info_tren/api/releases.dart';
|
||||||
|
import 'package:info_tren/models/changelog_entry.dart';
|
||||||
|
import 'package:info_tren/models/ui_design.dart';
|
||||||
|
import 'package:info_tren/pages/about/about_page_cupertino.dart';
|
||||||
|
import 'package:info_tren/pages/about/about_page_material.dart';
|
||||||
|
import 'package:info_tren/utils/default_ui_design.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
|
class AboutPage extends StatefulWidget {
|
||||||
|
final UiDesign? uiDesign;
|
||||||
|
|
||||||
|
const AboutPage({Key? key, this.uiDesign}) : super(key: key);
|
||||||
|
|
||||||
|
static String routeName = '/about';
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() {
|
||||||
|
final uiDesign = this.uiDesign ?? defaultUiDesign;
|
||||||
|
|
||||||
|
switch (uiDesign) {
|
||||||
|
case UiDesign.MATERIAL:
|
||||||
|
return AboutPageStateMaterial();
|
||||||
|
case UiDesign.CUPERTINO:
|
||||||
|
return AboutPageStateCupertino();
|
||||||
|
default:
|
||||||
|
throw UnmatchedUiDesignException(uiDesign);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class AboutPageState extends State<AboutPage> {
|
||||||
|
static const String DOWNLOAD = String.fromEnvironment('DOWNLOAD');
|
||||||
|
|
||||||
|
final String pageTitle = 'Despre aplicație';
|
||||||
|
final String versionTitleText = 'Versiunea aplicației';
|
||||||
|
final String latestVersionText = 'Cea mai recentă versiune';
|
||||||
|
final String currentVersionText = 'Versiunea curentă';
|
||||||
|
|
||||||
|
List<ChangelogEntry> localChangelog = [];
|
||||||
|
List<ChangelogEntry> remoteChangelog = [];
|
||||||
|
List<ChangelogEntry> get mergedChangelogs {
|
||||||
|
final logs = remoteChangelog.toList();
|
||||||
|
final versions = logs.map((log) => log.version).toSet();
|
||||||
|
for (final log in localChangelog) {
|
||||||
|
if (!versions.contains(log.version)) {
|
||||||
|
logs.add(log);
|
||||||
|
versions.add(log.version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logs.sort((l1, l2) => l2.version.compareTo(l1.version));
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageInfo? packageInfo;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
PackageInfo.fromPlatform().then((value) => setState(() {
|
||||||
|
packageInfo = value;
|
||||||
|
}));
|
||||||
|
|
||||||
|
DefaultAssetBundle.of(context).loadString('CHANGELOG.txt').then((value) {
|
||||||
|
setState(() {
|
||||||
|
localChangelog = ChangelogEntry.fromTextFile(value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
getRemoteReleases().then((value) => setState(() {
|
||||||
|
remoteChangelog = value;
|
||||||
|
}));
|
||||||
|
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
}
|
127
lib/pages/about/about_page_cupertino.dart
Normal file
127
lib/pages/about/about_page_cupertino.dart
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:info_tren/components/cupertino_divider.dart';
|
||||||
|
import 'package:info_tren/pages/about/about_page.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
class AboutPageStateCupertino extends AboutPageState {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CupertinoPageScaffold(
|
||||||
|
navigationBar: CupertinoNavigationBar(
|
||||||
|
middle: Text(pageTitle),
|
||||||
|
),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final topPadding = MediaQuery.of(context).padding.top;
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: topPadding,
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'Info Tren',
|
||||||
|
style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (packageInfo != null)
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
packageInfo!.packageName,
|
||||||
|
style: TextStyle(
|
||||||
|
inherit: true,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: CupertinoDivider(),
|
||||||
|
),
|
||||||
|
for (final log in mergedChangelogs) ...[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
log.version.toString(),
|
||||||
|
style: TextStyle(
|
||||||
|
inherit: true,
|
||||||
|
fontSize: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (localChangelog.isNotEmpty && log.version == localChangelog.first.version)
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: CupertinoTheme.of(context).textTheme.textStyle.color ?? CupertinoColors.inactiveGray,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Text(
|
||||||
|
currentVersionText,
|
||||||
|
style: TextStyle(
|
||||||
|
inherit: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (remoteChangelog.isNotEmpty && log.version == remoteChangelog.first.version && (localChangelog.isEmpty || localChangelog.first.version != log.version))
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: CupertinoColors.activeGreen,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Text(
|
||||||
|
latestVersionText,
|
||||||
|
style: TextStyle(
|
||||||
|
inherit: true,
|
||||||
|
color: CupertinoColors.activeGreen,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (AboutPageState.DOWNLOAD == 'apk' && log.apkLink != null)
|
||||||
|
CupertinoButton(
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
minSize: 0,
|
||||||
|
onPressed: () {
|
||||||
|
launchUrl(log.apkLink!);
|
||||||
|
},
|
||||||
|
child: Icon(CupertinoIcons.arrow_down_circle),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
text: log.description,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CupertinoDivider(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
112
lib/pages/about/about_page_material.dart
Normal file
112
lib/pages/about/about_page_material.dart
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:info_tren/pages/about/about_page.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
class AboutPageStateMaterial extends AboutPageState {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(pageTitle),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'Info Tren',
|
||||||
|
style: Theme.of(context).textTheme.displayMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (packageInfo != null)
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
packageInfo!.packageName,
|
||||||
|
style: Theme.of(context).textTheme.caption,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// ListTile(
|
||||||
|
// title: Text(versionTitleText),
|
||||||
|
// subtitle: localChangelog.isEmpty ? null : Text(localChangelog.first.title),
|
||||||
|
// ),
|
||||||
|
Divider(),
|
||||||
|
for (final log in mergedChangelogs) ...[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
log.version.toString(),
|
||||||
|
style: Theme.of(context).textTheme.headline4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (localChangelog.isNotEmpty && log.version == localChangelog.first.version)
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Text(
|
||||||
|
currentVersionText,
|
||||||
|
style: TextStyle(
|
||||||
|
inherit: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (remoteChangelog.isNotEmpty && log.version == remoteChangelog.first.version && (localChangelog.isEmpty || localChangelog.first.version != log.version))
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.green,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Text(
|
||||||
|
latestVersionText,
|
||||||
|
style: TextStyle(
|
||||||
|
inherit: true,
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (AboutPageState.DOWNLOAD == 'apk' && log.apkLink != null)
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
launchUrl(log.apkLink!);
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.download),
|
||||||
|
tooltip: 'Download APK',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
text: log.description,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:info_tren/models/ui_design.dart';
|
import 'package:info_tren/models/ui_design.dart';
|
||||||
|
import 'package:info_tren/pages/about/about_page.dart';
|
||||||
import 'package:info_tren/pages/main/main_page_cupertino.dart';
|
import 'package:info_tren/pages/main/main_page_cupertino.dart';
|
||||||
import 'package:info_tren/pages/main/main_page_material.dart';
|
import 'package:info_tren/pages/main/main_page_material.dart';
|
||||||
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
|
import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart';
|
||||||
|
@ -28,21 +29,31 @@ class MainPage extends StatelessWidget {
|
||||||
|
|
||||||
abstract class MainPageShared extends StatelessWidget {
|
abstract class MainPageShared extends StatelessWidget {
|
||||||
final String pageTitle = 'Info Tren';
|
final String pageTitle = 'Info Tren';
|
||||||
|
final String moreOptionsText = 'Mai multe opțiuni';
|
||||||
|
|
||||||
List<MainPageOption> get options => [
|
List<MainPageAction> get popupMenu => [
|
||||||
MainPageOption(
|
MainPageAction(
|
||||||
|
name: 'Despre aplicație',
|
||||||
|
action: (context) {
|
||||||
|
Navigator.of(context).pushNamed(AboutPage.routeName);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
List<MainPageAction> get options => [
|
||||||
|
MainPageAction(
|
||||||
name: 'Informații despre tren',
|
name: 'Informații despre tren',
|
||||||
action: (BuildContext context) {
|
action: (context) {
|
||||||
onTrainInfoPageInvoke(context);
|
onTrainInfoPageInvoke(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
MainPageOption(
|
MainPageAction(
|
||||||
name: 'Tabelă plecari/sosiri',
|
name: 'Tabelă plecari/sosiri',
|
||||||
action: (context) {
|
action: (context) {
|
||||||
onStationBoardPageInvoke(context);
|
onStationBoardPageInvoke(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
MainPageOption(
|
MainPageAction(
|
||||||
name: 'Planificare rută',
|
name: 'Planificare rută',
|
||||||
// TODO: Implement route planning
|
// TODO: Implement route planning
|
||||||
action: null,
|
action: null,
|
||||||
|
@ -62,9 +73,9 @@ abstract class MainPageShared extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainPageOption {
|
class MainPageAction {
|
||||||
final String name;
|
final String name;
|
||||||
final void Function(BuildContext context)? action;
|
final void Function(BuildContext context)? action;
|
||||||
|
|
||||||
MainPageOption({required this.name, this.action});
|
MainPageAction({required this.name, this.action});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,32 @@ class MainPageCupertino extends MainPageShared {
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
navigationBar: CupertinoNavigationBar(
|
navigationBar: CupertinoNavigationBar(
|
||||||
middle: Text(pageTitle),
|
middle: Text(pageTitle),
|
||||||
|
trailing: CupertinoButton(
|
||||||
|
child: Icon(CupertinoIcons.ellipsis_circle),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () {
|
||||||
|
showCupertinoModalPopup(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return CupertinoActionSheet(
|
||||||
|
actions: popupMenu.map((m) => CupertinoActionSheetAction(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
m.action?.call(context);
|
||||||
|
},
|
||||||
|
child: Text(m.name),
|
||||||
|
)).toList(),
|
||||||
|
cancelButton: CupertinoActionSheetAction(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text('Anulare'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|
|
@ -8,6 +8,19 @@ class MainPageMaterial extends MainPageShared {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(pageTitle),
|
title: Text(pageTitle),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
actions: [
|
||||||
|
PopupMenuButton<int>(
|
||||||
|
icon: Icon(Icons.more_vert),
|
||||||
|
tooltip: moreOptionsText,
|
||||||
|
itemBuilder: (_) => popupMenu.asMap().entries.map((e) => PopupMenuItem(
|
||||||
|
child: Text(e.value.name),
|
||||||
|
value: e.key,
|
||||||
|
)).toList(),
|
||||||
|
onSelected: (index) {
|
||||||
|
popupMenu[index].action?.call(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|
6
lib/utils/iterable_extensions.dart
Normal file
6
lib/utils/iterable_extensions.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
extension ITerableExtensions<T> on Iterable<T> {
|
||||||
|
T? get firstOrNull {
|
||||||
|
final lst = take(1).toList();
|
||||||
|
return lst.isEmpty ? null : lst.first;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,10 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|
129
pubspec.lock
129
pubspec.lock
|
@ -140,7 +140,7 @@ packages:
|
||||||
name: cupertino_icons
|
name: cupertino_icons
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "1.0.5"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -148,6 +148,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -174,6 +181,11 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.2"
|
version: "0.8.2"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -286,6 +298,48 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
package_info_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: package_info_plus
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.3"
|
||||||
|
package_info_plus_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
package_info_plus_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
package_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
package_info_plus_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
package_info_plus_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -293,6 +347,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.2"
|
version: "1.8.2"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -315,7 +376,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
quiver:
|
quiver:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: quiver
|
name: quiver
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
|
@ -438,6 +499,62 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
url_launcher:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: url_launcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.5"
|
||||||
|
url_launcher_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_android
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.17"
|
||||||
|
url_launcher_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_ios
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.17"
|
||||||
|
url_launcher_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
url_launcher_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
url_launcher_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
url_launcher_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.12"
|
||||||
|
url_launcher_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -459,6 +576,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -468,3 +592,4 @@ packages:
|
||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.17.0 <3.0.0"
|
dart: ">=2.17.0 <3.0.0"
|
||||||
|
flutter: ">=2.10.0"
|
||||||
|
|
11
pubspec.yaml
11
pubspec.yaml
|
@ -14,7 +14,7 @@ description: O aplicație de vizualizare a datelor puse la dispoziție de Inform
|
||||||
version: 2.7.4
|
version: 2.7.4
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.12.0 <3.0.0"
|
sdk: ">=2.15.0 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
|
@ -22,13 +22,15 @@ dependencies:
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
# cupertino_icons: ^0.1.2
|
cupertino_icons: ^1.0.5
|
||||||
rxdart: ^0.22.0
|
rxdart: ^0.22.0
|
||||||
http: ^0.13.0
|
http: ^0.13.0
|
||||||
cupertino_icons: ^0.1.2
|
|
||||||
tuple: ^2.0.0
|
tuple: ^2.0.0
|
||||||
sprintf: ^6.0.0
|
sprintf: ^6.0.0
|
||||||
flutter_redux: ^0.8.2
|
flutter_redux: ^0.8.2
|
||||||
|
package_info_plus: ^1.4.3
|
||||||
|
quiver: ^3.1.0
|
||||||
|
url_launcher: ^6.1.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
# flutter_test:
|
# flutter_test:
|
||||||
|
@ -49,7 +51,8 @@ flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
# assets:
|
assets:
|
||||||
|
- CHANGELOG.txt
|
||||||
# - images/a_dot_burr.jpeg
|
# - images/a_dot_burr.jpeg
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue