canal -> brook

This commit is contained in:
Henry Hiles 2025-01-04 12:14:46 -05:00
parent a2ef7da707
commit a6f2291309
25 changed files with 169 additions and 129 deletions

View file

@ -1 +1 @@
# Canal # Brook

View file

@ -6,7 +6,7 @@ plugins {
} }
android { android {
namespace = "com.example.canal" namespace = "com.example.brook"
compileSdk = flutter.compileSdkVersion compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion ndkVersion = flutter.ndkVersion
@ -21,7 +21,7 @@ android {
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.canal" applicationId = "com.example.brook"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion

View file

@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application <application
android:label="canal" android:label="brook"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity

View file

@ -1,4 +1,4 @@
package com.example.canal package com.example.brook
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity

View file

@ -1,4 +1,4 @@
import 'package:canal/widgets/loading.dart'; import 'package:brook/widgets/loading.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';

View file

@ -1,6 +1,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:canal/widgets/app.dart'; import 'package:brook/widgets/app.dart';
import 'package:window_size/window_size.dart'; import 'package:window_size/window_size.dart';
import 'package:yaru/yaru.dart'; import 'package:yaru/yaru.dart';

View file

@ -1,7 +1,7 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import "package:riverpod_annotation/riverpod_annotation.dart"; import "package:riverpod_annotation/riverpod_annotation.dart";
import 'package:canal/models/decorations.dart'; import 'package:brook/models/decorations.dart';
import 'package:canal/providers/button_layout_provider.dart'; import 'package:brook/providers/button_layout_provider.dart';
import 'package:yaru/yaru.dart'; import 'package:yaru/yaru.dart';
import "package:collection/collection.dart"; import "package:collection/collection.dart";
part 'decorations_provider.g.dart'; part 'decorations_provider.g.dart';

View file

@ -1,4 +1,4 @@
import 'package:canal/providers/ytmusic_provider.dart'; import 'package:brook/providers/ytmusic_provider.dart';
import 'package:dart_ytmusic_api/dart_ytmusic_api.dart'; import 'package:dart_ytmusic_api/dart_ytmusic_api.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';

View file

@ -1,23 +1,27 @@
import 'package:canal/models/search_type.dart'; import 'package:brook/models/search_type.dart';
import 'package:dart_ytmusic_api/types.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:canal/providers/ytmusic_provider.dart'; import 'package:brook/providers/ytmusic_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart';
part "search_provider.g.dart"; part "search_provider.g.dart";
@riverpod @riverpod
Future<IList<dynamic>> searchProvider( Future<IList<SearchResult>> searchProvider(
Ref ref, { Ref ref, {
required String search, required String search,
required SearchType searchType, required SearchType searchType,
}) async { }) async {
final yt = await ytmusic(ref); final yt = await ytmusic(ref);
return IList(switch (searchType) { return IList(
SearchType.any => await yt.search(search), switch (searchType) {
SearchType.songs => await yt.searchSongs(search), SearchType.any =>
SearchType.albums => await yt.searchAlbums(search), await yt.search(search).then((search) => search.cast<SearchResult>()),
SearchType.videos => await yt.searchVideos(search), SearchType.songs => await yt.searchSongs(search),
SearchType.artists => await yt.searchAlbums(search), SearchType.albums => await yt.searchAlbums(search),
SearchType.playlists => await yt.searchPlaylists(search), SearchType.videos => await yt.searchVideos(search),
}); SearchType.artists => await yt.searchAlbums(search),
SearchType.playlists => await yt.searchPlaylists(search),
},
);
} }

View file

@ -1,4 +1,4 @@
import 'package:canal/widgets/appbar.dart'; import 'package:brook/widgets/appbar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AlbumPage extends StatelessWidget { class AlbumPage extends StatelessWidget {

View file

@ -1,4 +1,4 @@
import 'package:canal/widgets/appbar.dart'; import 'package:brook/widgets/appbar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class PlaylistPage extends StatelessWidget { class PlaylistPage extends StatelessWidget {

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:canal/models/tab.dart'; import 'package:brook/models/tab.dart';
class AccountTab extends StatelessWidget implements TabPage { class AccountTab extends StatelessWidget implements TabPage {
const AccountTab({super.key}); const AccountTab({super.key});

View file

@ -1,11 +1,11 @@
import 'package:canal/helpers/extension_helper.dart'; import 'package:brook/helpers/extension_helper.dart';
import 'package:canal/providers/home_sections_provider.dart'; import 'package:brook/providers/home_sections_provider.dart';
import 'package:canal/screens/album_page.dart'; import 'package:brook/screens/album_page.dart';
import 'package:canal/screens/playlist_page.dart'; import 'package:brook/screens/playlist_page.dart';
import 'package:canal/widgets/thumbnail.dart'; import 'package:brook/widgets/thumbnail.dart';
import 'package:dart_ytmusic_api/dart_ytmusic_api.dart'; import 'package:dart_ytmusic_api/dart_ytmusic_api.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:canal/models/tab.dart'; import 'package:brook/models/tab.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:yaru/yaru.dart'; import 'package:yaru/yaru.dart';

View file

@ -1,11 +1,12 @@
import 'package:canal/helpers/extension_helper.dart'; import 'package:brook/helpers/extension_helper.dart';
import 'package:canal/providers/search_provider.dart'; import 'package:collection/collection.dart';
import 'package:canal/widgets/thumbnail.dart'; import 'package:brook/providers/search_provider.dart';
import 'package:brook/widgets/thumbnail.dart';
import 'package:dart_ytmusic_api/types.dart'; import 'package:dart_ytmusic_api/types.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:canal/widgets/select_button.dart'; import 'package:brook/widgets/select_button.dart';
import 'package:canal/models/search_type.dart'; import 'package:brook/models/search_type.dart';
import 'package:canal/models/tab.dart'; import 'package:brook/models/tab.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:yaru/yaru.dart'; import 'package:yaru/yaru.dart';
@ -23,7 +24,6 @@ class SearchTab extends HookConsumerWidget implements TabPage {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final type = useState(SearchType.any); final type = useState(SearchType.any);
final search = useState(""); final search = useState("");
final debouncedSearch = useDebounced(search, Duration(milliseconds: 250));
return ListView( return ListView(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
@ -45,39 +45,65 @@ class SearchTab extends HookConsumerWidget implements TabPage {
SizedBox(height: 8), SizedBox(height: 8),
ref ref
.watch(searchProviderProvider( .watch(searchProviderProvider(
search: search.value, searchType: type.value)) search: search.value,
searchType: type.value,
))
.betterWhen( .betterWhen(
data: (results) => Wrap( data: (results) => Column(
children: results children: results
.map((result) => SizedBox( .mapIndexed((index, result) => switch (result) {
height: 64, SongDetailed _ => Builder(builder: (_) {
width: 64, final padding =
child: switch (result) { EdgeInsets.symmetric(horizontal: 16);
SongDetailed _ => Thumbnail( final leading = Thumbnail(
url: result.thumbnails.first.url, url: result.thumbnails.first.url,
onClick: () {}, onClick: () {},
), );
AlbumDetailed _ => Thumbnail( return index == 0
url: result.thumbnails.first.url, ? SizedBox(
onClick: () {}, height: 128,
), child: YaruBanner.tile(
VideoDetailed _ => Thumbnail( padding: padding,
url: result.thumbnails.first.url, icon: leading,
onClick: () {}, title: Text(result.name),
), subtitle: Text(result.artist.name),
ArtistDetailed _ => Thumbnail( ),
url: result.thumbnails.first.url, )
onClick: () {}, : YaruTile(
), padding: padding,
PlaylistDetailed _ => Thumbnail( leading: leading,
url: result.thumbnails.first.url, title: Text(result.name),
onClick: () {}, subtitle: Text(result.artist.name),
), );
_ => throw Exception( }),
"Unknown Detailed Result: ${result.runtimeType}", AlbumDetailed _ => Thumbnail(
), url: result.thumbnails.first.url,
})) onClick: () {},
.toList())), ),
VideoDetailed _ => Thumbnail(
url: result.thumbnails.first.url,
onClick: () {},
radius: 0,
),
ArtistDetailed _ => Thumbnail(
url: result.thumbnails.first.url,
onClick: () {},
),
PlaylistDetailed _ => Thumbnail(
url: result.thumbnails.first.url,
onClick: () {},
),
_ => throw Exception(
"Unknown Detailed Result: ${result.runtimeType}",
),
})
.map((element) => Padding(
padding: EdgeInsets.only(bottom: 16),
child: element,
))
.toList(),
),
),
], ],
); );
} }

View file

@ -1,18 +1,18 @@
import 'package:adwaita/adwaita.dart'; import 'package:adwaita/adwaita.dart';
import 'package:canal/helpers/extension_helper.dart'; import 'package:brook/helpers/extension_helper.dart';
import 'package:canal/models/tab.dart'; import 'package:brook/models/tab.dart';
import 'package:canal/providers/ytmusic_provider.dart'; import 'package:brook/providers/ytmusic_provider.dart';
import 'package:canal/screens/tabs/account.dart'; import 'package:brook/screens/tabs/account.dart';
import 'package:canal/screens/tabs/home.dart'; import 'package:brook/screens/tabs/home.dart';
import 'package:canal/screens/tabs/search.dart'; import 'package:brook/screens/tabs/search.dart';
import 'package:canal/widgets/appbar.dart'; import 'package:brook/widgets/appbar.dart';
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:yaru/yaru.dart'; import 'package:yaru/yaru.dart';
import 'package:canal/providers/button_layout_provider.dart'; import 'package:brook/providers/button_layout_provider.dart';
import 'package:canal/providers/warmup_provider.dart'; import 'package:brook/providers/warmup_provider.dart';
const List<TabPage> tabs = [HomeTab(), SearchTab(), AccountTab()]; const List<TabPage> tabs = [HomeTab(), SearchTab(), AccountTab()];
@ -31,7 +31,7 @@ class App extends HookConsumerWidget {
]))) ])))
.betterWhen( .betterWhen(
data: (_) => YaruDetailPage( data: (_) => YaruDetailPage(
appBar: const Appbar(title: "Canal"), appBar: const Appbar(title: "Brook"),
body: tabs[selected.value], body: tabs[selected.value],
bottomNavigationBar: NavigationBar( bottomNavigationBar: NavigationBar(
destinations: tabs destinations: tabs

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:canal/providers/decorations_provider.dart'; import 'package:brook/providers/decorations_provider.dart';
import 'package:yaru/yaru.dart'; import 'package:yaru/yaru.dart';
class Appbar extends ConsumerWidget implements PreferredSizeWidget { class Appbar extends ConsumerWidget implements PreferredSizeWidget {

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:canal/models/package.dart'; import 'package:brook/models/package.dart';
class PackageCard extends StatelessWidget { class PackageCard extends StatelessWidget {
final Package package; final Package package;

View file

@ -1,4 +1,4 @@
import 'package:canal/helpers/extension_helper.dart'; import 'package:brook/helpers/extension_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';

View file

@ -3,11 +3,17 @@ import 'package:flutter/material.dart';
class Thumbnail extends StatelessWidget { class Thumbnail extends StatelessWidget {
final String url; final String url;
final VoidCallback onClick; final VoidCallback onClick;
const Thumbnail({required this.url, required this.onClick, super.key}); final double radius;
const Thumbnail({
super.key,
required this.url,
required this.onClick,
this.radius = 16,
});
@override @override
Widget build(BuildContext context) => ClipRRect( Widget build(BuildContext context) => ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(16)), borderRadius: BorderRadius.all(Radius.circular(radius)),
child: InkWell( child: InkWell(
onTap: onClick, onTap: onClick,
child: Image.network( child: Image.network(

View file

@ -4,10 +4,10 @@ project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change # The name of the executable created for the application. Change this to change
# the on-disk name of your application. # the on-disk name of your application.
set(BINARY_NAME "canal") set(BINARY_NAME "brook")
# The unique GTK application identifier for this application. See: # The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID # https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.canal") set(APPLICATION_ID "com.example.brook")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake. # versions of CMake.

View file

@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) {
if (use_header_bar) { if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar)); gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "canal"); gtk_header_bar_set_title(header_bar, "brook");
gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else { } else {
gtk_window_set_title(window, "canal"); gtk_window_set_title(window, "brook");
} }
gtk_window_set_default_size(window, 1280, 720); gtk_window_set_default_size(window, 1280, 720);

View file

@ -202,10 +202,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: collection name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.19.1"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -281,10 +281,11 @@ packages:
dart_ytmusic_api: dart_ytmusic_api:
dependency: "direct main" dependency: "direct main"
description: description:
name: dart_ytmusic_api path: "."
sha256: "1e35f07c69a36eab672f37815bc0f644328c06cda51404e30fa90a0412023135" ref: HEAD
url: "https://pub.dev" resolved-ref: "2f3c6f7d385cf10827780e03bfaf133f3b8221a3"
source: hosted url: "https://github.com/Henry-Hiles/dart_ytmusic_api"
source: git
version: "1.0.8" version: "1.0.8"
dbus: dbus:
dependency: transitive dependency: transitive
@ -477,10 +478,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.1.2"
image: image:
dependency: transitive dependency: transitive
description: description:
@ -490,13 +491,13 @@ packages:
source: hosted source: hosted
version: "4.5.2" version: "4.5.2"
intl: intl:
dependency: transitive dependency: "direct overridden"
description: description:
name: intl name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf sha256: "00f33b908655e606b86d2ade4710a231b802eec6f11e87e4ea3783fd72077a50"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.19.0" version: "0.20.1"
io: io:
dependency: transitive dependency: transitive
description: description:
@ -570,13 +571,13 @@ packages:
source: hosted source: hosted
version: "0.11.1" version: "0.11.1"
meta: meta:
dependency: "direct overridden" dependency: transitive
description: description:
name: meta name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.16.0" version: "1.15.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -765,10 +766,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shelf name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.4.2"
shelf_web_socket: shelf_web_socket:
dependency: transitive dependency: transitive
description: description:

View file

@ -1,4 +1,4 @@
name: canal name: brook
description: "A music player" description: "A music player"
publish_to: "none" publish_to: "none"
version: 1.0.0+1 version: 1.0.0+1
@ -7,8 +7,8 @@ environment:
sdk: "^3.5.4" sdk: "^3.5.4"
dependencies: dependencies:
collection: ^1.19.1
adwaita: ^1.1.0 adwaita: ^1.1.0
collection: ^1.18.0
fast_immutable_collections: ^11.0.2 fast_immutable_collections: ^11.0.2
flutter: flutter:
sdk: flutter sdk: flutter
@ -25,10 +25,13 @@ dependencies:
git: git:
url: https://github.com/google/flutter-desktop-embedding url: https://github.com/google/flutter-desktop-embedding
path: plugins/window_size path: plugins/window_size
dart_ytmusic_api: ^1.0.8 dart_ytmusic_api:
git:
url: https://github.com/Henry-Hiles/dart_ytmusic_api
dependency_overrides: dependency_overrides:
meta: ^1.15.0 collection: ^1.19.1
intl: ^0.20.1
dev_dependencies: dev_dependencies:
build_runner: ^2.4.11 build_runner: ^2.4.11

View file

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<!-- <!--
If you are serving your web app in a path other than the root, change the If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from. href value below to reflect the base path you are serving from.
@ -14,25 +14,25 @@
This is a placeholder for base href that will be replaced by the value of This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`. the `--base-href` argument provided to `flutter build`.
--> -->
<base href="$FLUTTER_BASE_HREF"> <base href="$FLUTTER_BASE_HREF" />
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta name="description" content="A new Flutter project."> <meta name="description" content="A new Flutter project." />
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="canal"> <meta name="apple-mobile-web-app-title" content="brook" />
<link rel="apple-touch-icon" href="icons/Icon-192.png"> <link rel="apple-touch-icon" href="icons/Icon-192.png" />
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png" />
<title>canal</title> <title>brook</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json" />
</head> </head>
<body> <body>
<script src="flutter_bootstrap.js" async></script> <script src="flutter_bootstrap.js" async></script>
</body> </body>
</html> </html>

View file

@ -1,6 +1,6 @@
{ {
"name": "canal", "name": "brook",
"short_name": "canal", "short_name": "brook",
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"background_color": "#0175C2", "background_color": "#0175C2",