complete project
51
.gitignore
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
|
|
||||||
|
# Rust related
|
||||||
|
.cargo/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Generated messages
|
||||||
|
*/**/messages/
|
6
.gitmodules
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[submodule "packages/real_time_chart"]
|
||||||
|
path = packages/real_time_chart
|
||||||
|
url = https://avoid.sh/bachelor/real_time_chart.git
|
||||||
|
[submodule "packages/virtual_keyboard_multi_language"]
|
||||||
|
path = packages/virtual_keyboard_multi_language
|
||||||
|
url = https://avoid.sh/bachelor/virtual_keyboard_multi_language.git
|
33
.metadata
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "7482962148e8d758338d8a28f589f317e1e42ba4"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||||
|
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||||
|
- platform: linux
|
||||||
|
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||||
|
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||||
|
- platform: macos
|
||||||
|
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||||
|
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
3
.puro.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"env": "bachelor_elinux"
|
||||||
|
}
|
19
.vscode/c_cpp_properties.json
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Mac",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/**"
|
||||||
|
],
|
||||||
|
"defines": [],
|
||||||
|
"macFrameworkPath": [
|
||||||
|
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
|
||||||
|
],
|
||||||
|
"compilerPath": "/usr/bin/clang",
|
||||||
|
"cStandard": "c17",
|
||||||
|
"cppStandard": "c++17",
|
||||||
|
"intelliSenseMode": "macos-clang-arm64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
4
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"dart.flutterSdkPath": "/Users/fabian/.puro/envs/bachelor_elinux/flutter",
|
||||||
|
"dart.sdkPath": "/Users/fabian/.puro/envs/bachelor_elinux/flutter/bin/cache/dart-sdk"
|
||||||
|
}
|
16
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "make",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "dart run build_runner build --delete-conflicting-outputs",
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
29
LICENSE
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2023, Fabian Baldeau
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
17
Makefile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
release:
|
||||||
|
flutter pub global run flutterpi_tool build --arch=arm64 --cpu=pi4 --release
|
||||||
|
|
||||||
|
profile:
|
||||||
|
flutter pub global run flutterpi_tool build --arch=arm64 --cpu=pi4 --profile
|
||||||
|
|
||||||
|
sync-release:
|
||||||
|
rsync -a ./build/flutter_assets/ fabian@raspberrypi:/home/fabian/release
|
||||||
|
|
||||||
|
sync-profile:
|
||||||
|
rsync -a ./build/flutter_assets/ fabian@raspberrypi:/home/fabian/profile
|
||||||
|
|
||||||
|
stop:
|
||||||
|
ssh -o BatchMode=no fabian@raspberrypi pkill flutter-pi
|
||||||
|
|
||||||
|
dep-graph:
|
||||||
|
layerlens
|
16
README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# flutter_elinux
|
||||||
|
|
||||||
|
A demo Flutter project for embedded Linux systems.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
This project is a starting point for a Flutter application.
|
||||||
|
|
||||||
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
|
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||||
|
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||||
|
|
||||||
|
For help getting started with Flutter development, view the
|
||||||
|
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||||
|
samples, guidance on mobile development, and a full API reference.
|
32
analysis_options.yaml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
plugins:
|
||||||
|
- custom_lint
|
BIN
assets/images/flame_benchmark/rogue_shooter/bullet.png
Normal file
After Width: | Height: | Size: 343 B |
BIN
assets/images/flame_benchmark/rogue_shooter/enemy.png
Normal file
After Width: | Height: | Size: 389 B |
BIN
assets/images/flame_benchmark/rogue_shooter/explosion.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/images/flame_benchmark/rogue_shooter/player.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
assets/images/flame_benchmark/rogue_shooter/stars.png
Normal file
After Width: | Height: | Size: 332 B |
BIN
assets/images/flame_benchmark/sprite_benchmark/ember.png
Normal file
After Width: | Height: | Size: 851 B |
BIN
assets/img/image_placeholder.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
assets/img/raspi_logo.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
assets/img/tagesschau_logo.jpg
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
assets/rive/dark_light_switch.riv
Normal file
BIN
assets/rive/walk_cycle_blend.riv
Normal file
BIN
assets/rive/weather_icon_animation.riv
Normal file
BIN
assets/video/video_1080p.mp4
Normal file
BIN
assets/video/video_2160p.mp4
Normal file
BIN
assets/video/video_720p.mp4
Normal file
36
lib/app.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'common/settings/app_settings.dart';
|
||||||
|
import 'constants/colors.dart';
|
||||||
|
import 'common/routing/router.dart';
|
||||||
|
|
||||||
|
class App extends ConsumerWidget {
|
||||||
|
const App({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
bool useMaterial3 = ref.watch(useMaterial3Provider);
|
||||||
|
bool useDarkMode = ref.watch(useDarkModeProvider);
|
||||||
|
bool showPerformanceOverlay = ref.watch(showPerformanceOverlayProvider);
|
||||||
|
ColorSeed colorSeedSelected = ref.watch(colorSeedSelectedProvider);
|
||||||
|
|
||||||
|
return MaterialApp.router(
|
||||||
|
showPerformanceOverlay: showPerformanceOverlay,
|
||||||
|
routerConfig: goRouter,
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
title: 'eLinux',
|
||||||
|
theme: ThemeData(
|
||||||
|
colorSchemeSeed: colorSeedSelected.color,
|
||||||
|
useMaterial3: useMaterial3,
|
||||||
|
brightness: Brightness.light,
|
||||||
|
),
|
||||||
|
darkTheme: ThemeData(
|
||||||
|
colorSchemeSeed: colorSeedSelected.color,
|
||||||
|
useMaterial3: useMaterial3,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
themeMode: useDarkMode ? ThemeMode.dark : ThemeMode.light,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
3
lib/common/extension/getCharCodes.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
extension StringExtension on String {
|
||||||
|
Iterable<int> getCharCodes() => codeUnits;
|
||||||
|
}
|
11
lib/common/extension/toFormatedNewsDate.dart
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
extension StringExtension on String {
|
||||||
|
String toFormattedNewsDate() {
|
||||||
|
final parsedDate = DateTime.parse(this);
|
||||||
|
|
||||||
|
// The intl package is used here for more robust formatting options
|
||||||
|
final DateFormat formatter = DateFormat('dd.MM.yyyy HH:mm', 'de_DE');
|
||||||
|
return "Stand: ${formatter.format(parsedDate)} Uhr";
|
||||||
|
}
|
||||||
|
}
|
203
lib/common/routing/router.dart
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:nm/nm.dart';
|
||||||
|
|
||||||
|
import '../../features/benchmark/presentation/benchmark_screen.dart';
|
||||||
|
import '../../features/benchmark/presentation/map_benchmark_screen.dart';
|
||||||
|
import '../../features/benchmark/presentation/rogue_shooter_screen.dart';
|
||||||
|
import '../../features/benchmark/presentation/sprite_benchmark_screen.dart';
|
||||||
|
import '../../features/benchmark/presentation/video_benchmark_screen.dart';
|
||||||
|
import '../../features/benchmark/presentation/vsync_benchmark_screen.dart';
|
||||||
|
import '../../features/home/presentation/home_screen.dart';
|
||||||
|
import '../../features/material_demo/presentation/material_demo_screen.dart';
|
||||||
|
import '../../features/matrix_rgb/presentation/matrix_screen.dart';
|
||||||
|
import '../../features/morse_led/presentation/led_screen.dart';
|
||||||
|
import '../../features/news_api/presentation/news_screen.dart';
|
||||||
|
import '../../features/settings/presentation/settings_screen/settings_screen.dart';
|
||||||
|
import '../../features/settings/presentation/wifi_settings_screen/accesspoint_screen.dart';
|
||||||
|
import '../../features/settings/presentation/wifi_settings_screen/wifi_settings_screen.dart';
|
||||||
|
import '../../features/system_resources/presentation/system_resources_screen.dart';
|
||||||
|
import '../widgets/home_scaffold.dart';
|
||||||
|
import 'routes.dart';
|
||||||
|
|
||||||
|
const ValueKey<String> _scaffoldKey = ValueKey<String>('App scaffold');
|
||||||
|
|
||||||
|
final goRouter = GoRouter(
|
||||||
|
initialLocation: '/',
|
||||||
|
debugLogDiagnostics: false,
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
redirect: (_, __) => '/home',
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/home',
|
||||||
|
name: RoutesHome.home.name,
|
||||||
|
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||||
|
FadeTransitionPage(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
child: const HomeScaffold(
|
||||||
|
selectedTab: RoutesHome.home,
|
||||||
|
child: HomeScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/material-demo',
|
||||||
|
name: RoutesHome.materialDemo.name,
|
||||||
|
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||||
|
FadeTransitionPage(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
child: const HomeScaffold(
|
||||||
|
selectedTab: RoutesHome.materialDemo,
|
||||||
|
child: MaterialDemoScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/news',
|
||||||
|
name: RoutesHome.news.name,
|
||||||
|
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||||
|
FadeTransitionPage(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
child: const HomeScaffold(
|
||||||
|
selectedTab: RoutesHome.news,
|
||||||
|
child: NewsScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/matrix',
|
||||||
|
name: RoutesHome.matrix.name,
|
||||||
|
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||||
|
FadeTransitionPage(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
child: const HomeScaffold(
|
||||||
|
selectedTab: RoutesHome.matrix,
|
||||||
|
child: MatrixScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/led',
|
||||||
|
name: RoutesHome.led.name,
|
||||||
|
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||||
|
FadeTransitionPage(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
child: const HomeScaffold(
|
||||||
|
selectedTab: RoutesHome.led,
|
||||||
|
child: LedScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/system',
|
||||||
|
name: RoutesHome.system.name,
|
||||||
|
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||||
|
FadeTransitionPage(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
child: const HomeScaffold(
|
||||||
|
selectedTab: RoutesHome.system,
|
||||||
|
child: SystemResourcesScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/benchmark',
|
||||||
|
name: RoutesHome.benchmark.name,
|
||||||
|
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||||
|
FadeTransitionPage(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
child: const HomeScaffold(
|
||||||
|
selectedTab: RoutesHome.benchmark,
|
||||||
|
child: BenchmarkScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: RoutesBenchmark.sprites.name,
|
||||||
|
name: RoutesBenchmark.sprites.name,
|
||||||
|
builder: (context, state) => const SpriteBenchmarkScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: RoutesBenchmark.rogueShooter.name,
|
||||||
|
name: RoutesBenchmark.rogueShooter.name,
|
||||||
|
builder: (context, state) => const RogueShooterScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: RoutesBenchmark.shader.name,
|
||||||
|
name: RoutesBenchmark.shader.name,
|
||||||
|
builder: (context, state) => const VsyncBenchmarkScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: RoutesBenchmark.video.name,
|
||||||
|
name: RoutesBenchmark.video.name,
|
||||||
|
builder: (context, state) => const VideoBenchmarkScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: RoutesBenchmark.map.name,
|
||||||
|
name: RoutesBenchmark.map.name,
|
||||||
|
builder: (context, state) => const MapBenchmarkScreen(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/settings',
|
||||||
|
name: RoutesHome.settings.name,
|
||||||
|
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||||
|
FadeTransitionPage(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
child: const HomeScaffold(
|
||||||
|
selectedTab: RoutesHome.settings,
|
||||||
|
child: SettingsScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: RoutesSettings.wifi.name,
|
||||||
|
name: RoutesSettings.wifi.name,
|
||||||
|
builder: (context, state) => const WifiSettingsScreen(),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: 'accesspoint',
|
||||||
|
name: RoutesSettings.accesspoint.name,
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state.extra == null) {
|
||||||
|
return const AccessPointScreen();
|
||||||
|
}
|
||||||
|
final accessPoint = state.extra as NetworkManagerAccessPoint;
|
||||||
|
return AccessPointScreen(
|
||||||
|
accessPoint: accessPoint,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// A page that fades in and out.
|
||||||
|
/// From the official Go Router Example:
|
||||||
|
/// https://github.com/flutter/packages/blob/6701c9e618041574cf4cc53cb028a38de8322b53/packages/go_router/example/lib/books/main.dart#L154
|
||||||
|
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
class FadeTransitionPage extends CustomTransitionPage<void> {
|
||||||
|
/// Creates a [FadeTransitionPage].
|
||||||
|
FadeTransitionPage({
|
||||||
|
required LocalKey super.key,
|
||||||
|
required super.child,
|
||||||
|
}) : super(
|
||||||
|
transitionsBuilder: (BuildContext context,
|
||||||
|
Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation,
|
||||||
|
Widget child) =>
|
||||||
|
FadeTransition(
|
||||||
|
opacity: animation.drive(_curveTween),
|
||||||
|
child: child,
|
||||||
|
));
|
||||||
|
|
||||||
|
static final CurveTween _curveTween = CurveTween(curve: Curves.easeIn);
|
||||||
|
}
|
29
lib/common/routing/routes.dart
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
enum RoutesHome {
|
||||||
|
home,
|
||||||
|
materialDemo,
|
||||||
|
benchmark,
|
||||||
|
news,
|
||||||
|
led,
|
||||||
|
matrix,
|
||||||
|
system,
|
||||||
|
settings,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RoutesSettings {
|
||||||
|
wifi,
|
||||||
|
accesspoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RoutesBenchmark {
|
||||||
|
sprites,
|
||||||
|
rogueShooter,
|
||||||
|
shader,
|
||||||
|
video,
|
||||||
|
map,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RoutesEndlessRunner {
|
||||||
|
play,
|
||||||
|
session,
|
||||||
|
settings,
|
||||||
|
}
|
61
lib/common/settings/app_settings.dart
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
import '../../constants/colors.dart';
|
||||||
|
import 'sharedPrefs.dart';
|
||||||
|
|
||||||
|
part 'app_settings.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class UseMaterial3 extends _$UseMaterial3 {
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
final bool material3 = SharedPrefs().getValue('material3') ?? true;
|
||||||
|
return material3;
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggle() {
|
||||||
|
SharedPrefs().setValue('material3', !state);
|
||||||
|
state = !state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class UseDarkMode extends _$UseDarkMode {
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
final bool darkMode = SharedPrefs().getValue('darkMode') ?? false;
|
||||||
|
return darkMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggle() {
|
||||||
|
SharedPrefs().setValue('darkMode', !state);
|
||||||
|
state = !state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class ColorSeedSelected extends _$ColorSeedSelected {
|
||||||
|
@override
|
||||||
|
ColorSeed build() {
|
||||||
|
final String colorSeed =
|
||||||
|
SharedPrefs().getValue('colorSeed') ?? ColorSeed.baseColor.label;
|
||||||
|
return ColorSeed.values.firstWhere((element) => element.label == colorSeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setColorSeed(ColorSeed colorSeed) {
|
||||||
|
SharedPrefs().setValue('colorSeed', colorSeed.label);
|
||||||
|
state = colorSeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class ShowPerformanceOverlay extends _$ShowPerformanceOverlay {
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggle() {
|
||||||
|
state = !state;
|
||||||
|
}
|
||||||
|
}
|
73
lib/common/settings/app_settings.g.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'app_settings.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$useMaterial3Hash() => r'38dbfd0b19379fa8ee73c1b0ec03dfe914dc23bc';
|
||||||
|
|
||||||
|
/// See also [UseMaterial3].
|
||||||
|
@ProviderFor(UseMaterial3)
|
||||||
|
final useMaterial3Provider =
|
||||||
|
AutoDisposeNotifierProvider<UseMaterial3, bool>.internal(
|
||||||
|
UseMaterial3.new,
|
||||||
|
name: r'useMaterial3Provider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product') ? null : _$useMaterial3Hash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$UseMaterial3 = AutoDisposeNotifier<bool>;
|
||||||
|
String _$useDarkModeHash() => r'd9f1ffc5d34dc2c9a59b99e0393b8f5ccc0a7040';
|
||||||
|
|
||||||
|
/// See also [UseDarkMode].
|
||||||
|
@ProviderFor(UseDarkMode)
|
||||||
|
final useDarkModeProvider =
|
||||||
|
AutoDisposeNotifierProvider<UseDarkMode, bool>.internal(
|
||||||
|
UseDarkMode.new,
|
||||||
|
name: r'useDarkModeProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product') ? null : _$useDarkModeHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$UseDarkMode = AutoDisposeNotifier<bool>;
|
||||||
|
String _$colorSeedSelectedHash() => r'13745dbe8aee72eb6d43f49cb0d155164a96eb8c';
|
||||||
|
|
||||||
|
/// See also [ColorSeedSelected].
|
||||||
|
@ProviderFor(ColorSeedSelected)
|
||||||
|
final colorSeedSelectedProvider =
|
||||||
|
AutoDisposeNotifierProvider<ColorSeedSelected, ColorSeed>.internal(
|
||||||
|
ColorSeedSelected.new,
|
||||||
|
name: r'colorSeedSelectedProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$colorSeedSelectedHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$ColorSeedSelected = AutoDisposeNotifier<ColorSeed>;
|
||||||
|
String _$showPerformanceOverlayHash() =>
|
||||||
|
r'8fb9b73ed0c32730b2b83cf0e6db78f066603ac4';
|
||||||
|
|
||||||
|
/// See also [ShowPerformanceOverlay].
|
||||||
|
@ProviderFor(ShowPerformanceOverlay)
|
||||||
|
final showPerformanceOverlayProvider =
|
||||||
|
AutoDisposeNotifierProvider<ShowPerformanceOverlay, bool>.internal(
|
||||||
|
ShowPerformanceOverlay.new,
|
||||||
|
name: r'showPerformanceOverlayProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$showPerformanceOverlayHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$ShowPerformanceOverlay = AutoDisposeNotifier<bool>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
88
lib/common/settings/custom_layout.dart
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import 'package:virtual_keyboard_multi_language/virtual_keyboard_multi_language.dart';
|
||||||
|
|
||||||
|
// example custom layout file (example from the virtual_keyboard_multi_language package)
|
||||||
|
// here different layouts for different languages could be defined:
|
||||||
|
|
||||||
|
class CustomLayoutKeys extends VirtualKeyboardLayoutKeys {
|
||||||
|
@override
|
||||||
|
int getLanguagesCount() => 2;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<List> getLanguage(int index) {
|
||||||
|
switch (index) {
|
||||||
|
case 1:
|
||||||
|
return _arabicLayout;
|
||||||
|
default:
|
||||||
|
return defaultEnglishLayout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const List<List> _arabicLayout = [
|
||||||
|
// Row 1
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
'2',
|
||||||
|
'3',
|
||||||
|
'4',
|
||||||
|
'5',
|
||||||
|
'6',
|
||||||
|
'7',
|
||||||
|
'8',
|
||||||
|
'9',
|
||||||
|
'0',
|
||||||
|
],
|
||||||
|
// Row 2
|
||||||
|
[
|
||||||
|
'ض',
|
||||||
|
'ص',
|
||||||
|
'ث',
|
||||||
|
'ق',
|
||||||
|
'ف',
|
||||||
|
'غ',
|
||||||
|
'ع',
|
||||||
|
'ه',
|
||||||
|
'خ',
|
||||||
|
'ح',
|
||||||
|
'د',
|
||||||
|
VirtualKeyboardKeyAction.Backspace
|
||||||
|
],
|
||||||
|
// Row 3
|
||||||
|
[
|
||||||
|
'ش',
|
||||||
|
'س',
|
||||||
|
'ي',
|
||||||
|
'ب',
|
||||||
|
'ل',
|
||||||
|
'ا',
|
||||||
|
'ت',
|
||||||
|
'ن',
|
||||||
|
'م',
|
||||||
|
'ك',
|
||||||
|
'ط',
|
||||||
|
VirtualKeyboardKeyAction.Return
|
||||||
|
],
|
||||||
|
// Row 4
|
||||||
|
[
|
||||||
|
'ذ',
|
||||||
|
'ئ',
|
||||||
|
'ء',
|
||||||
|
'ؤ',
|
||||||
|
'ر',
|
||||||
|
'لا',
|
||||||
|
'ى',
|
||||||
|
'ة',
|
||||||
|
'و',
|
||||||
|
'.',
|
||||||
|
'ظ',
|
||||||
|
VirtualKeyboardKeyAction.Shift
|
||||||
|
],
|
||||||
|
// Row 5
|
||||||
|
[
|
||||||
|
VirtualKeyboardKeyAction.SwithLanguage,
|
||||||
|
'@',
|
||||||
|
VirtualKeyboardKeyAction.Space,
|
||||||
|
'-',
|
||||||
|
'_',
|
||||||
|
]
|
||||||
|
];
|
31
lib/common/settings/sharedPrefs.dart
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
/// A class to handle shared preferences
|
||||||
|
class SharedPrefs {
|
||||||
|
late final SharedPreferences _sharedPrefs;
|
||||||
|
|
||||||
|
static final SharedPrefs _instance = SharedPrefs._internal();
|
||||||
|
factory SharedPrefs() => _instance;
|
||||||
|
SharedPrefs._internal();
|
||||||
|
Future<void> init() async {
|
||||||
|
_sharedPrefs = await SharedPreferences.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(String key, dynamic value) {
|
||||||
|
if (value is bool) {
|
||||||
|
_sharedPrefs.setBool(key, value);
|
||||||
|
} else if (value is int) {
|
||||||
|
_sharedPrefs.setInt(key, value);
|
||||||
|
} else if (value is double) {
|
||||||
|
_sharedPrefs.setDouble(key, value);
|
||||||
|
} else if (value is String) {
|
||||||
|
_sharedPrefs.setString(key, value);
|
||||||
|
} else if (value is List<String>) {
|
||||||
|
_sharedPrefs.setStringList(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic getValue(String key) {
|
||||||
|
return _sharedPrefs.get(key);
|
||||||
|
}
|
||||||
|
}
|
13
lib/common/utils/linux_shutdown.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
Future<void> linuxShutdown({bool restart = false, int delay = 0}) {
|
||||||
|
final params = <String>[];
|
||||||
|
|
||||||
|
if (restart) {
|
||||||
|
params.add('-r');
|
||||||
|
}
|
||||||
|
|
||||||
|
params.addAll(<String>['-t', delay.toString(), 'now']);
|
||||||
|
|
||||||
|
return Process.run('sudo', ['shutdown', ...params]);
|
||||||
|
}
|
6
lib/common/utils/time_stream.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Stream<DateTime> timeStream() async* {
|
||||||
|
while (true) {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
yield DateTime.now();
|
||||||
|
}
|
||||||
|
}
|
30
lib/common/widgets/clock.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../utils/time_stream.dart';
|
||||||
|
|
||||||
|
class ClockWidget extends ConsumerWidget {
|
||||||
|
const ClockWidget({this.textStyle, super.key});
|
||||||
|
|
||||||
|
final TextStyle? textStyle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return StreamBuilder<DateTime>(
|
||||||
|
stream: timeStream(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
DateTime time = snapshot.data ?? DateTime.now();
|
||||||
|
DateFormat format = DateFormat('HH:mm:ss');
|
||||||
|
return Text(
|
||||||
|
format.format(time),
|
||||||
|
style: textStyle,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const CircularProgressIndicator();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
52
lib/common/widgets/floating_menu_button.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
class FloatingMenuButton extends StatelessWidget {
|
||||||
|
const FloatingMenuButton({this.popupMenuButton, super.key});
|
||||||
|
|
||||||
|
final PopupMenuButton? popupMenuButton;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PositionedDirectional(
|
||||||
|
start: 16,
|
||||||
|
top: 16,
|
||||||
|
child: SafeArea(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
borderRadius: BorderRadius.circular(999),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
// check if dark mode
|
||||||
|
color: (Theme.of(context).brightness == Brightness.light)
|
||||||
|
? Colors.grey.withOpacity(0.5)
|
||||||
|
: Colors.black.withOpacity(0.5),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 7,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
),
|
||||||
|
if (popupMenuButton != null)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
popupMenuButton!,
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
100
lib/common/widgets/home_scaffold.dart
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../routing/routes.dart';
|
||||||
|
import 'scaffold_with_statusbar.dart';
|
||||||
|
|
||||||
|
/// The Scaffold that is used for the home screen.
|
||||||
|
class HomeScaffold extends StatelessWidget {
|
||||||
|
const HomeScaffold({
|
||||||
|
required this.selectedTab,
|
||||||
|
required this.child,
|
||||||
|
this.appBar,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Which tab of the scaffold to display.
|
||||||
|
final RoutesHome selectedTab;
|
||||||
|
|
||||||
|
/// The scaffold body.
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
/// The scaffold AppBar.
|
||||||
|
final AppBar? appBar;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => ScaffoldWithStatusbar(
|
||||||
|
body: AdaptiveScaffold(
|
||||||
|
appBar: appBar,
|
||||||
|
smallBreakpoint: const WidthPlatformBreakpoint(end: 700),
|
||||||
|
mediumBreakpoint:
|
||||||
|
const WidthPlatformBreakpoint(begin: 700, end: 1400),
|
||||||
|
largeBreakpoint: const WidthPlatformBreakpoint(begin: 1400),
|
||||||
|
body: (_) => child,
|
||||||
|
selectedIndex: selectedTab.index,
|
||||||
|
destinations: const <NavigationDestination>[
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'Home',
|
||||||
|
icon: Icon(Icons.home_outlined),
|
||||||
|
selectedIcon: Icon(Icons.home),
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'Material Demo',
|
||||||
|
icon: Icon(Icons.widgets_outlined),
|
||||||
|
selectedIcon: Icon(Icons.widgets),
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'Benchmark',
|
||||||
|
icon: Icon(Icons.fireplace_outlined),
|
||||||
|
selectedIcon: Icon(Icons.fireplace),
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'News',
|
||||||
|
icon: Icon(Icons.newspaper_outlined),
|
||||||
|
selectedIcon: Icon(Icons.newspaper),
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'Led',
|
||||||
|
icon: Icon(Icons.fluorescent_outlined),
|
||||||
|
selectedIcon: Icon(Icons.fluorescent),
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'Matrix',
|
||||||
|
icon: Icon(Icons.grid_off),
|
||||||
|
selectedIcon: Icon(Icons.grid_on),
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'System',
|
||||||
|
icon: Icon(Icons.insert_chart_outlined_outlined),
|
||||||
|
selectedIcon: Icon(Icons.insert_chart),
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'Settings',
|
||||||
|
icon: Icon(Icons.settings_outlined),
|
||||||
|
selectedIcon: Icon(Icons.settings),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSelectedIndexChange: (int idx) {
|
||||||
|
switch (RoutesHome.values[idx]) {
|
||||||
|
case RoutesHome.home:
|
||||||
|
context.goNamed(RoutesHome.home.name);
|
||||||
|
case RoutesHome.materialDemo:
|
||||||
|
context.goNamed(RoutesHome.materialDemo.name);
|
||||||
|
case RoutesHome.news:
|
||||||
|
context.goNamed(RoutesHome.news.name);
|
||||||
|
case RoutesHome.benchmark:
|
||||||
|
context.goNamed(RoutesHome.benchmark.name);
|
||||||
|
case RoutesHome.led:
|
||||||
|
context.goNamed(RoutesHome.led.name);
|
||||||
|
case RoutesHome.matrix:
|
||||||
|
context.goNamed(RoutesHome.matrix.name);
|
||||||
|
case RoutesHome.system:
|
||||||
|
context.goNamed(RoutesHome.system.name);
|
||||||
|
case RoutesHome.settings:
|
||||||
|
context.goNamed(RoutesHome.settings.name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
67
lib/common/widgets/menu_drawer.dart
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../routing/routes.dart';
|
||||||
|
|
||||||
|
class MenuDrawer extends StatelessWidget {
|
||||||
|
const MenuDrawer({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Drawer(
|
||||||
|
child: ListView(
|
||||||
|
children: <Widget>[
|
||||||
|
const DrawerHeader(
|
||||||
|
child: Column(
|
||||||
|
children: [],
|
||||||
|
)),
|
||||||
|
ListTile(
|
||||||
|
selected: GoRouterState.of(context).uri.toString() ==
|
||||||
|
GoRouter.of(context).namedLocation(RoutesHome.home.name),
|
||||||
|
title: const Text('Home'),
|
||||||
|
leading: const Icon(Icons.home),
|
||||||
|
onTap: () {
|
||||||
|
context.replaceNamed(RoutesHome.home.name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
selected: GoRouterState.of(context).uri.toString() ==
|
||||||
|
GoRouter.of(context).namedLocation(RoutesHome.news.name),
|
||||||
|
title: const Text('News'),
|
||||||
|
leading: const Icon(Icons.newspaper),
|
||||||
|
onTap: () {
|
||||||
|
context.replaceNamed(RoutesHome.news.name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
selected: GoRouterState.of(context).uri.toString() ==
|
||||||
|
GoRouter.of(context).namedLocation(RoutesHome.news.name),
|
||||||
|
title: const Text('Led'),
|
||||||
|
leading: const Icon(Icons.newspaper),
|
||||||
|
onTap: () {
|
||||||
|
context.replaceNamed(RoutesHome.led.name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
selected: GoRouterState.of(context).uri.toString() ==
|
||||||
|
GoRouter.of(context).namedLocation(RoutesHome.news.name),
|
||||||
|
title: const Text('Matrix'),
|
||||||
|
leading: const Icon(Icons.newspaper),
|
||||||
|
onTap: () {
|
||||||
|
context.replaceNamed(RoutesHome.matrix.name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
selected: GoRouterState.of(context).uri.toString() ==
|
||||||
|
GoRouter.of(context).namedLocation(RoutesHome.settings.name),
|
||||||
|
title: const Text('Settings'),
|
||||||
|
leading: const Icon(Icons.settings),
|
||||||
|
onTap: () {
|
||||||
|
context.replaceNamed(RoutesHome.settings.name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
34
lib/common/widgets/scaffold_with_statusbar.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'statusbar.dart';
|
||||||
|
|
||||||
|
class ScaffoldWithStatusbar extends StatelessWidget {
|
||||||
|
const ScaffoldWithStatusbar({
|
||||||
|
this.appBar,
|
||||||
|
this.body,
|
||||||
|
this.floatingActionButton,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final AppBar? appBar;
|
||||||
|
final Widget? body;
|
||||||
|
final FloatingActionButton? floatingActionButton;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
const StatusBar(),
|
||||||
|
Expanded(
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: appBar,
|
||||||
|
body: body,
|
||||||
|
floatingActionButton: floatingActionButton,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
113
lib/common/widgets/statusbar.dart
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:nm/nm.dart';
|
||||||
|
|
||||||
|
import '../../features/settings/business/wifi_controller.dart';
|
||||||
|
import '../../features/settings/presentation/wifi_settings_screen/widgets/wifi_bar_icon.dart';
|
||||||
|
import 'clock.dart';
|
||||||
|
|
||||||
|
const horizontalSpace = SizedBox(
|
||||||
|
width: 8,
|
||||||
|
);
|
||||||
|
|
||||||
|
class StatusBar extends ConsumerWidget {
|
||||||
|
const StatusBar({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final wifiStream = (Platform.isLinux)
|
||||||
|
? ref.watch(wifiControllerProvider.notifier).wifiStream()
|
||||||
|
: const Stream.empty();
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 24,
|
||||||
|
color: Colors.black,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Row(
|
||||||
|
children: [
|
||||||
|
horizontalSpace,
|
||||||
|
kDebugMode
|
||||||
|
? Text(
|
||||||
|
'DEBUG MODE',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
)
|
||||||
|
: SizedBox(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
StreamBuilder(
|
||||||
|
stream: wifiStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!Platform.isLinux) {
|
||||||
|
return const Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'DemoConnection',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
horizontalSpace,
|
||||||
|
WifiBarIcon(
|
||||||
|
strength: 1,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
final data = snapshot.data?.$1 as NetworkManagerDevice;
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
data.activeConnection?.id ?? 'No connection',
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
horizontalSpace,
|
||||||
|
WifiBarIcon(
|
||||||
|
strength:
|
||||||
|
data.wireless?.activeAccessPoint?.strength ?? 0,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
// error can be caught here
|
||||||
|
// display error in statusbar:
|
||||||
|
// return Text(
|
||||||
|
// snapshot.error.toString(),
|
||||||
|
// style: const TextStyle(color: Colors.white),
|
||||||
|
// );
|
||||||
|
// show nothing (hide error message from user):
|
||||||
|
// the error that can happen here is mostly related
|
||||||
|
// to not finding any usable wifi device
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
return const Text(
|
||||||
|
"No connection",
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
horizontalSpace,
|
||||||
|
const ClockWidget(
|
||||||
|
textStyle: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
horizontalSpace,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
97
lib/common/widgets/text_input_dialog.dart
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:virtual_keyboard_multi_language/virtual_keyboard_multi_language.dart';
|
||||||
|
|
||||||
|
import '../settings/custom_layout.dart';
|
||||||
|
|
||||||
|
class TextInputDialog extends ConsumerWidget {
|
||||||
|
const TextInputDialog({
|
||||||
|
required this.controller,
|
||||||
|
this.title = 'Text Input',
|
||||||
|
this.onReturnPress,
|
||||||
|
this.hintText,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TextEditingController controller;
|
||||||
|
final String title;
|
||||||
|
final Function? onReturnPress;
|
||||||
|
final String? hintText;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return TextField(
|
||||||
|
controller: controller,
|
||||||
|
decoration: InputDecoration(hintText: hintText),
|
||||||
|
onTap: () {
|
||||||
|
// always move the cursor to the end position by default
|
||||||
|
controller.value = TextEditingValue(
|
||||||
|
text: controller.text,
|
||||||
|
selection: TextSelection.collapsed(
|
||||||
|
offset: controller.text.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => Dialog.fullscreen(
|
||||||
|
// backgroundColor: Colors.transparent,
|
||||||
|
child: ClipRect(
|
||||||
|
// TODO: Blur background causes lag on the raspberry pi
|
||||||
|
// BackdropFilter() with:
|
||||||
|
// filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.background.withOpacity(0.5),
|
||||||
|
appBar: AppBar(
|
||||||
|
// backgroundColor: Colors.transparent,
|
||||||
|
title: Text(title),
|
||||||
|
centerTitle: false,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
context.pop();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 400),
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
autofocus: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: TextFieldTapRegion(
|
||||||
|
child: VirtualKeyboard(
|
||||||
|
textController: controller,
|
||||||
|
textColor:
|
||||||
|
Theme.of(context).textTheme.headlineLarge!.color!,
|
||||||
|
type: VirtualKeyboardType.Alphanumeric,
|
||||||
|
// onKeyPress: (key) => onKeyPress(key, context),
|
||||||
|
customLayoutKeys: CustomLayoutKeys(),
|
||||||
|
onReturnKey: onReturnPress,
|
||||||
|
unfocus: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
8
lib/constants/api_urls.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class TagesschauApiUrls {
|
||||||
|
static const main = 'https://www.tagesschau.de/api2/';
|
||||||
|
static const news = '$main/news/';
|
||||||
|
static const placeholderImage =
|
||||||
|
'https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Placeholder_view_vector.svg/1362px-Placeholder_view_vector.svg.png';
|
||||||
|
static const logoImage =
|
||||||
|
'https://images.tagesschau.de/image/89045d82-5cd5-46ad-8f91-73911add30ee/AAABh3YLLz0/AAABjcWen7M/16x9-1280/tagesschau-logo-100.jpg';
|
||||||
|
}
|
17
lib/constants/colors.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
enum ColorSeed {
|
||||||
|
baseColor('M3 Baseline', Color(0xff6750a4)),
|
||||||
|
indigo('Indigo', Colors.indigo),
|
||||||
|
blue('Blue', Colors.blue),
|
||||||
|
teal('Teal', Colors.teal),
|
||||||
|
green('Green', Colors.green),
|
||||||
|
yellow('Yellow', Colors.yellow),
|
||||||
|
orange('Orange', Colors.orange),
|
||||||
|
deepOrange('Deep Orange', Colors.deepOrange),
|
||||||
|
pink('Pink', Colors.pink);
|
||||||
|
|
||||||
|
const ColorSeed(this.label, this.color);
|
||||||
|
final String label;
|
||||||
|
final Color color;
|
||||||
|
}
|
3
lib/features/benchmark/business/flame/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Flame Business Layer
|
||||||
|
|
||||||
|
Custom Layer for the benchmarks that use the Flame engine.
|
21
lib/features/benchmark/business/flame/rogue_shooter/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Blue Fire
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,10 @@
|
|||||||
|
# Flame Performance Test Game
|
||||||
|
|
||||||
|
This is a simple scrolling shooter game which we use for testing the performance of Flame,
|
||||||
|
since it uses a lot of components and hitboxes. When it reaches a certain amount of
|
||||||
|
components (counted in the lower right corner) you can expect it to drop a bit in FPS,
|
||||||
|
depending on what platform you are on.
|
||||||
|
|
||||||
|
# Source
|
||||||
|
|
||||||
|
https://github.com/flame-engine/flame/tree/main/examples/games/rogue_shooter
|
@ -0,0 +1,55 @@
|
|||||||
|
import 'package:flame/collisions.dart';
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
|
||||||
|
import 'enemy_component.dart';
|
||||||
|
|
||||||
|
class BulletComponent extends SpriteAnimationComponent
|
||||||
|
with HasGameRef, CollisionCallbacks {
|
||||||
|
static const speed = 500.0;
|
||||||
|
late final Vector2 velocity;
|
||||||
|
final Vector2 deltaPosition = Vector2.zero();
|
||||||
|
|
||||||
|
BulletComponent({required super.position, super.angle})
|
||||||
|
: super(size: Vector2(10, 20), anchor: Anchor.center);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
add(CircleHitbox());
|
||||||
|
animation = await game.loadSpriteAnimation(
|
||||||
|
'flame_benchmark/rogue_shooter/bullet.png',
|
||||||
|
SpriteAnimationData.sequenced(
|
||||||
|
stepTime: 0.2,
|
||||||
|
amount: 4,
|
||||||
|
textureSize: Vector2(8, 16),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
velocity = Vector2(0, -1)
|
||||||
|
..rotate(angle)
|
||||||
|
..scale(speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCollisionStart(
|
||||||
|
Set<Vector2> intersectionPoints,
|
||||||
|
PositionComponent other,
|
||||||
|
) {
|
||||||
|
super.onCollisionStart(intersectionPoints, other);
|
||||||
|
if (other is EnemyComponent) {
|
||||||
|
other.takeHit();
|
||||||
|
removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
deltaPosition
|
||||||
|
..setFrom(velocity)
|
||||||
|
..scale(dt);
|
||||||
|
position += deltaPosition;
|
||||||
|
|
||||||
|
if (position.y < 0 || position.x > game.size.x || position.x + size.x < 0) {
|
||||||
|
removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
import 'package:flame/collisions.dart';
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
|
||||||
|
import '../rogue_shooter_game.dart';
|
||||||
|
import 'explosion_component.dart';
|
||||||
|
|
||||||
|
class EnemyComponent extends SpriteAnimationComponent
|
||||||
|
with HasGameReference<RogueShooterGame>, CollisionCallbacks {
|
||||||
|
static const speed = 150;
|
||||||
|
static final Vector2 initialSize = Vector2.all(25);
|
||||||
|
|
||||||
|
EnemyComponent({required super.position})
|
||||||
|
: super(size: initialSize, anchor: Anchor.center);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
animation = await game.loadSpriteAnimation(
|
||||||
|
'flame_benchmark/rogue_shooter/enemy.png',
|
||||||
|
SpriteAnimationData.sequenced(
|
||||||
|
stepTime: 0.2,
|
||||||
|
amount: 4,
|
||||||
|
textureSize: Vector2.all(16),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
add(CircleHitbox(collisionType: CollisionType.passive));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
y += speed * dt;
|
||||||
|
if (y >= game.size.y) {
|
||||||
|
removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void takeHit() {
|
||||||
|
removeFromParent();
|
||||||
|
|
||||||
|
game.add(ExplosionComponent(position: position));
|
||||||
|
game.increaseScore();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
|
||||||
|
import 'enemy_component.dart';
|
||||||
|
|
||||||
|
class EnemyCreator extends TimerComponent with HasGameRef {
|
||||||
|
final Random random = Random();
|
||||||
|
final _halfWidth = EnemyComponent.initialSize.x / 2;
|
||||||
|
|
||||||
|
EnemyCreator() : super(period: 0.05, repeat: true);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTick() {
|
||||||
|
game.addAll(
|
||||||
|
List.generate(
|
||||||
|
5,
|
||||||
|
(index) => EnemyComponent(
|
||||||
|
position: Vector2(
|
||||||
|
_halfWidth + (game.size.x - _halfWidth) * random.nextDouble(),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
|
||||||
|
class ExplosionComponent extends SpriteAnimationComponent with HasGameRef {
|
||||||
|
ExplosionComponent({super.position})
|
||||||
|
: super(
|
||||||
|
size: Vector2.all(50),
|
||||||
|
anchor: Anchor.center,
|
||||||
|
removeOnFinish: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
animation = await game.loadSpriteAnimation(
|
||||||
|
'flame_benchmark/rogue_shooter/explosion.png',
|
||||||
|
SpriteAnimationData.sequenced(
|
||||||
|
stepTime: 0.1,
|
||||||
|
amount: 6,
|
||||||
|
textureSize: Vector2.all(32),
|
||||||
|
loop: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
import 'package:flame/collisions.dart';
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
|
||||||
|
import 'bullet_component.dart';
|
||||||
|
import 'enemy_component.dart';
|
||||||
|
import 'explosion_component.dart';
|
||||||
|
|
||||||
|
class PlayerComponent extends SpriteAnimationComponent
|
||||||
|
with HasGameRef, CollisionCallbacks {
|
||||||
|
late TimerComponent bulletCreator;
|
||||||
|
|
||||||
|
PlayerComponent() : super(size: Vector2(50, 75), anchor: Anchor.center);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
position = game.size / 2;
|
||||||
|
add(CircleHitbox());
|
||||||
|
add(
|
||||||
|
bulletCreator = TimerComponent(
|
||||||
|
period: 0.05,
|
||||||
|
repeat: true,
|
||||||
|
autoStart: false,
|
||||||
|
onTick: _createBullet,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
animation = await game.loadSpriteAnimation(
|
||||||
|
'flame_benchmark/rogue_shooter/player.png',
|
||||||
|
SpriteAnimationData.sequenced(
|
||||||
|
stepTime: 0.2,
|
||||||
|
amount: 4,
|
||||||
|
textureSize: Vector2(32, 39),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final _bulletAngles = [0.5, 0.3, 0.0, -0.5, -0.3];
|
||||||
|
void _createBullet() {
|
||||||
|
game.addAll(
|
||||||
|
_bulletAngles.map(
|
||||||
|
(angle) => BulletComponent(
|
||||||
|
position: position + Vector2(0, -size.y / 2),
|
||||||
|
angle: angle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void beginFire() {
|
||||||
|
bulletCreator.timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopFire() {
|
||||||
|
bulletCreator.timer.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
void takeHit() {
|
||||||
|
game.add(ExplosionComponent(position: position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCollisionStart(
|
||||||
|
Set<Vector2> intersectionPoints,
|
||||||
|
PositionComponent other,
|
||||||
|
) {
|
||||||
|
super.onCollisionStart(intersectionPoints, other);
|
||||||
|
if (other is EnemyComponent) {
|
||||||
|
other.takeHit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/sprite.dart';
|
||||||
|
|
||||||
|
import 'star_component.dart';
|
||||||
|
|
||||||
|
class StarBackGroundCreator extends Component with HasGameRef {
|
||||||
|
final gapSize = 12;
|
||||||
|
|
||||||
|
late final SpriteSheet spriteSheet;
|
||||||
|
Random random = Random();
|
||||||
|
|
||||||
|
StarBackGroundCreator();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
spriteSheet = SpriteSheet.fromColumnsAndRows(
|
||||||
|
image: await game.images.load('flame_benchmark/rogue_shooter/stars.png'),
|
||||||
|
rows: 4,
|
||||||
|
columns: 4,
|
||||||
|
);
|
||||||
|
|
||||||
|
final starGapTime = (game.size.y / gapSize) / StarComponent.speed;
|
||||||
|
|
||||||
|
add(
|
||||||
|
TimerComponent(
|
||||||
|
period: starGapTime,
|
||||||
|
repeat: true,
|
||||||
|
onTick: () => _createRowOfStars(0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_createInitialStars();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _createStarAt(double x, double y) {
|
||||||
|
final animation = spriteSheet.createAnimation(
|
||||||
|
row: random.nextInt(3),
|
||||||
|
to: 4,
|
||||||
|
stepTime: 0.1,
|
||||||
|
)..variableStepTimes = [max(20, 100 * random.nextDouble()), 0.1, 0.1, 0.1];
|
||||||
|
|
||||||
|
game.add(StarComponent(animation: animation, position: Vector2(x, y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _createRowOfStars(double y) {
|
||||||
|
const gapSize = 6;
|
||||||
|
final starGap = game.size.x / gapSize;
|
||||||
|
|
||||||
|
for (var i = 0; i < gapSize; i++) {
|
||||||
|
_createStarAt(
|
||||||
|
starGap * i + (random.nextDouble() * starGap),
|
||||||
|
y + (random.nextDouble() * 20),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _createInitialStars() {
|
||||||
|
final rows = game.size.y / gapSize;
|
||||||
|
|
||||||
|
for (var i = 0; i < gapSize; i++) {
|
||||||
|
_createRowOfStars(i * rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
|
||||||
|
class StarComponent extends SpriteAnimationComponent with HasGameRef {
|
||||||
|
static const speed = 10;
|
||||||
|
|
||||||
|
StarComponent({super.animation, super.position})
|
||||||
|
: super(size: Vector2.all(20));
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
y += dt * speed;
|
||||||
|
if (y >= game.size.y) {
|
||||||
|
removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
// import 'package:flame/game.dart';
|
||||||
|
// import 'package:flutter/widgets.dart';
|
||||||
|
// import 'package:rogue_shooter/rogue_shooter_game.dart';
|
||||||
|
|
||||||
|
// void main() {
|
||||||
|
// runApp(GameWidget(game: RogueShooterGame()));
|
||||||
|
// }
|
@ -0,0 +1,36 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
// Source: https://github.com/flame-engine/flame/blob/main/packages/flame/lib/src/game/mixins/has_performance_tracker.dart#L6
|
||||||
|
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
|
||||||
|
/// A mixin that adds performance tracking to a game.
|
||||||
|
mixin HasPerformanceTracker on Game {
|
||||||
|
int _updateTime = 0;
|
||||||
|
int _renderTime = 0;
|
||||||
|
final _stopwatch = Stopwatch();
|
||||||
|
|
||||||
|
/// The time it took to update the game in milliseconds.
|
||||||
|
int get updateTime => _updateTime;
|
||||||
|
|
||||||
|
/// The time it took to render the game in milliseconds.
|
||||||
|
int get renderTime => _renderTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
_stopwatch.reset();
|
||||||
|
_stopwatch.start();
|
||||||
|
super.update(dt);
|
||||||
|
_stopwatch.stop();
|
||||||
|
_updateTime = _stopwatch.elapsedMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
_stopwatch.reset();
|
||||||
|
_stopwatch.start();
|
||||||
|
super.render(canvas);
|
||||||
|
_stopwatch.stop();
|
||||||
|
_renderTime = _stopwatch.elapsedMilliseconds;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/events.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
|
||||||
|
import 'components/enemy_creator.dart';
|
||||||
|
import 'components/player_component.dart';
|
||||||
|
import 'components/star_background_creator.dart';
|
||||||
|
import 'mixins/has_performance_tracker.dart';
|
||||||
|
|
||||||
|
class RogueShooterGame extends FlameGame
|
||||||
|
with PanDetector, HasCollisionDetection, HasPerformanceTracker {
|
||||||
|
static const String description = '''
|
||||||
|
A simple space shooter game used for testing performance of the collision
|
||||||
|
detection system in Flame.
|
||||||
|
''';
|
||||||
|
|
||||||
|
late final PlayerComponent _player;
|
||||||
|
late final TextComponent _componentCounter;
|
||||||
|
late final TextComponent _scoreText;
|
||||||
|
|
||||||
|
final _updateTime = TextComponent(
|
||||||
|
text: 'Update time: 0ms',
|
||||||
|
position: Vector2(90, 20),
|
||||||
|
priority: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
final TextComponent _renderTime = TextComponent(
|
||||||
|
text: 'Render time: 0ms',
|
||||||
|
position: Vector2(90, 45),
|
||||||
|
priority: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
int _score = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
add(_player = PlayerComponent());
|
||||||
|
addAll([
|
||||||
|
FpsTextComponent(
|
||||||
|
position: size - Vector2(0, 50),
|
||||||
|
anchor: Anchor.bottomRight,
|
||||||
|
),
|
||||||
|
_scoreText = TextComponent(
|
||||||
|
position: size - Vector2(0, 25),
|
||||||
|
anchor: Anchor.bottomRight,
|
||||||
|
priority: 1,
|
||||||
|
),
|
||||||
|
_componentCounter = TextComponent(
|
||||||
|
position: size,
|
||||||
|
anchor: Anchor.bottomRight,
|
||||||
|
priority: 1,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
add(EnemyCreator());
|
||||||
|
add(StarBackGroundCreator());
|
||||||
|
|
||||||
|
addAll([_updateTime, _renderTime]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
_scoreText.text = 'Score: $_score';
|
||||||
|
_componentCounter.text = 'Components: ${children.length}';
|
||||||
|
_updateTime.text = 'Update time: $updateTime ms';
|
||||||
|
_renderTime.text = 'Render time: $renderTime ms';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onPanStart(_) {
|
||||||
|
_player.beginFire();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onPanEnd(_) {
|
||||||
|
_player.stopFire();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onPanCancel() {
|
||||||
|
_player.stopFire();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onPanUpdate(DragUpdateInfo info) {
|
||||||
|
_player.position += info.delta.global;
|
||||||
|
}
|
||||||
|
|
||||||
|
void increaseScore() {
|
||||||
|
_score++;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'rogue_shooter_game.dart';
|
||||||
|
|
||||||
|
class RogueShooterWidget extends StatelessWidget {
|
||||||
|
const RogueShooterWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GameWidget(
|
||||||
|
game: RogueShooterGame(),
|
||||||
|
loadingBuilder: (_) => const Center(
|
||||||
|
child: Text('Loading'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Blue Fire
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,12 @@
|
|||||||
|
# Flame Engine Sprite Renderer Benchmark
|
||||||
|
|
||||||
|
This is a simple benchmark to test the render
|
||||||
|
performance of the Flutter Render Engine.
|
||||||
|
|
||||||
|
It was modified from: https://github.com/flame-engine/flame/blob/main/examples/lib/stories/animations/benchmark_example.dart
|
||||||
|
|
||||||
|
## Modifications
|
||||||
|
|
||||||
|
- added framerate display
|
||||||
|
- add sprites automatically for 30 minutes and only if the framerate is above 59
|
||||||
|
- timer and benchmark test starts automatically and displays the result after 30 minutes
|
@ -0,0 +1,14 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/events.dart';
|
||||||
|
|
||||||
|
import 'sprite_benchmark.dart';
|
||||||
|
|
||||||
|
class BenchmarkWorld extends World
|
||||||
|
with TapCallbacks, HasGameReference<SpriteBenchmark> {
|
||||||
|
final Random random = Random();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTapDown(TapDownEvent event) {}
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame/text.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../data/models/ember.dart';
|
||||||
|
import 'benchmark_world.dart';
|
||||||
|
|
||||||
|
class SpriteBenchmark extends FlameGame with HasGameRef {
|
||||||
|
static const description = '''
|
||||||
|
See how many SpriteAnimationComponent's your platform can handle before it
|
||||||
|
starts to drop in FPS, this is without any sprite batching and such.
|
||||||
|
''';
|
||||||
|
|
||||||
|
SpriteBenchmark() : super(world: BenchmarkWorld());
|
||||||
|
|
||||||
|
// 30 minutes countdown
|
||||||
|
final countdown = Timer(1800);
|
||||||
|
|
||||||
|
final emberSize = Vector2.all(20);
|
||||||
|
late final TextComponent emberCounter;
|
||||||
|
final counterPrefix = 'Animations: ';
|
||||||
|
double posX = 0;
|
||||||
|
double posY = 0;
|
||||||
|
|
||||||
|
final Queue<double> window = Queue();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color backgroundColor() => Colors.black;
|
||||||
|
|
||||||
|
double _sum = 0;
|
||||||
|
int spriteCount = 0;
|
||||||
|
|
||||||
|
final int windowSize = 60;
|
||||||
|
|
||||||
|
TextPaint textPaint = TextPaint(
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.0,
|
||||||
|
fontFamily: 'Awesome Font',
|
||||||
|
background: Paint()
|
||||||
|
..color = Colors.indigo
|
||||||
|
..strokeJoin = StrokeJoin.round
|
||||||
|
..strokeCap = StrokeCap.round
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 25.0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await camera.viewport.addAll([
|
||||||
|
FpsTextComponent(
|
||||||
|
position: size - Vector2(20, 80),
|
||||||
|
anchor: Anchor.bottomRight,
|
||||||
|
textRenderer: textPaint,
|
||||||
|
),
|
||||||
|
emberCounter = TextComponent(
|
||||||
|
position: size - Vector2(20, 25),
|
||||||
|
anchor: Anchor.bottomRight,
|
||||||
|
priority: 1,
|
||||||
|
textRenderer: textPaint,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
// world.add(Ember(size: emberSize));
|
||||||
|
children.register<Ember>();
|
||||||
|
}
|
||||||
|
|
||||||
|
double get fps {
|
||||||
|
return window.isEmpty ? 0 : window.length / _sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) async {
|
||||||
|
// add text to the screen only once
|
||||||
|
if (countdown.finished) {
|
||||||
|
showDialog(
|
||||||
|
context: gameRef.buildContext!,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Benchmark finished'),
|
||||||
|
content: Text(
|
||||||
|
'After 30 minutes, the benchmark finished with $spriteCount sprite animations.',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Close'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
countdown.reset();
|
||||||
|
}
|
||||||
|
if (!countdown.finished && countdown.isRunning()) {
|
||||||
|
countdown.update(dt);
|
||||||
|
|
||||||
|
window.addLast(dt);
|
||||||
|
_sum += dt;
|
||||||
|
if (window.length > windowSize) {
|
||||||
|
_sum -= window.removeFirst();
|
||||||
|
}
|
||||||
|
if (fps > 59) {
|
||||||
|
if (posY > size.y) {
|
||||||
|
final Random random = Random();
|
||||||
|
add(
|
||||||
|
Ember(
|
||||||
|
size: emberSize,
|
||||||
|
position: Vector2(
|
||||||
|
(size.x) * random.nextDouble() * (random.nextBool() ? 1 : -1),
|
||||||
|
(size.y) * random.nextDouble() * (random.nextBool() ? 1 : -1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
sleep(const Duration(milliseconds: 50));
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < 1; i++) {
|
||||||
|
add(Ember(
|
||||||
|
size: emberSize,
|
||||||
|
position: Vector2(10 + posX, 10 + posY),
|
||||||
|
));
|
||||||
|
if (posX < size.x) {
|
||||||
|
posX += 20;
|
||||||
|
} else {
|
||||||
|
posY += 20;
|
||||||
|
posX = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sleep(const Duration(milliseconds: 50));
|
||||||
|
}
|
||||||
|
spriteCount += 1;
|
||||||
|
}
|
||||||
|
super.update(dt);
|
||||||
|
emberCounter.text = '$counterPrefix $spriteCount';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
lib/features/benchmark/data/benchmarks_list.dart
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import '../../../common/routing/routes.dart';
|
||||||
|
import 'models/benchmark.dart';
|
||||||
|
|
||||||
|
final List<Benchmark> benchmarksList = [
|
||||||
|
Benchmark(
|
||||||
|
title: 'Flame Sprite Rendering',
|
||||||
|
desc: 'Tests how many sprites can be renderer without a frame drop.',
|
||||||
|
route: RoutesBenchmark.sprites,
|
||||||
|
),
|
||||||
|
Benchmark(
|
||||||
|
title: 'Rogue Shooter Benchmark',
|
||||||
|
desc:
|
||||||
|
'A simple scrolling shooter game which is used by the Flame Engines team for testing the performance of Flame.',
|
||||||
|
route: RoutesBenchmark.rogueShooter,
|
||||||
|
),
|
||||||
|
Benchmark(
|
||||||
|
title: 'VSync Test',
|
||||||
|
desc: 'Tests if the VSync is working properly with a fragment shader.',
|
||||||
|
route: RoutesBenchmark.shader,
|
||||||
|
),
|
||||||
|
Benchmark(
|
||||||
|
title: 'Video Benchmark',
|
||||||
|
desc: 'Video Player demo.',
|
||||||
|
route: RoutesBenchmark.video,
|
||||||
|
),
|
||||||
|
Benchmark(
|
||||||
|
title: 'Simple Tiles Map Benchmark',
|
||||||
|
desc: 'Map demo.',
|
||||||
|
route: RoutesBenchmark.map,
|
||||||
|
),
|
||||||
|
];
|
15
lib/features/benchmark/data/models/benchmark.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import '../../../../common/routing/routes.dart';
|
||||||
|
|
||||||
|
class Benchmark {
|
||||||
|
final String title;
|
||||||
|
final String desc;
|
||||||
|
final String buttonText;
|
||||||
|
final RoutesBenchmark route;
|
||||||
|
|
||||||
|
Benchmark({
|
||||||
|
required this.title,
|
||||||
|
required this.desc,
|
||||||
|
this.buttonText = 'Go to benchmark',
|
||||||
|
required this.route,
|
||||||
|
});
|
||||||
|
}
|
25
lib/features/benchmark/data/models/ember.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
class Ember<T extends FlameGame> extends SpriteAnimationComponent
|
||||||
|
with HasGameReference<T> {
|
||||||
|
Ember({super.position, Vector2? size, super.priority, super.key})
|
||||||
|
: super(
|
||||||
|
size: size ?? Vector2.all(50),
|
||||||
|
anchor: Anchor.center,
|
||||||
|
);
|
||||||
|
|
||||||
|
@mustCallSuper
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
animation = await game.loadSpriteAnimation(
|
||||||
|
'flame_benchmark/sprite_benchmark/ember.png',
|
||||||
|
SpriteAnimationData.sequenced(
|
||||||
|
amount: 3,
|
||||||
|
textureSize: Vector2.all(16),
|
||||||
|
stepTime: 0.15,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
27
lib/features/benchmark/presentation/benchmark_screen.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import '../data/benchmarks_list.dart';
|
||||||
|
import 'widgets/benchmark_card.dart';
|
||||||
|
|
||||||
|
class BenchmarkScreen extends StatelessWidget {
|
||||||
|
const BenchmarkScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GridView.builder(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: MediaQuery.of(context).size.shortestSide < 600 ? 2 : 3,
|
||||||
|
crossAxisSpacing: 12.0,
|
||||||
|
mainAxisSpacing: 12,
|
||||||
|
),
|
||||||
|
itemCount: benchmarksList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return BenchmarkCard(
|
||||||
|
benchmark: benchmarksList[index],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_elinux/common/widgets/floating_menu_button.dart';
|
||||||
|
import 'package:flutter_elinux/common/widgets/scaffold_with_statusbar.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:simple_tiles_map/simple_tiles_map.dart';
|
||||||
|
|
||||||
|
class MapBenchmarkScreen extends StatefulWidget {
|
||||||
|
const MapBenchmarkScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MapBenchmarkScreen> createState() => _MapBenchmarkScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MapBenchmarkScreenState extends State<MapBenchmarkScreen> {
|
||||||
|
TypeMap _typeMap = TypeMap.osm;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
MapOptions mapOptions = MapOptions(
|
||||||
|
maxZoom: 19,
|
||||||
|
minZoom: 5,
|
||||||
|
);
|
||||||
|
return ScaffoldWithStatusbar(
|
||||||
|
body: Stack(children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
SimpleTilesMap(
|
||||||
|
typeMap: _typeMap,
|
||||||
|
mapOptions: mapOptions,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
FloatingMenuButton(
|
||||||
|
popupMenuButton: PopupMenuButton(
|
||||||
|
itemBuilder: (BuildContext context) {
|
||||||
|
return [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: TypeMap.google,
|
||||||
|
enabled: (_typeMap != TypeMap.google),
|
||||||
|
child: const Text('Google'),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: TypeMap.googleHybrid,
|
||||||
|
enabled: (_typeMap != TypeMap.googleHybrid),
|
||||||
|
child: const Text('Google Hybrid'),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: TypeMap.googleSatellite,
|
||||||
|
enabled: (_typeMap != TypeMap.googleSatellite),
|
||||||
|
child: const Text('Google Satellite'),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: TypeMap.osm,
|
||||||
|
enabled: (_typeMap != TypeMap.osm),
|
||||||
|
child: const Text('Open Street Map'),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: TypeMap.cartoMapPositron,
|
||||||
|
enabled: (_typeMap != TypeMap.cartoMapPositron),
|
||||||
|
child: const Text('Carto Map Positron'),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: TypeMap.cartoMapDark,
|
||||||
|
enabled: (_typeMap != TypeMap.cartoMapDark),
|
||||||
|
child: const Text('Carto Map Dark'),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: TypeMap.esriSatellite,
|
||||||
|
enabled: (_typeMap != TypeMap.esriSatellite),
|
||||||
|
child: const Text('Esri Satellite'),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: TypeMap.esriStreets,
|
||||||
|
enabled: (_typeMap != TypeMap.esriStreets),
|
||||||
|
child: const Text('Esri Streets'),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: TypeMap.esriTopo,
|
||||||
|
enabled: (_typeMap != TypeMap.esriTopo),
|
||||||
|
child: const Text('Esri Topo'),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
onSelected: (dynamic value) {
|
||||||
|
setState(() {
|
||||||
|
_typeMap = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../common/widgets/floating_menu_button.dart';
|
||||||
|
import '../business/flame/rogue_shooter/rogue_shooter_game.dart';
|
||||||
|
|
||||||
|
class RogueShooterScreen extends StatefulWidget {
|
||||||
|
const RogueShooterScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RogueShooterScreen> createState() => _RogueShooterScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RogueShooterScreenState extends State<RogueShooterScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Stack(children: [
|
||||||
|
GameWidget(game: RogueShooterGame()),
|
||||||
|
const FloatingMenuButton(),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../common/widgets/floating_menu_button.dart';
|
||||||
|
import '../business/flame/sprite_renderer/sprite_benchmark.dart';
|
||||||
|
|
||||||
|
class SpriteBenchmarkScreen extends StatelessWidget {
|
||||||
|
const SpriteBenchmarkScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Stack(children: [
|
||||||
|
GameWidget(
|
||||||
|
game: SpriteBenchmark(),
|
||||||
|
),
|
||||||
|
const FloatingMenuButton(),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
111
lib/features/benchmark/presentation/video_benchmark_screen.dart
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
|
class VideoBenchmarkScreen extends StatefulWidget {
|
||||||
|
const VideoBenchmarkScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<VideoBenchmarkScreen> createState() => _VideoBenchmarkScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VideoBenchmarkScreenState extends State<VideoBenchmarkScreen> {
|
||||||
|
late VideoPlayerController _controller;
|
||||||
|
String selectedMode = '720p';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = VideoPlayerController.asset(
|
||||||
|
'assets/video/video_720p.mp4',
|
||||||
|
)
|
||||||
|
..initialize().then((_) {
|
||||||
|
setState(() {});
|
||||||
|
})
|
||||||
|
..setLooping(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
_controller.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Video Player Demo ($selectedMode)'),
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
_controller = VideoPlayerController.asset(
|
||||||
|
'assets/video/video_720p.mp4',
|
||||||
|
)
|
||||||
|
..initialize().then((_) {
|
||||||
|
setState(() {
|
||||||
|
selectedMode = '720p';
|
||||||
|
});
|
||||||
|
})
|
||||||
|
..setLooping(true);
|
||||||
|
},
|
||||||
|
child: const Text('load 720p')),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
_controller = VideoPlayerController.asset(
|
||||||
|
'assets/video/video_1080p.mp4',
|
||||||
|
)
|
||||||
|
..initialize().then((_) {
|
||||||
|
setState(() {
|
||||||
|
selectedMode = '1080p';
|
||||||
|
});
|
||||||
|
})
|
||||||
|
..setLooping(true);
|
||||||
|
},
|
||||||
|
child: const Text('load 1080p')),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
_controller = VideoPlayerController.asset(
|
||||||
|
'assets/video/video_2160p.mp4',
|
||||||
|
)
|
||||||
|
..initialize().then((_) {
|
||||||
|
setState(() {
|
||||||
|
selectedMode = '2160p';
|
||||||
|
});
|
||||||
|
})
|
||||||
|
..setLooping(true);
|
||||||
|
},
|
||||||
|
child: const Text('load 2160p')),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: _controller.value.isInitialized
|
||||||
|
? AspectRatio(
|
||||||
|
aspectRatio: _controller.value.aspectRatio,
|
||||||
|
child: VideoPlayer(_controller),
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_controller.value.isPlaying
|
||||||
|
? _controller.pause()
|
||||||
|
: _controller.play();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
||||||
|
import '../../../common/widgets/floating_menu_button.dart';
|
||||||
|
|
||||||
|
class VsyncBenchmarkScreen extends StatelessWidget {
|
||||||
|
const VsyncBenchmarkScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
child: ShaderHomePage(),
|
||||||
|
),
|
||||||
|
FloatingMenuButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShaderHomePage extends StatefulWidget {
|
||||||
|
const ShaderHomePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ShaderHomePage> createState() => _ShaderHomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShaderHomePageState extends State<ShaderHomePage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
double delta = 0;
|
||||||
|
FragmentShader? shader;
|
||||||
|
|
||||||
|
late final Ticker _ticker;
|
||||||
|
|
||||||
|
void loadMyShader() async {
|
||||||
|
var program = await FragmentProgram.fromAsset('shaders/vsync.frag');
|
||||||
|
shader = program.fragmentShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadMyShader();
|
||||||
|
_ticker = createTicker((elapsed) {
|
||||||
|
setState(() {
|
||||||
|
delta += 1 / 60;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_ticker.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_ticker.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (shader == null) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
} else {
|
||||||
|
return Container(
|
||||||
|
color: Colors.white,
|
||||||
|
child: CustomPaint(painter: ShaderPainter(shader!, delta)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShaderPainter extends CustomPainter {
|
||||||
|
final FragmentShader shader;
|
||||||
|
final double time;
|
||||||
|
|
||||||
|
ShaderPainter(FragmentShader fragmentShader, this.time)
|
||||||
|
: shader = fragmentShader;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint = Paint();
|
||||||
|
shader.setFloat(0, size.width);
|
||||||
|
shader.setFloat(1, size.height);
|
||||||
|
shader.setFloat(2, time);
|
||||||
|
paint.shader = shader;
|
||||||
|
canvas.drawRect(Offset.zero & size, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../../data/models/benchmark.dart';
|
||||||
|
|
||||||
|
class BenchmarkCard extends StatelessWidget {
|
||||||
|
const BenchmarkCard({
|
||||||
|
required this.benchmark,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Benchmark benchmark;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
benchmark.title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
child: Text(
|
||||||
|
benchmark.desc,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pushNamed(benchmark.route.name);
|
||||||
|
},
|
||||||
|
child: Text(benchmark.buttonText),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
34
lib/features/home/presentation/home_screen.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class HomeScreen extends StatefulWidget {
|
||||||
|
const HomeScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HomeScreen> createState() => _HomeScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Flutter on Embedded Linux',
|
||||||
|
style: Theme.of(context).textTheme.headlineLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,365 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'widgets/component_decoration.dart';
|
||||||
|
|
||||||
|
// https://github.com/flutter/samples/blob/main/material_3_demo/lib/component_screen.dart
|
||||||
|
|
||||||
|
const rowDivider = SizedBox(width: 20);
|
||||||
|
const colDivider = SizedBox(height: 10);
|
||||||
|
|
||||||
|
const tinySpacing = 3.0;
|
||||||
|
const smallSpacing = 10.0;
|
||||||
|
const double cardWidth = 115;
|
||||||
|
const double widthConstraint = 450;
|
||||||
|
|
||||||
|
class MaterialDemoScreen extends StatefulWidget {
|
||||||
|
const MaterialDemoScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MaterialDemoScreen> createState() => _MaterialDemoScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MaterialDemoScreenState extends State<MaterialDemoScreen> {
|
||||||
|
bool switch1 = false;
|
||||||
|
bool switch2 = true;
|
||||||
|
|
||||||
|
double sliderValue1 = 0.2;
|
||||||
|
double sliderValue2 = 0.5;
|
||||||
|
|
||||||
|
int navBarIndex = 0;
|
||||||
|
|
||||||
|
bool indicatorPlayAnimation = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 84,
|
||||||
|
),
|
||||||
|
ComponentDecoration(
|
||||||
|
title: 'Common Buttons',
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {},
|
||||||
|
child: const Text('Elevated'),
|
||||||
|
),
|
||||||
|
rowDivider,
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('Icon'),
|
||||||
|
),
|
||||||
|
rowDivider,
|
||||||
|
const ElevatedButton(
|
||||||
|
style: ButtonStyle(),
|
||||||
|
onPressed: null,
|
||||||
|
child: Text('Elevated'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () {},
|
||||||
|
child: const Text('Filled'),
|
||||||
|
),
|
||||||
|
rowDivider,
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('Icon'),
|
||||||
|
),
|
||||||
|
rowDivider,
|
||||||
|
const FilledButton(
|
||||||
|
style: ButtonStyle(),
|
||||||
|
onPressed: null,
|
||||||
|
child: Text('Filled'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {},
|
||||||
|
child: const Text('Text'),
|
||||||
|
),
|
||||||
|
rowDivider,
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('Icon'),
|
||||||
|
),
|
||||||
|
rowDivider,
|
||||||
|
const TextButton(
|
||||||
|
style: ButtonStyle(),
|
||||||
|
onPressed: null,
|
||||||
|
child: Text('Text'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ComponentDecoration(
|
||||||
|
title: 'Switches',
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
Switch(
|
||||||
|
value: switch1,
|
||||||
|
onChanged: (bool value) {
|
||||||
|
setState(() {
|
||||||
|
switch1 = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: switch2,
|
||||||
|
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
||||||
|
(Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return const Icon(Icons.check);
|
||||||
|
}
|
||||||
|
return null; // All other states will use the default thumbIcon.
|
||||||
|
}),
|
||||||
|
onChanged: (bool value) {
|
||||||
|
setState(() {
|
||||||
|
switch2 = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ComponentDecoration(
|
||||||
|
title: 'Floating Action Buttons',
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
FloatingActionButton.small(
|
||||||
|
onPressed: () {},
|
||||||
|
tooltip: 'Small',
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
FloatingActionButton(
|
||||||
|
onPressed: () {},
|
||||||
|
tooltip: 'Standard',
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
FloatingActionButton.extended(
|
||||||
|
onPressed: () {},
|
||||||
|
tooltip: 'Extended',
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('Create'),
|
||||||
|
),
|
||||||
|
FloatingActionButton.large(
|
||||||
|
onPressed: () {},
|
||||||
|
tooltip: 'Large',
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ComponentDecoration(
|
||||||
|
title: 'Sliders',
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
Slider(
|
||||||
|
value: sliderValue1,
|
||||||
|
onChanged: (double value) {
|
||||||
|
setState(() {
|
||||||
|
sliderValue1 = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Slider(
|
||||||
|
value: sliderValue2,
|
||||||
|
onChanged: (double value) {
|
||||||
|
setState(() {
|
||||||
|
sliderValue2 = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
divisions: 5,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ComponentDecoration(
|
||||||
|
title: 'Loading Indicators',
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
indicatorPlayAnimation = !indicatorPlayAnimation;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: indicatorPlayAnimation
|
||||||
|
? const Icon(Icons.pause)
|
||||||
|
: const Icon(Icons.play_arrow),
|
||||||
|
),
|
||||||
|
colDivider,
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
rowDivider,
|
||||||
|
CircularProgressIndicator(
|
||||||
|
value: indicatorPlayAnimation ? null : 0.5,
|
||||||
|
),
|
||||||
|
rowDivider,
|
||||||
|
Expanded(
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: indicatorPlayAnimation ? null : 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rowDivider,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ComponentDecoration(
|
||||||
|
title: 'Dialogs and BottomSheets',
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
// https://api.flutter.dev/flutter/material/showModalBottomSheet.html
|
||||||
|
return SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
const Text('Modal BottomSheet'),
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Text('Close BottomSheet'),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('show ModalBottomSheet'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Dialog Title'),
|
||||||
|
content: const Text('Dialog description'),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('Close'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('show Dialog'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Dialog.fullscreen(
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Fullscreen Dialog'),
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: ElevatedButton(
|
||||||
|
child: const Text('Close Dialog'),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('show Fullscreen Dialog'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ComponentDecoration(
|
||||||
|
title: 'Navigation Bar',
|
||||||
|
child: NavigationBar(
|
||||||
|
selectedIndex: navBarIndex,
|
||||||
|
onDestinationSelected: (value) {
|
||||||
|
setState(() {
|
||||||
|
navBarIndex = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destinations: const [
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.home),
|
||||||
|
label: 'Home',
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.favorite),
|
||||||
|
label: 'Favorites',
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.search),
|
||||||
|
label: 'Search',
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.settings),
|
||||||
|
label: 'Settings',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ComponentDecoration(
|
||||||
|
title: 'Bottom App Bar',
|
||||||
|
child: BottomAppBar(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.search),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.more_vert),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ComponentDecoration extends StatelessWidget {
|
||||||
|
const ComponentDecoration(
|
||||||
|
{required this.child, required this.title, super.key});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Center(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(title),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Icon(
|
||||||
|
Icons.info_outline,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: Card(
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
side: BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.outlineVariant,
|
||||||
|
),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 5.0,
|
||||||
|
vertical: 20.0,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
28
lib/features/matrix_rgb/business/service/matrix_service.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:dart_periphery/dart_periphery.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
// linux_spidev was tried out here beforehand,
|
||||||
|
// but it didn't work and was replaced by dart_periphery
|
||||||
|
// import 'package:linux_spidev/linux_spidev.dart';
|
||||||
|
|
||||||
|
class MatrixService {
|
||||||
|
final spi = SPI(0, 0, SPImode.mode0, 35000);
|
||||||
|
// final spidev = Spidev.fromBusDevNrs(0, 0);
|
||||||
|
|
||||||
|
void setLedsOn(List<int> pixels) async {
|
||||||
|
// final handle = spidev.open();
|
||||||
|
try {
|
||||||
|
int refresh = 1;
|
||||||
|
while (refresh != 0) {
|
||||||
|
spi.transfer(pixels, false);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
refresh--;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint(e.toString());
|
||||||
|
} finally {
|
||||||
|
// clean up the resource
|
||||||
|
spi.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
int findClosestColorIndex(Color sourceColor) {
|
||||||
|
int bestMatchIndex = 0;
|
||||||
|
double smallestDistance = double.infinity; // Start with a 'large' distance
|
||||||
|
|
||||||
|
// Iterate through possible controller colors (0-255)
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
// Example: Assume your controller just uses an even R/G/B scaling
|
||||||
|
int r = (i >> 5) * 32; // Assuming an even split to get 8 values
|
||||||
|
int g = ((i >> 2) & 0x7) * 32;
|
||||||
|
int b = (i & 0x3) * 64;
|
||||||
|
|
||||||
|
// Color controllerColor = Color.fromARGB(255, r, g, b);
|
||||||
|
|
||||||
|
// Calculate distance (you can use different formulas)
|
||||||
|
int distance = (sourceColor.red - r) * (sourceColor.red - r) +
|
||||||
|
(sourceColor.green - g) * (sourceColor.green - g) +
|
||||||
|
(sourceColor.blue - b) * (sourceColor.blue - b);
|
||||||
|
|
||||||
|
if (distance < smallestDistance) {
|
||||||
|
smallestDistance = distance.toDouble();
|
||||||
|
bestMatchIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestMatchIndex;
|
||||||
|
}
|
11
lib/features/matrix_rgb/data/models/pixel.dart
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
class Pixel {
|
||||||
|
final Offset offset;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
Pixel({
|
||||||
|
required this.offset,
|
||||||
|
required this.color,
|
||||||
|
});
|
||||||
|
}
|
143
lib/features/matrix_rgb/presentation/matrix_screen.dart
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import 'package:flex_color_picker/flex_color_picker.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../data/models/pixel.dart';
|
||||||
|
import 'widgets/pixel_painter.dart';
|
||||||
|
|
||||||
|
class MatrixScreen extends StatefulWidget {
|
||||||
|
const MatrixScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MatrixScreen> createState() => _MatrixScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MatrixScreenState extends State<MatrixScreen> {
|
||||||
|
List<Pixel> pixels = List.generate(
|
||||||
|
64,
|
||||||
|
(index) => Pixel(
|
||||||
|
offset: Offset.zero,
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Color color = Colors.green;
|
||||||
|
|
||||||
|
void _updatePixel(Offset offset) {
|
||||||
|
const double cellSize = 50; // Size of each grid cell
|
||||||
|
|
||||||
|
// Get the row and column based on touch position
|
||||||
|
int row = (offset.dy / cellSize).floor();
|
||||||
|
int col = (offset.dx / cellSize).floor();
|
||||||
|
|
||||||
|
// Bound Check (Only proceed if within the grid)
|
||||||
|
if (row >= 0 && row < 8 && col >= 0 && col < 8) {
|
||||||
|
// Calculate the index in the flat list
|
||||||
|
int index = row * 8 + col;
|
||||||
|
|
||||||
|
// Update the Pixel
|
||||||
|
pixels[index] = Pixel(offset: offset, color: color);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Listener(
|
||||||
|
onPointerDown: (event) {
|
||||||
|
// Determine offset
|
||||||
|
Offset offset =
|
||||||
|
Offset(event.localPosition.dx, event.localPosition.dy);
|
||||||
|
|
||||||
|
// Calculate index and update pixel
|
||||||
|
_updatePixel(offset);
|
||||||
|
},
|
||||||
|
onPointerMove: (event) {
|
||||||
|
// Determine offset
|
||||||
|
Offset offset =
|
||||||
|
Offset(event.localPosition.dx, event.localPosition.dy);
|
||||||
|
|
||||||
|
// Calculate index and update pixel
|
||||||
|
_updatePixel(offset);
|
||||||
|
},
|
||||||
|
child: InteractiveViewer(
|
||||||
|
maxScale: 5,
|
||||||
|
child: Container(
|
||||||
|
// color: Colors.white,
|
||||||
|
decoration: BoxDecoration(border: Border.all()),
|
||||||
|
child: ClipRRect(
|
||||||
|
child: GridPaper(
|
||||||
|
divisions: 2,
|
||||||
|
interval: 500,
|
||||||
|
color: Theme.of(context).textTheme.headlineLarge?.color ??
|
||||||
|
Colors.red,
|
||||||
|
child: Transform(
|
||||||
|
transform: Matrix4.identity()..scale(50.0),
|
||||||
|
child: ClipRect(
|
||||||
|
child: CustomPaint(
|
||||||
|
size: const Size(400, 400),
|
||||||
|
painter: PixelPainter(
|
||||||
|
pixels: pixels,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
),
|
||||||
|
ColorIndicator(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 24,
|
||||||
|
color: color,
|
||||||
|
elevation: 1,
|
||||||
|
onSelectFocus: false,
|
||||||
|
onSelect: () async {
|
||||||
|
// Wait for the dialog to return color selection result.
|
||||||
|
final Color newColor = await showColorPickerDialog(
|
||||||
|
context,
|
||||||
|
color,
|
||||||
|
title: Text('ColorPicker',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
spacing: 0,
|
||||||
|
runSpacing: 0,
|
||||||
|
borderRadius: 0,
|
||||||
|
wheelDiameter: 165,
|
||||||
|
enableOpacity: true,
|
||||||
|
showColorCode: true,
|
||||||
|
colorCodeHasColor: true,
|
||||||
|
pickersEnabled: <ColorPickerType, bool>{
|
||||||
|
ColorPickerType.wheel: true,
|
||||||
|
},
|
||||||
|
copyPasteBehavior: const ColorPickerCopyPasteBehavior(
|
||||||
|
copyButton: true,
|
||||||
|
pasteButton: true,
|
||||||
|
longPressMenu: true,
|
||||||
|
),
|
||||||
|
actionButtons: const ColorPickerActionButtons(
|
||||||
|
okButton: true,
|
||||||
|
closeButton: true,
|
||||||
|
dialogActionButtons: false,
|
||||||
|
),
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minHeight: 480, minWidth: 320, maxWidth: 320),
|
||||||
|
);
|
||||||
|
// Update the state with the newly selected color from the color picker
|
||||||
|
setState(() {
|
||||||
|
color = newColor;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../data/models/pixel.dart';
|
||||||
|
import '../../business/service/matrix_service.dart';
|
||||||
|
import '../../business/utils/find_closest_color_index.dart';
|
||||||
|
|
||||||
|
class PixelPainter extends CustomPainter {
|
||||||
|
final List<Pixel> pixels;
|
||||||
|
|
||||||
|
PixelPainter({required this.pixels});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
const double cellSize = 50;
|
||||||
|
|
||||||
|
if (pixels.isNotEmpty) {
|
||||||
|
for (final pixel in pixels) {
|
||||||
|
final Offset roundedOffset = Offset(
|
||||||
|
(pixel.offset.dx / cellSize).clamp(0, 7).toInt() * cellSize + 25,
|
||||||
|
(pixel.offset.dy / cellSize).clamp(0, 7).toInt() * cellSize + 25,
|
||||||
|
);
|
||||||
|
|
||||||
|
canvas.drawPoints(
|
||||||
|
PointMode.points,
|
||||||
|
[roundedOffset / 50],
|
||||||
|
Paint()
|
||||||
|
..color = pixel.color
|
||||||
|
..strokeWidth = 1
|
||||||
|
..isAntiAlias = false
|
||||||
|
..strokeJoin = StrokeJoin.round
|
||||||
|
..style = PaintingStyle.stroke,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming 'width' represents the width of your matrix (in this case, 8)
|
||||||
|
const width = 8;
|
||||||
|
|
||||||
|
List<int> colorIntList = pixels.map((pixel) {
|
||||||
|
if (pixel.color.value != 0) return findClosestColorIndex(pixel.color);
|
||||||
|
return pixel.color.value;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
// horizontal mirroring
|
||||||
|
List<int> mirroredList = List.generate(64, (index) {
|
||||||
|
int row = index ~/ width;
|
||||||
|
int reversedCol = width - 1 - (index % width);
|
||||||
|
int newIndex = row * width + reversedCol;
|
||||||
|
return colorIntList[newIndex];
|
||||||
|
});
|
||||||
|
|
||||||
|
// dart periphery is only supported on linux
|
||||||
|
if (Platform.isLinux) {
|
||||||
|
MatrixService().setLedsOn(mirroredList.reversed.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||||
|
}
|
102
lib/features/morse_led/business/service/morse_service.dart
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import 'package:flutter_gpiod/flutter_gpiod.dart';
|
||||||
|
|
||||||
|
class MorseService {
|
||||||
|
final _chips = FlutterGpiod.instance.chips;
|
||||||
|
|
||||||
|
printMessage(String message) async {
|
||||||
|
final chip = _getRaspberryGPIO();
|
||||||
|
final ledPin = chip.lines[18];
|
||||||
|
ledPin.requestOutput(consumer: "morse code led", initialValue: false);
|
||||||
|
final List<String> chars = message.toUpperCase().split('');
|
||||||
|
const int pulseDuration = 100;
|
||||||
|
|
||||||
|
for (var char in chars) {
|
||||||
|
final List<String> morseCode = _morseAlphabet[char]?.split('') ?? ['*'];
|
||||||
|
for (var code in morseCode) {
|
||||||
|
print('CODE: $code from $morseCode');
|
||||||
|
if (code == '*') {
|
||||||
|
await Future.delayed(const Duration(milliseconds: pulseDuration * 7));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ledPin.setValue(true);
|
||||||
|
print('ON');
|
||||||
|
if (code == '.') {
|
||||||
|
await Future.delayed(const Duration(milliseconds: pulseDuration));
|
||||||
|
} else if (code == '-') {
|
||||||
|
await Future.delayed(const Duration(milliseconds: pulseDuration * 3));
|
||||||
|
}
|
||||||
|
print('OFF');
|
||||||
|
ledPin.setValue(false);
|
||||||
|
// pause between letters
|
||||||
|
await Future.delayed(const Duration(milliseconds: pulseDuration));
|
||||||
|
}
|
||||||
|
print("SPACE BETWEEN LETTERS");
|
||||||
|
// space between letters is 3*pulseDuration
|
||||||
|
await Future.delayed(const Duration(milliseconds: pulseDuration * 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
ledPin.release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GpioChip _getRaspberryGPIO() {
|
||||||
|
/// Retrieve the line with index 23 of the first chip.
|
||||||
|
/// This is BCM pin 23 for the Raspberry Pi.
|
||||||
|
///
|
||||||
|
/// I recommend finding the chip you want
|
||||||
|
/// based on the chip label, as is done here.
|
||||||
|
///
|
||||||
|
/// In this example, we search for the main Raspberry Pi GPIO chip and then
|
||||||
|
/// retrieve the line with index 23 of it. So [line] is GPIO pin BCM 23.
|
||||||
|
///
|
||||||
|
/// The main GPIO chip is called `pinctrl-bcm2711` on Pi 4 and `pinctrl-bcm2835`
|
||||||
|
/// on older Raspberry Pis and it was also called that way on Pi 4 with older
|
||||||
|
/// kernel versions.
|
||||||
|
final chip = _chips.singleWhere(
|
||||||
|
(chip) => chip.label == 'pinctrl-bcm2711',
|
||||||
|
orElse: () =>
|
||||||
|
_chips.singleWhere((chip) => chip.label == 'pinctrl-bcm2835'),
|
||||||
|
);
|
||||||
|
return chip;
|
||||||
|
}
|
||||||
|
|
||||||
|
// International Morse Code
|
||||||
|
final Map<String, String> _morseAlphabet = {
|
||||||
|
'A': '.-',
|
||||||
|
'B': '-...',
|
||||||
|
'C': '-.-.',
|
||||||
|
'D': '-..',
|
||||||
|
'E': '.',
|
||||||
|
'F': '..-.',
|
||||||
|
'G': '--.',
|
||||||
|
'H': '....',
|
||||||
|
'I': '..',
|
||||||
|
'J': '.---',
|
||||||
|
'K': '-.-',
|
||||||
|
'L': '.-..',
|
||||||
|
'M': '--',
|
||||||
|
'N': '-.',
|
||||||
|
'O': '---',
|
||||||
|
'P': '.--.',
|
||||||
|
'Q': '--.-',
|
||||||
|
'R': '.-.',
|
||||||
|
'S': '...',
|
||||||
|
'T': '-',
|
||||||
|
'U': '..-',
|
||||||
|
'V': '...-',
|
||||||
|
'W': '.--',
|
||||||
|
'X': '-..-',
|
||||||
|
'Y': '-.--',
|
||||||
|
'Z': '--..',
|
||||||
|
'0': '-----',
|
||||||
|
'1': '.----',
|
||||||
|
'2': '..---',
|
||||||
|
'3': '...--',
|
||||||
|
'4': '....-',
|
||||||
|
'5': '.....',
|
||||||
|
'6': '-....',
|
||||||
|
'7': '--...',
|
||||||
|
'8': '---..',
|
||||||
|
'9': '----.',
|
||||||
|
};
|
||||||
|
}
|
57
lib/features/morse_led/presentation/led_screen.dart
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../../../common/widgets/text_input_dialog.dart';
|
||||||
|
import '../business/service/morse_service.dart';
|
||||||
|
|
||||||
|
class LedScreen extends ConsumerWidget {
|
||||||
|
const LedScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final TextEditingController textController = TextEditingController();
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 224,
|
||||||
|
child: Card(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3),
|
||||||
|
elevation: 0,
|
||||||
|
margin: const EdgeInsets.all(32),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 400),
|
||||||
|
child: TextInputDialog(
|
||||||
|
controller: textController,
|
||||||
|
title: 'Enter Morse Code Message',
|
||||||
|
onReturnPress: () {
|
||||||
|
// FocusScope.of(context).unfocus();
|
||||||
|
context.pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
MorseService().printMessage(textController.text);
|
||||||
|
},
|
||||||
|
child: const Text("Morse Code"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
45
lib/features/news_api/business/news_controller.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
import '../data/repository/news_repository.dart';
|
||||||
|
import '../data/models/region_news.dart';
|
||||||
|
|
||||||
|
part 'news_controller.g.dart';
|
||||||
|
|
||||||
|
/// The news controller
|
||||||
|
@riverpod
|
||||||
|
class NewsController extends _$NewsController {
|
||||||
|
@override
|
||||||
|
Future<RegionNews> build() async {
|
||||||
|
try {
|
||||||
|
final String newsResponse = await NewsRepository().fetchNews();
|
||||||
|
final Map<String, dynamic> jsonData = json.decode(newsResponse);
|
||||||
|
final RegionNews news = RegionNews.fromJson(jsonData);
|
||||||
|
return news;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append the next page of news to the current news list
|
||||||
|
Future<void> appendPage() async {
|
||||||
|
try {
|
||||||
|
final RegionNews currentNews =
|
||||||
|
state.asData?.value ?? const RegionNews(news: []);
|
||||||
|
final newsResponse =
|
||||||
|
await NewsRepository().fetchNewsPage(currentNews.nextPage ?? 'null');
|
||||||
|
final Map<String, dynamic> jsonData = jsonDecode(newsResponse);
|
||||||
|
final RegionNews nextNews = RegionNews.fromJson(jsonData);
|
||||||
|
final RegionNews appendNews =
|
||||||
|
currentNews.copyWith(nextPage: nextNews.nextPage, news: [
|
||||||
|
...currentNews.news,
|
||||||
|
...nextNews.news,
|
||||||
|
]);
|
||||||
|
state = AsyncData(appendNews);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
lib/features/news_api/business/news_controller.g.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'news_controller.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$newsControllerHash() => r'd2b544008f72eeee9a71bec7d0fe5b8981536fee';
|
||||||
|
|
||||||
|
/// The news controller
|
||||||
|
///
|
||||||
|
/// Copied from [NewsController].
|
||||||
|
@ProviderFor(NewsController)
|
||||||
|
final newsControllerProvider =
|
||||||
|
AutoDisposeAsyncNotifierProvider<NewsController, RegionNews>.internal(
|
||||||
|
NewsController.new,
|
||||||
|
name: r'newsControllerProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$newsControllerHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$NewsController = AutoDisposeAsyncNotifier<RegionNews>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
15
lib/features/news_api/business/scroll_state_controller.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'scroll_state_controller.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class ScrollStateController extends _$ScrollStateController {
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(bool set) {
|
||||||
|
state = set;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'scroll_state_controller.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$scrollStateControllerHash() =>
|
||||||
|
r'0fd7eab4471ba8adac0d631c0580afb88e31901d';
|
||||||
|
|
||||||
|
/// See also [ScrollStateController].
|
||||||
|
@ProviderFor(ScrollStateController)
|
||||||
|
final scrollStateControllerProvider =
|
||||||
|
AutoDisposeNotifierProvider<ScrollStateController, bool>.internal(
|
||||||
|
ScrollStateController.new,
|
||||||
|
name: r'scrollStateControllerProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$scrollStateControllerHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$ScrollStateController = AutoDisposeNotifier<bool>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
134
lib/features/news_api/data/models/news.dart
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import 'teaser_image.dart';
|
||||||
|
|
||||||
|
part 'news.freezed.dart';
|
||||||
|
part 'news.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class News with _$News {
|
||||||
|
const factory News({
|
||||||
|
String? title,
|
||||||
|
TeaserImage? teaserImage,
|
||||||
|
TeaserImage? brandingImage,
|
||||||
|
String? date,
|
||||||
|
String? type,
|
||||||
|
}) = _News;
|
||||||
|
|
||||||
|
factory News.fromJson(Map<String, dynamic> json) => _$NewsFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
// old manual implementation:
|
||||||
|
|
||||||
|
// final String sophoraId;
|
||||||
|
// final String externalId;
|
||||||
|
// final String title;
|
||||||
|
// final TeaserImage? teaserImage;
|
||||||
|
// final TeaserImage? brandingImage;
|
||||||
|
// final String date;
|
||||||
|
// final String updateCheckUrl;
|
||||||
|
// final int regionId;
|
||||||
|
// final String details;
|
||||||
|
// final String detailsweb;
|
||||||
|
// final String shareURL;
|
||||||
|
// final String topline;
|
||||||
|
// final String firstSentence;
|
||||||
|
// final String type;
|
||||||
|
// News({
|
||||||
|
// required this.sophoraId,
|
||||||
|
// required this.externalId,
|
||||||
|
// required this.title,
|
||||||
|
// this.teaserImage,
|
||||||
|
// this.brandingImage,
|
||||||
|
// required this.date,
|
||||||
|
// required this.updateCheckUrl,
|
||||||
|
// required this.regionId,
|
||||||
|
// required this.details,
|
||||||
|
// required this.detailsweb,
|
||||||
|
// required this.shareURL,
|
||||||
|
// required this.topline,
|
||||||
|
// required this.firstSentence,
|
||||||
|
// required this.type,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// News copyWith({
|
||||||
|
// String? sophoraId,
|
||||||
|
// String? externalId,
|
||||||
|
// String? title,
|
||||||
|
// TeaserImage? teaserImage,
|
||||||
|
// TeaserImage? brandingImage,
|
||||||
|
// String? date,
|
||||||
|
// String? updateCheckUrl,
|
||||||
|
// int? regionId,
|
||||||
|
// String? details,
|
||||||
|
// String? detailsweb,
|
||||||
|
// String? shareURL,
|
||||||
|
// String? topline,
|
||||||
|
// String? firstSentence,
|
||||||
|
// String? type,
|
||||||
|
// }) {
|
||||||
|
// return News(
|
||||||
|
// sophoraId: sophoraId ?? this.sophoraId,
|
||||||
|
// externalId: externalId ?? this.externalId,
|
||||||
|
// title: title ?? this.title,
|
||||||
|
// teaserImage: teaserImage ?? this.teaserImage,
|
||||||
|
// brandingImage: brandingImage ?? this.brandingImage,
|
||||||
|
// date: date ?? this.date,
|
||||||
|
// updateCheckUrl: updateCheckUrl ?? this.updateCheckUrl,
|
||||||
|
// regionId: regionId ?? this.regionId,
|
||||||
|
// details: details ?? this.details,
|
||||||
|
// detailsweb: detailsweb ?? this.detailsweb,
|
||||||
|
// shareURL: shareURL ?? this.shareURL,
|
||||||
|
// topline: topline ?? this.topline,
|
||||||
|
// firstSentence: firstSentence ?? this.firstSentence,
|
||||||
|
// type: type ?? this.type,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Map<String, dynamic> toMap() {
|
||||||
|
// return {
|
||||||
|
// 'sophoraId': sophoraId,
|
||||||
|
// 'externalId': externalId,
|
||||||
|
// 'title': title,
|
||||||
|
// 'teaserImage': teaserImage?.toMap(),
|
||||||
|
// 'brandingImage': brandingImage?.toMap(),
|
||||||
|
// 'date': date,
|
||||||
|
// 'updateCheckUrl': updateCheckUrl,
|
||||||
|
// 'regionId': regionId,
|
||||||
|
// 'details': details,
|
||||||
|
// 'detailsweb': detailsweb,
|
||||||
|
// 'shareURL': shareURL,
|
||||||
|
// 'topline': topline,
|
||||||
|
// 'firstSentence': firstSentence,
|
||||||
|
// 'type': type,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// factory News.fromMap(Map<String, dynamic> map) {
|
||||||
|
// return News(
|
||||||
|
// sophoraId: map['sophoraId'] ?? '',
|
||||||
|
// externalId: map['externalId'] ?? '',
|
||||||
|
// title: map['title'] ?? '',
|
||||||
|
// teaserImage: map['teaserImage'] != null
|
||||||
|
// ? TeaserImage.fromMap(map['teaserImage'])
|
||||||
|
// : null,
|
||||||
|
// brandingImage: map['brandingImage'] != null
|
||||||
|
// ? TeaserImage.fromMap(map['brandingImage'])
|
||||||
|
// : null,
|
||||||
|
// date: map['date'] ?? '',
|
||||||
|
// updateCheckUrl: map['updateCheckUrl'] ?? '',
|
||||||
|
// regionId: map['regionId']?.toInt() ?? 0,
|
||||||
|
// details: map['details'] ?? '',
|
||||||
|
// detailsweb: map['detailsweb'] ?? '',
|
||||||
|
// shareURL: map['shareURL'] ?? '',
|
||||||
|
// topline: map['topline'] ?? '',
|
||||||
|
// firstSentence: map['firstSentence'] ?? '',
|
||||||
|
// type: map['type'] ?? '',
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
// factory News.fromJson(String source) => News.fromMap(json.decode(source));
|
||||||
|
// }
|
273
lib/features/news_api/data/models/news.freezed.dart
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'news.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
News _$NewsFromJson(Map<String, dynamic> json) {
|
||||||
|
return _News.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$News {
|
||||||
|
String? get title => throw _privateConstructorUsedError;
|
||||||
|
TeaserImage? get teaserImage => throw _privateConstructorUsedError;
|
||||||
|
TeaserImage? get brandingImage => throw _privateConstructorUsedError;
|
||||||
|
String? get date => throw _privateConstructorUsedError;
|
||||||
|
String? get type => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$NewsCopyWith<News> get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $NewsCopyWith<$Res> {
|
||||||
|
factory $NewsCopyWith(News value, $Res Function(News) then) =
|
||||||
|
_$NewsCopyWithImpl<$Res, News>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{String? title,
|
||||||
|
TeaserImage? teaserImage,
|
||||||
|
TeaserImage? brandingImage,
|
||||||
|
String? date,
|
||||||
|
String? type});
|
||||||
|
|
||||||
|
$TeaserImageCopyWith<$Res>? get teaserImage;
|
||||||
|
$TeaserImageCopyWith<$Res>? get brandingImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$NewsCopyWithImpl<$Res, $Val extends News>
|
||||||
|
implements $NewsCopyWith<$Res> {
|
||||||
|
_$NewsCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? title = freezed,
|
||||||
|
Object? teaserImage = freezed,
|
||||||
|
Object? brandingImage = freezed,
|
||||||
|
Object? date = freezed,
|
||||||
|
Object? type = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
title: freezed == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
teaserImage: freezed == teaserImage
|
||||||
|
? _value.teaserImage
|
||||||
|
: teaserImage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as TeaserImage?,
|
||||||
|
brandingImage: freezed == brandingImage
|
||||||
|
? _value.brandingImage
|
||||||
|
: brandingImage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as TeaserImage?,
|
||||||
|
date: freezed == date
|
||||||
|
? _value.date
|
||||||
|
: date // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
type: freezed == type
|
||||||
|
? _value.type
|
||||||
|
: type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$TeaserImageCopyWith<$Res>? get teaserImage {
|
||||||
|
if (_value.teaserImage == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $TeaserImageCopyWith<$Res>(_value.teaserImage!, (value) {
|
||||||
|
return _then(_value.copyWith(teaserImage: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$TeaserImageCopyWith<$Res>? get brandingImage {
|
||||||
|
if (_value.brandingImage == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $TeaserImageCopyWith<$Res>(_value.brandingImage!, (value) {
|
||||||
|
return _then(_value.copyWith(brandingImage: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$NewsImplCopyWith<$Res> implements $NewsCopyWith<$Res> {
|
||||||
|
factory _$$NewsImplCopyWith(
|
||||||
|
_$NewsImpl value, $Res Function(_$NewsImpl) then) =
|
||||||
|
__$$NewsImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{String? title,
|
||||||
|
TeaserImage? teaserImage,
|
||||||
|
TeaserImage? brandingImage,
|
||||||
|
String? date,
|
||||||
|
String? type});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$TeaserImageCopyWith<$Res>? get teaserImage;
|
||||||
|
@override
|
||||||
|
$TeaserImageCopyWith<$Res>? get brandingImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$NewsImplCopyWithImpl<$Res>
|
||||||
|
extends _$NewsCopyWithImpl<$Res, _$NewsImpl>
|
||||||
|
implements _$$NewsImplCopyWith<$Res> {
|
||||||
|
__$$NewsImplCopyWithImpl(_$NewsImpl _value, $Res Function(_$NewsImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? title = freezed,
|
||||||
|
Object? teaserImage = freezed,
|
||||||
|
Object? brandingImage = freezed,
|
||||||
|
Object? date = freezed,
|
||||||
|
Object? type = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$NewsImpl(
|
||||||
|
title: freezed == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
teaserImage: freezed == teaserImage
|
||||||
|
? _value.teaserImage
|
||||||
|
: teaserImage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as TeaserImage?,
|
||||||
|
brandingImage: freezed == brandingImage
|
||||||
|
? _value.brandingImage
|
||||||
|
: brandingImage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as TeaserImage?,
|
||||||
|
date: freezed == date
|
||||||
|
? _value.date
|
||||||
|
: date // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
type: freezed == type
|
||||||
|
? _value.type
|
||||||
|
: type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$NewsImpl with DiagnosticableTreeMixin implements _News {
|
||||||
|
const _$NewsImpl(
|
||||||
|
{this.title, this.teaserImage, this.brandingImage, this.date, this.type});
|
||||||
|
|
||||||
|
factory _$NewsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$NewsImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? title;
|
||||||
|
@override
|
||||||
|
final TeaserImage? teaserImage;
|
||||||
|
@override
|
||||||
|
final TeaserImage? brandingImage;
|
||||||
|
@override
|
||||||
|
final String? date;
|
||||||
|
@override
|
||||||
|
final String? type;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||||
|
return 'News(title: $title, teaserImage: $teaserImage, brandingImage: $brandingImage, date: $date, type: $type)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
properties
|
||||||
|
..add(DiagnosticsProperty('type', 'News'))
|
||||||
|
..add(DiagnosticsProperty('title', title))
|
||||||
|
..add(DiagnosticsProperty('teaserImage', teaserImage))
|
||||||
|
..add(DiagnosticsProperty('brandingImage', brandingImage))
|
||||||
|
..add(DiagnosticsProperty('date', date))
|
||||||
|
..add(DiagnosticsProperty('type', type));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$NewsImpl &&
|
||||||
|
(identical(other.title, title) || other.title == title) &&
|
||||||
|
(identical(other.teaserImage, teaserImage) ||
|
||||||
|
other.teaserImage == teaserImage) &&
|
||||||
|
(identical(other.brandingImage, brandingImage) ||
|
||||||
|
other.brandingImage == brandingImage) &&
|
||||||
|
(identical(other.date, date) || other.date == date) &&
|
||||||
|
(identical(other.type, type) || other.type == type));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
Object.hash(runtimeType, title, teaserImage, brandingImage, date, type);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$NewsImplCopyWith<_$NewsImpl> get copyWith =>
|
||||||
|
__$$NewsImplCopyWithImpl<_$NewsImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$NewsImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _News implements News {
|
||||||
|
const factory _News(
|
||||||
|
{final String? title,
|
||||||
|
final TeaserImage? teaserImage,
|
||||||
|
final TeaserImage? brandingImage,
|
||||||
|
final String? date,
|
||||||
|
final String? type}) = _$NewsImpl;
|
||||||
|
|
||||||
|
factory _News.fromJson(Map<String, dynamic> json) = _$NewsImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get title;
|
||||||
|
@override
|
||||||
|
TeaserImage? get teaserImage;
|
||||||
|
@override
|
||||||
|
TeaserImage? get brandingImage;
|
||||||
|
@override
|
||||||
|
String? get date;
|
||||||
|
@override
|
||||||
|
String? get type;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$NewsImplCopyWith<_$NewsImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
28
lib/features/news_api/data/models/news.g.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'news.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$NewsImpl _$$NewsImplFromJson(Map<String, dynamic> json) => _$NewsImpl(
|
||||||
|
title: json['title'] as String?,
|
||||||
|
teaserImage: json['teaserImage'] == null
|
||||||
|
? null
|
||||||
|
: TeaserImage.fromJson(json['teaserImage'] as Map<String, dynamic>),
|
||||||
|
brandingImage: json['brandingImage'] == null
|
||||||
|
? null
|
||||||
|
: TeaserImage.fromJson(json['brandingImage'] as Map<String, dynamic>),
|
||||||
|
date: json['date'] as String?,
|
||||||
|
type: json['type'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$NewsImplToJson(_$NewsImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'title': instance.title,
|
||||||
|
'teaserImage': instance.teaserImage,
|
||||||
|
'brandingImage': instance.brandingImage,
|
||||||
|
'date': instance.date,
|
||||||
|
'type': instance.type,
|
||||||
|
};
|
94
lib/features/news_api/data/models/region_news.dart
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import 'news.dart';
|
||||||
|
|
||||||
|
part 'region_news.freezed.dart';
|
||||||
|
part 'region_news.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class RegionNews with _$RegionNews {
|
||||||
|
const factory RegionNews({
|
||||||
|
required List<News> news,
|
||||||
|
String? nextPage,
|
||||||
|
}) = _RegionNews;
|
||||||
|
|
||||||
|
factory RegionNews.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$RegionNewsFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
// old manual implementation:
|
||||||
|
|
||||||
|
// class RegionNews {
|
||||||
|
// List<News> news;
|
||||||
|
// String newStoriesCountLink;
|
||||||
|
// String type;
|
||||||
|
// String? nextPage;
|
||||||
|
// RegionNews({
|
||||||
|
// required this.news,
|
||||||
|
// required this.newStoriesCountLink,
|
||||||
|
// required this.type,
|
||||||
|
// this.nextPage,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// RegionNews copyWith({
|
||||||
|
// List<News>? news,
|
||||||
|
// String? newStoriesCountLink,
|
||||||
|
// String? type,
|
||||||
|
// String? nextPage,
|
||||||
|
// }) {
|
||||||
|
// return RegionNews(
|
||||||
|
// news: news ?? this.news,
|
||||||
|
// newStoriesCountLink: newStoriesCountLink ?? this.newStoriesCountLink,
|
||||||
|
// type: type ?? this.type,
|
||||||
|
// nextPage: nextPage ?? this.nextPage,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Map<String, dynamic> toMap() {
|
||||||
|
// return {
|
||||||
|
// 'news': news.map((x) => x.toMap()).toList(),
|
||||||
|
// 'newStoriesCountLink': newStoriesCountLink,
|
||||||
|
// 'type': type,
|
||||||
|
// 'nextPage': nextPage,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// factory RegionNews.fromMap(Map<String, dynamic> map) {
|
||||||
|
// return RegionNews(
|
||||||
|
// news: List<News>.from(map['news']?.map((x) => News.fromMap(x))),
|
||||||
|
// newStoriesCountLink: map['newStoriesCountLink'] ?? '',
|
||||||
|
// type: map['type'] ?? '',
|
||||||
|
// nextPage: map['nextPage'],
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
// factory RegionNews.fromJson(String source) =>
|
||||||
|
// RegionNews.fromMap(json.decode(source));
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// String toString() {
|
||||||
|
// return 'RegionNews(news: $news, newStoriesCountLink: $newStoriesCountLink, type: $type, nextPage: $nextPage)';
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// bool operator ==(Object other) {
|
||||||
|
// if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
// return other is RegionNews &&
|
||||||
|
// listEquals(other.news, news) &&
|
||||||
|
// other.newStoriesCountLink == newStoriesCountLink &&
|
||||||
|
// other.type == type &&
|
||||||
|
// other.nextPage == nextPage;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// int get hashCode {
|
||||||
|
// return news.hashCode ^
|
||||||
|
// newStoriesCountLink.hashCode ^
|
||||||
|
// type.hashCode ^
|
||||||
|
// nextPage.hashCode;
|
||||||
|
// }
|
||||||
|
// }
|
187
lib/features/news_api/data/models/region_news.freezed.dart
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'region_news.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
RegionNews _$RegionNewsFromJson(Map<String, dynamic> json) {
|
||||||
|
return _RegionNews.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$RegionNews {
|
||||||
|
List<News> get news => throw _privateConstructorUsedError;
|
||||||
|
String? get nextPage => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$RegionNewsCopyWith<RegionNews> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $RegionNewsCopyWith<$Res> {
|
||||||
|
factory $RegionNewsCopyWith(
|
||||||
|
RegionNews value, $Res Function(RegionNews) then) =
|
||||||
|
_$RegionNewsCopyWithImpl<$Res, RegionNews>;
|
||||||
|
@useResult
|
||||||
|
$Res call({List<News> news, String? nextPage});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$RegionNewsCopyWithImpl<$Res, $Val extends RegionNews>
|
||||||
|
implements $RegionNewsCopyWith<$Res> {
|
||||||
|
_$RegionNewsCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? news = null,
|
||||||
|
Object? nextPage = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
news: null == news
|
||||||
|
? _value.news
|
||||||
|
: news // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<News>,
|
||||||
|
nextPage: freezed == nextPage
|
||||||
|
? _value.nextPage
|
||||||
|
: nextPage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$RegionNewsImplCopyWith<$Res>
|
||||||
|
implements $RegionNewsCopyWith<$Res> {
|
||||||
|
factory _$$RegionNewsImplCopyWith(
|
||||||
|
_$RegionNewsImpl value, $Res Function(_$RegionNewsImpl) then) =
|
||||||
|
__$$RegionNewsImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({List<News> news, String? nextPage});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$RegionNewsImplCopyWithImpl<$Res>
|
||||||
|
extends _$RegionNewsCopyWithImpl<$Res, _$RegionNewsImpl>
|
||||||
|
implements _$$RegionNewsImplCopyWith<$Res> {
|
||||||
|
__$$RegionNewsImplCopyWithImpl(
|
||||||
|
_$RegionNewsImpl _value, $Res Function(_$RegionNewsImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? news = null,
|
||||||
|
Object? nextPage = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$RegionNewsImpl(
|
||||||
|
news: null == news
|
||||||
|
? _value._news
|
||||||
|
: news // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<News>,
|
||||||
|
nextPage: freezed == nextPage
|
||||||
|
? _value.nextPage
|
||||||
|
: nextPage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$RegionNewsImpl with DiagnosticableTreeMixin implements _RegionNews {
|
||||||
|
const _$RegionNewsImpl({required final List<News> news, this.nextPage})
|
||||||
|
: _news = news;
|
||||||
|
|
||||||
|
factory _$RegionNewsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$RegionNewsImplFromJson(json);
|
||||||
|
|
||||||
|
final List<News> _news;
|
||||||
|
@override
|
||||||
|
List<News> get news {
|
||||||
|
if (_news is EqualUnmodifiableListView) return _news;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_news);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? nextPage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||||
|
return 'RegionNews(news: $news, nextPage: $nextPage)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
properties
|
||||||
|
..add(DiagnosticsProperty('type', 'RegionNews'))
|
||||||
|
..add(DiagnosticsProperty('news', news))
|
||||||
|
..add(DiagnosticsProperty('nextPage', nextPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$RegionNewsImpl &&
|
||||||
|
const DeepCollectionEquality().equals(other._news, _news) &&
|
||||||
|
(identical(other.nextPage, nextPage) ||
|
||||||
|
other.nextPage == nextPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType, const DeepCollectionEquality().hash(_news), nextPage);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$RegionNewsImplCopyWith<_$RegionNewsImpl> get copyWith =>
|
||||||
|
__$$RegionNewsImplCopyWithImpl<_$RegionNewsImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$RegionNewsImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _RegionNews implements RegionNews {
|
||||||
|
const factory _RegionNews(
|
||||||
|
{required final List<News> news,
|
||||||
|
final String? nextPage}) = _$RegionNewsImpl;
|
||||||
|
|
||||||
|
factory _RegionNews.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$RegionNewsImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<News> get news;
|
||||||
|
@override
|
||||||
|
String? get nextPage;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$RegionNewsImplCopyWith<_$RegionNewsImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
21
lib/features/news_api/data/models/region_news.g.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'region_news.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$RegionNewsImpl _$$RegionNewsImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$RegionNewsImpl(
|
||||||
|
news: (json['news'] as List<dynamic>)
|
||||||
|
.map((e) => News.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
nextPage: json['nextPage'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$RegionNewsImplToJson(_$RegionNewsImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'news': instance.news,
|
||||||
|
'nextPage': instance.nextPage,
|
||||||
|
};
|
62
lib/features/news_api/data/models/teaser_image.dart
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'teaser_image.freezed.dart';
|
||||||
|
part 'teaser_image.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class TeaserImage with _$TeaserImage {
|
||||||
|
const factory TeaserImage({
|
||||||
|
Map<String, String>? imageVariants,
|
||||||
|
}) = _TeaserImage;
|
||||||
|
|
||||||
|
factory TeaserImage.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TeaserImageFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
// old manual implementation:
|
||||||
|
|
||||||
|
// class TeaserImage {
|
||||||
|
// final String? title;
|
||||||
|
// final String alttext;
|
||||||
|
// final String? copyright;
|
||||||
|
// final String type;
|
||||||
|
// final Map<String, String> imageVariants;
|
||||||
|
|
||||||
|
// TeaserImage({
|
||||||
|
// this.title,
|
||||||
|
// required this.alttext,
|
||||||
|
// this.copyright,
|
||||||
|
// required this.type,
|
||||||
|
// required this.imageVariants,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Map<String, dynamic> toMap() {
|
||||||
|
// return {
|
||||||
|
// 'title': title,
|
||||||
|
// 'alttext': alttext,
|
||||||
|
// 'copyright': copyright,
|
||||||
|
// 'type': type,
|
||||||
|
// 'imageVariants': Map.from(imageVariants),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// factory TeaserImage.fromMap(Map<String, dynamic> map) {
|
||||||
|
// return TeaserImage(
|
||||||
|
// title: map['title'] ?? '',
|
||||||
|
// alttext: map['alttext'] ?? '',
|
||||||
|
// copyright: map['copyright'],
|
||||||
|
// type: map['type'] ?? '',
|
||||||
|
// imageVariants: Map.from(map['imageVariants']),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
// factory TeaserImage.fromJson(String source) =>
|
||||||
|
// TeaserImage.fromMap(json.decode(source));
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// String toString() {
|
||||||
|
// return 'TeaserImage(title: $title, alttext: $alttext, copyright: $copyright, type: $type)';
|
||||||
|
// }
|
||||||
|
// }
|
162
lib/features/news_api/data/models/teaser_image.freezed.dart
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'teaser_image.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
TeaserImage _$TeaserImageFromJson(Map<String, dynamic> json) {
|
||||||
|
return _TeaserImage.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$TeaserImage {
|
||||||
|
Map<String, String>? get imageVariants => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$TeaserImageCopyWith<TeaserImage> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $TeaserImageCopyWith<$Res> {
|
||||||
|
factory $TeaserImageCopyWith(
|
||||||
|
TeaserImage value, $Res Function(TeaserImage) then) =
|
||||||
|
_$TeaserImageCopyWithImpl<$Res, TeaserImage>;
|
||||||
|
@useResult
|
||||||
|
$Res call({Map<String, String>? imageVariants});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$TeaserImageCopyWithImpl<$Res, $Val extends TeaserImage>
|
||||||
|
implements $TeaserImageCopyWith<$Res> {
|
||||||
|
_$TeaserImageCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? imageVariants = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
imageVariants: freezed == imageVariants
|
||||||
|
? _value.imageVariants
|
||||||
|
: imageVariants // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, String>?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$TeaserImageImplCopyWith<$Res>
|
||||||
|
implements $TeaserImageCopyWith<$Res> {
|
||||||
|
factory _$$TeaserImageImplCopyWith(
|
||||||
|
_$TeaserImageImpl value, $Res Function(_$TeaserImageImpl) then) =
|
||||||
|
__$$TeaserImageImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({Map<String, String>? imageVariants});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$TeaserImageImplCopyWithImpl<$Res>
|
||||||
|
extends _$TeaserImageCopyWithImpl<$Res, _$TeaserImageImpl>
|
||||||
|
implements _$$TeaserImageImplCopyWith<$Res> {
|
||||||
|
__$$TeaserImageImplCopyWithImpl(
|
||||||
|
_$TeaserImageImpl _value, $Res Function(_$TeaserImageImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? imageVariants = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$TeaserImageImpl(
|
||||||
|
imageVariants: freezed == imageVariants
|
||||||
|
? _value._imageVariants
|
||||||
|
: imageVariants // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, String>?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$TeaserImageImpl implements _TeaserImage {
|
||||||
|
const _$TeaserImageImpl({final Map<String, String>? imageVariants})
|
||||||
|
: _imageVariants = imageVariants;
|
||||||
|
|
||||||
|
factory _$TeaserImageImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$TeaserImageImplFromJson(json);
|
||||||
|
|
||||||
|
final Map<String, String>? _imageVariants;
|
||||||
|
@override
|
||||||
|
Map<String, String>? get imageVariants {
|
||||||
|
final value = _imageVariants;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_imageVariants is EqualUnmodifiableMapView) return _imageVariants;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'TeaserImage(imageVariants: $imageVariants)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$TeaserImageImpl &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other._imageVariants, _imageVariants));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType, const DeepCollectionEquality().hash(_imageVariants));
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$TeaserImageImplCopyWith<_$TeaserImageImpl> get copyWith =>
|
||||||
|
__$$TeaserImageImplCopyWithImpl<_$TeaserImageImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$TeaserImageImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _TeaserImage implements TeaserImage {
|
||||||
|
const factory _TeaserImage({final Map<String, String>? imageVariants}) =
|
||||||
|
_$TeaserImageImpl;
|
||||||
|
|
||||||
|
factory _TeaserImage.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$TeaserImageImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, String>? get imageVariants;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$TeaserImageImplCopyWith<_$TeaserImageImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
19
lib/features/news_api/data/models/teaser_image.g.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'teaser_image.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$TeaserImageImpl _$$TeaserImageImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TeaserImageImpl(
|
||||||
|
imageVariants: (json['imageVariants'] as Map<String, dynamic>?)?.map(
|
||||||
|
(k, e) => MapEntry(k, e as String),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$TeaserImageImplToJson(_$TeaserImageImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'imageVariants': instance.imageVariants,
|
||||||
|
};
|
62
lib/features/news_api/data/models/teaser_image_url.dart
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'teaser_image_url.freezed.dart';
|
||||||
|
part 'teaser_image_url.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class TeaserImageUrl with _$TeaserImageUrl {
|
||||||
|
const factory TeaserImageUrl({
|
||||||
|
String? imageurl,
|
||||||
|
}) = _TeaserImageUrl;
|
||||||
|
|
||||||
|
factory TeaserImageUrl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TeaserImageUrlFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
// old manual implementation:
|
||||||
|
|
||||||
|
// class TeaserImageUrl {
|
||||||
|
// final String imageurl;
|
||||||
|
|
||||||
|
// TeaserImageUrl({
|
||||||
|
// required this.imageurl,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// TeaserImageUrl copyWith({
|
||||||
|
// String? imageurl,
|
||||||
|
// }) {
|
||||||
|
// return TeaserImageUrl(
|
||||||
|
// imageurl: imageurl ?? this.imageurl,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Map<String, dynamic> toMap() {
|
||||||
|
// return {
|
||||||
|
// 'imageurl': imageurl,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// factory TeaserImageUrl.fromMap(Map<String, dynamic> map) {
|
||||||
|
// return TeaserImageUrl(
|
||||||
|
// imageurl: map['imageurl'] ?? '',
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
// factory TeaserImageUrl.fromJson(String source) =>
|
||||||
|
// TeaserImageUrl.fromMap(json.decode(source));
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// String toString() => 'TeaserImageUrl(imageurl: $imageurl)';
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// bool operator ==(Object other) {
|
||||||
|
// if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
// return other is TeaserImageUrl && other.imageurl == imageurl;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// int get hashCode => imageurl.hashCode;
|
||||||
|
// }
|
154
lib/features/news_api/data/models/teaser_image_url.freezed.dart
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'teaser_image_url.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
TeaserImageUrl _$TeaserImageUrlFromJson(Map<String, dynamic> json) {
|
||||||
|
return _TeaserImageUrl.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$TeaserImageUrl {
|
||||||
|
String? get imageurl => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$TeaserImageUrlCopyWith<TeaserImageUrl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $TeaserImageUrlCopyWith<$Res> {
|
||||||
|
factory $TeaserImageUrlCopyWith(
|
||||||
|
TeaserImageUrl value, $Res Function(TeaserImageUrl) then) =
|
||||||
|
_$TeaserImageUrlCopyWithImpl<$Res, TeaserImageUrl>;
|
||||||
|
@useResult
|
||||||
|
$Res call({String? imageurl});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$TeaserImageUrlCopyWithImpl<$Res, $Val extends TeaserImageUrl>
|
||||||
|
implements $TeaserImageUrlCopyWith<$Res> {
|
||||||
|
_$TeaserImageUrlCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? imageurl = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
imageurl: freezed == imageurl
|
||||||
|
? _value.imageurl
|
||||||
|
: imageurl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$TeaserImageUrlImplCopyWith<$Res>
|
||||||
|
implements $TeaserImageUrlCopyWith<$Res> {
|
||||||
|
factory _$$TeaserImageUrlImplCopyWith(_$TeaserImageUrlImpl value,
|
||||||
|
$Res Function(_$TeaserImageUrlImpl) then) =
|
||||||
|
__$$TeaserImageUrlImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({String? imageurl});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$TeaserImageUrlImplCopyWithImpl<$Res>
|
||||||
|
extends _$TeaserImageUrlCopyWithImpl<$Res, _$TeaserImageUrlImpl>
|
||||||
|
implements _$$TeaserImageUrlImplCopyWith<$Res> {
|
||||||
|
__$$TeaserImageUrlImplCopyWithImpl(
|
||||||
|
_$TeaserImageUrlImpl _value, $Res Function(_$TeaserImageUrlImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? imageurl = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$TeaserImageUrlImpl(
|
||||||
|
imageurl: freezed == imageurl
|
||||||
|
? _value.imageurl
|
||||||
|
: imageurl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$TeaserImageUrlImpl implements _TeaserImageUrl {
|
||||||
|
const _$TeaserImageUrlImpl({this.imageurl});
|
||||||
|
|
||||||
|
factory _$TeaserImageUrlImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$TeaserImageUrlImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? imageurl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'TeaserImageUrl(imageurl: $imageurl)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$TeaserImageUrlImpl &&
|
||||||
|
(identical(other.imageurl, imageurl) ||
|
||||||
|
other.imageurl == imageurl));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, imageurl);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$TeaserImageUrlImplCopyWith<_$TeaserImageUrlImpl> get copyWith =>
|
||||||
|
__$$TeaserImageUrlImplCopyWithImpl<_$TeaserImageUrlImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$TeaserImageUrlImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _TeaserImageUrl implements TeaserImageUrl {
|
||||||
|
const factory _TeaserImageUrl({final String? imageurl}) =
|
||||||
|
_$TeaserImageUrlImpl;
|
||||||
|
|
||||||
|
factory _TeaserImageUrl.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$TeaserImageUrlImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get imageurl;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$TeaserImageUrlImplCopyWith<_$TeaserImageUrlImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
18
lib/features/news_api/data/models/teaser_image_url.g.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'teaser_image_url.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$TeaserImageUrlImpl _$$TeaserImageUrlImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TeaserImageUrlImpl(
|
||||||
|
imageurl: json['imageurl'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$TeaserImageUrlImplToJson(
|
||||||
|
_$TeaserImageUrlImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'imageurl': instance.imageurl,
|
||||||
|
};
|