mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Add About OOTT page with version, links, and legal notices
Implements the /about route with app description, version info, clickable links to the source repo and AGPL-3.0 license, and third-party copyright notices. Adds url_launcher for cross-platform link handling. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
93f40b892d
commit
97e4bb1000
@@ -0,0 +1,293 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
const _version = '0.1.0';
|
||||
const _releaseDate = 'May 28, 2026';
|
||||
const _repoUrl = 'https://github.com/rzuasti/oott';
|
||||
const _licenseUrl = 'https://www.gnu.org/licenses/agpl-3.0.html';
|
||||
const _licenseName = 'GNU Affero General Public License v3 (AGPL-3.0)';
|
||||
|
||||
class About extends StatelessWidget {
|
||||
const About({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('About OOTT')),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Easy to setup and use network device discovery and alert system. '
|
||||
'Notifies you when new or unknown devices join your local area network.',
|
||||
style: textTheme.bodyLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'v$_version - released $_releaseDate',
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_SurfaceContainer(
|
||||
colorScheme: colorScheme,
|
||||
child: Column(
|
||||
children: [
|
||||
_LinkRow(
|
||||
icon: Icons.code,
|
||||
label: 'Source code',
|
||||
url: _repoUrl,
|
||||
colorScheme: colorScheme,
|
||||
textTheme: textTheme,
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: colorScheme.outlineVariant,
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
),
|
||||
_LicenseRow(colorScheme: colorScheme, textTheme: textTheme),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_SurfaceContainer(
|
||||
colorScheme: colorScheme,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: _NoticesSection(textTheme: textTheme),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SurfaceContainer extends StatelessWidget {
|
||||
const _SurfaceContainer({
|
||||
required this.colorScheme,
|
||||
required this.child,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
final ColorScheme colorScheme;
|
||||
final Widget child;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: padding,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LinkRow extends StatelessWidget {
|
||||
const _LinkRow({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.url,
|
||||
required this.colorScheme,
|
||||
required this.textTheme,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String url;
|
||||
final ColorScheme colorScheme;
|
||||
final TextTheme textTheme;
|
||||
|
||||
Future<void> _open() async {
|
||||
final uri = Uri.parse(url);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: _open,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 18, color: colorScheme.primary),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
label,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LicenseRow extends StatelessWidget {
|
||||
const _LicenseRow({required this.colorScheme, required this.textTheme});
|
||||
|
||||
final ColorScheme colorScheme;
|
||||
final TextTheme textTheme;
|
||||
|
||||
Future<void> _open() async {
|
||||
final uri = Uri.parse(_licenseUrl);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.gavel, size: 18, color: colorScheme.primary),
|
||||
const SizedBox(width: 12),
|
||||
Flexible(
|
||||
child: Wrap(
|
||||
children: [
|
||||
Text('Licensed under ', style: textTheme.bodyMedium),
|
||||
InkWell(
|
||||
onTap: _open,
|
||||
child: Text(
|
||||
_licenseName,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NoticesSection extends StatelessWidget {
|
||||
const _NoticesSection({required this.textTheme});
|
||||
|
||||
final TextTheme textTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'OOTT, Copyright (C) 2024-2026 Ricardo Zuasti',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'This product includes software developed by third parties and distributed '
|
||||
'under the Apache License, Version 2.0.',
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
..._thirdPartyComponents.map(
|
||||
(c) => _ThirdPartyEntry(component: c, textTheme: textTheme),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ThirdPartyEntry extends StatelessWidget {
|
||||
const _ThirdPartyEntry({required this.component, required this.textTheme});
|
||||
|
||||
final _ThirdPartyComponent component;
|
||||
final TextTheme textTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
component.name,
|
||||
style: textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
Text(
|
||||
component.copyright,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
SelectableText(
|
||||
component.url,
|
||||
style: textTheme.bodySmall?.copyWith(color: colorScheme.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ThirdPartyComponent {
|
||||
const _ThirdPartyComponent({
|
||||
required this.name,
|
||||
required this.copyright,
|
||||
required this.url,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String copyright;
|
||||
final String url;
|
||||
}
|
||||
|
||||
const _thirdPartyComponents = [
|
||||
_ThirdPartyComponent(
|
||||
name: 'openssl',
|
||||
copyright:
|
||||
'Copyright 2011-2017 Google Inc., 2013 Jack Lloyd, 2013-2014 Steven Fackler. Apache-2.0.',
|
||||
url: 'https://github.com/rust-openssl/rust-openssl',
|
||||
),
|
||||
_ThirdPartyComponent(
|
||||
name: 'rusqlite_migration',
|
||||
copyright: 'Copyright Clément Joly and contributors. Apache-2.0.',
|
||||
url: 'https://github.com/cljoly/rusqlite_migration',
|
||||
),
|
||||
_ThirdPartyComponent(
|
||||
name: 'sync_wrapper',
|
||||
copyright: 'Copyright Actyx AG. Apache-2.0.',
|
||||
url: 'https://github.com/Actyx/sync_wrapper',
|
||||
),
|
||||
_ThirdPartyComponent(
|
||||
name: 'zopfli',
|
||||
copyright:
|
||||
'Copyright the zopfli-rs contributors. Based on the original Zopfli C code by Google. Apache-2.0.',
|
||||
url: 'https://github.com/zopfli-rs/zopfli',
|
||||
),
|
||||
];
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:frontend/about/about.dart';
|
||||
import 'package:frontend/settings/settings.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
@@ -47,7 +48,7 @@ final GoRouter router = GoRouter(
|
||||
GoRoute(
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
builder: (context, state) => const Placeholder(),
|
||||
builder: (context, state) => const About(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <encrypter/encrypter_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) encrypter_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "EncrypterPlugin");
|
||||
encrypter_plugin_register_with_registrar(encrypter_registrar);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
encrypter
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@@ -7,8 +7,10 @@ import Foundation
|
||||
|
||||
import encrypter
|
||||
import shared_preferences_foundation
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
EncrypterPlugin.register(with: registry.registrar(forPlugin: "EncrypterPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
||||
|
||||
@@ -589,6 +589,70 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.30"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.1"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.5"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.5"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -18,6 +18,7 @@ dependencies:
|
||||
infinite_scroll_pagination: ^5.1.1
|
||||
fl_chart: ^1.2.0
|
||||
google_fonts: ^6.2.1
|
||||
url_launcher: ^6.3.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <encrypter/encrypter_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
EncrypterPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("EncrypterPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
encrypter
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Reference in New Issue
Block a user