summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2023-04-23 17:24:03 -0600
committerIván Ávalos <avalos@disroot.org>2023-04-23 20:53:58 -0600
commite6699735a68763787a74941b3402007738683c03 (patch)
treecd59324e108a298a1a657c30a73582f718a379bf
parent80bad7a1b1c31fb7a28d74796adcf44ee319f586 (diff)
downloadpmsna1-e6699735a68763787a74941b3402007738683c03.tar.gz
pmsna1-e6699735a68763787a74941b3402007738683c03.tar.bz2
pmsna1-e6699735a68763787a74941b3402007738683c03.zip
Initial WIP for practice 6
-rw-r--r--lib/models/album.dart37
-rw-r--r--lib/models/artist.dart28
-rw-r--r--lib/network/music_api.dart32
-rw-r--r--lib/routes.dart4
-rw-r--r--lib/screens/album_detail_screen.dart130
-rw-r--r--lib/screens/albums_screen.dart70
-rw-r--r--lib/screens/dashboard_screen.dart7
-rw-r--r--lib/widgets/album_item.dart72
-rw-r--r--lib/widgets/album_placeholder.dart21
-rw-r--r--pubspec.lock8
-rw-r--r--pubspec.yaml1
11 files changed, 410 insertions, 0 deletions
diff --git a/lib/models/album.dart b/lib/models/album.dart
new file mode 100644
index 0000000..a7a0f91
--- /dev/null
+++ b/lib/models/album.dart
@@ -0,0 +1,37 @@
+class Album {
+ final String title;
+ final String firstReleaseDate;
+ final List<dynamic>? secondaryTypes;
+ final String primaryTypeId;
+ final String id;
+ final String primaryType;
+ final List<dynamic>? secondaryTypeIds;
+ final String disambiguation;
+
+ Uri get coverUri =>
+ Uri.parse('https://coverartarchive.org/release-group/$id/front');
+
+ const Album({
+ required this.title,
+ required this.firstReleaseDate,
+ required this.secondaryTypes,
+ required this.primaryTypeId,
+ required this.id,
+ required this.primaryType,
+ required this.secondaryTypeIds,
+ required this.disambiguation,
+ });
+
+ factory Album.fromMap(Map<String, dynamic> map) {
+ return Album(
+ title: map["title"],
+ firstReleaseDate: map["first-release-date"],
+ secondaryTypes: map["secondary-types"],
+ primaryTypeId: map["primary-type-id"],
+ id: map["id"],
+ primaryType: map["primary-type"],
+ secondaryTypeIds: map["secondary-types-id"],
+ disambiguation: map["disambiguation"],
+ );
+ }
+}
diff --git a/lib/models/artist.dart b/lib/models/artist.dart
new file mode 100644
index 0000000..70c17db
--- /dev/null
+++ b/lib/models/artist.dart
@@ -0,0 +1,28 @@
+class Artist {
+ final String type;
+ final String name;
+ final String typeId;
+ final String sortName;
+ final String disambiguation;
+ final String id;
+
+ const Artist({
+ required this.type,
+ required this.name,
+ required this.typeId,
+ required this.sortName,
+ required this.disambiguation,
+ required this.id,
+ });
+
+ factory Artist.fromMap(Map<String, dynamic> map) {
+ return Artist(
+ type: map["type"],
+ name: map["name"],
+ typeId: map["type-id"],
+ sortName: map["sort-name"],
+ disambiguation: map["disambiguation"],
+ id: map["id"],
+ );
+ }
+}
diff --git a/lib/network/music_api.dart b/lib/network/music_api.dart
new file mode 100644
index 0000000..490c51d
--- /dev/null
+++ b/lib/network/music_api.dart
@@ -0,0 +1,32 @@
+import 'dart:convert';
+
+import 'package:http/http.dart' as http;
+import 'package:pmsna1/models/album.dart';
+import 'package:pmsna1/models/artist.dart';
+
+class MusicApi {
+ Uri artistUri(String artistId) => Uri.parse(
+ 'https://musicbrainz.org/ws/2/artist/$artistId?inc=release-groups&fmt=json');
+
+ Uri releaseGroupUri(String releaseGroupId) => Uri.parse(
+ 'https://musicbrainz.org/ws/2/release-group/$releaseGroupId?inc=artists&fmt=json');
+
+ Future<List<Album>?> getAlvvaysAlbums() async {
+ String id = '99450990-b24e-4132-bb68-235f8c3e2564';
+ http.Response result = await http.get(artistUri(id));
+ var list = jsonDecode(result.body)['release-groups'] as List;
+ if (result.statusCode != 200) {
+ return null;
+ }
+ return list.map((map) => Album.fromMap(map)).toList();
+ }
+
+ Future<List<Artist>?> getAlbumArtists(String id) async {
+ http.Response result = await http.get(releaseGroupUri(id));
+ var list = jsonDecode(result.body)['artist-credit'] as List;
+ if (result.statusCode != 200) {
+ return null;
+ }
+ return list.map((map) => Artist.fromMap(map["artist"])).toList();
+ }
+}
diff --git a/lib/routes.dart b/lib/routes.dart
index 766273b..4259727 100644
--- a/lib/routes.dart
+++ b/lib/routes.dart
@@ -1,4 +1,6 @@
import 'package:flutter/material.dart';
+import 'package:pmsna1/screens/album_detail_screen.dart';
+import 'package:pmsna1/screens/albums_screen.dart';
import 'package:pmsna1/screens/dashboard_screen.dart';
import 'package:pmsna1/screens/events_screen.dart';
import 'package:pmsna1/screens/new_event_screen.dart';
@@ -19,5 +21,7 @@ Map<String, WidgetBuilder> getApplicationRoutes() {
'/events': (BuildContext context) => const EventsScreen(),
'/popular': (BuildContext context) => const PopularScreen(),
'/newevent': (BuildContext context) => const NewEventScreen(),
+ '/albums': (BuildContext context) => const AlbumsScreen(),
+ '/album': (BuildContext context) => const AlbumDetailScreen(),
};
}
diff --git a/lib/screens/album_detail_screen.dart b/lib/screens/album_detail_screen.dart
new file mode 100644
index 0000000..478c0b6
--- /dev/null
+++ b/lib/screens/album_detail_screen.dart
@@ -0,0 +1,130 @@
+import 'package:flutter/material.dart';
+import 'package:pmsna1/models/album.dart';
+import 'package:pmsna1/models/artist.dart';
+import 'package:pmsna1/network/music_api.dart';
+import 'package:pmsna1/widgets/album_placeholder.dart';
+import 'package:pmsna1/widgets/responsive.dart';
+
+class AlbumDetailScreen extends StatefulWidget {
+ const AlbumDetailScreen({super.key});
+
+ @override
+ State<AlbumDetailScreen> createState() => _AlbumDetailScreenState();
+}
+
+class _AlbumDetailScreenState extends State<AlbumDetailScreen> {
+ MusicApi? api;
+ Album? album;
+
+ @override
+ void initState() {
+ super.initState();
+ api = MusicApi();
+ }
+
+ Widget getArtistCard(Artist artist, BuildContext context) => Card(
+ margin: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 2.0),
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ artist.name,
+ style: Theme.of(context)
+ .typography
+ .englishLike
+ .bodyLarge
+ ?.copyWith(color: Theme.of(context).colorScheme.onSurface),
+ ),
+ const SizedBox(height: 4.0),
+ Text(artist.disambiguation),
+ ],
+ ),
+ ),
+ );
+
+ // Widget getLeft() => Image.network(album!.coverUri.toString());
+ Widget getLeft() => const SizedBox.shrink();
+
+ Widget getRight() => Column(
+ children: [
+ ListTile(
+ title: const Text('Fecha de lanzamiento'),
+ subtitle: Text(album!.firstReleaseDate),
+ ),
+ ListTile(
+ title: const Text('Tipo'),
+ subtitle: Text(album!.primaryType),
+ ),
+ ListTile(
+ title: const Text('Artistas'),
+ subtitle: FutureBuilder(
+ future: api!.getAlbumArtists(album!.id),
+ initialData: const [],
+ builder: (context, snapshot) => snapshot.hasData
+ ? ListView.builder(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: snapshot.data?.length,
+ itemBuilder: (context, index) {
+ Artist artist = snapshot.data![index];
+ return getArtistCard(artist, context);
+ })
+ : snapshot.hasError
+ ? const Text('Ocurrió un error')
+ : const CircularProgressIndicator(),
+ ),
+ )
+ ],
+ );
+
+ @override
+ Widget build(BuildContext context) {
+ album = ModalRoute.of(context)?.settings.arguments as Album?;
+ if (album == null) return const Placeholder();
+
+ Image cover = Image.network(
+ album!.coverUri.toString(),
+ fit: BoxFit.cover,
+ alignment: Alignment.center,
+ errorBuilder: (context, error, stackTrace) => const AlbumPlaceholder(400),
+ loadingBuilder: (context, child, loadingProgress) =>
+ loadingProgress == null ? child : const AlbumPlaceholder(400),
+ );
+
+ return Scaffold(
+ body: NestedScrollView(
+ headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) =>
+ <Widget>[
+ SliverAppBar(
+ expandedHeight: 400.0,
+ pinned: true,
+ flexibleSpace: FlexibleSpaceBar(
+ title: Text(album!.title,
+ style: TextStyle(
+ backgroundColor:
+ Theme.of(context).colorScheme.primaryContainer,
+ color: Theme.of(context).colorScheme.onPrimaryContainer,
+ )),
+ background: cover,
+ ),
+ )
+ ],
+ body: SingleChildScrollView(
+ child: Responsive(
+ mobile: Column(children: [getLeft(), getRight()]),
+ tablet: Row(children: [
+ Expanded(child: getLeft()),
+ Expanded(child: getRight()),
+ ]),
+ desktop: Row(children: [
+ Expanded(child: getLeft()),
+ Expanded(child: getRight()),
+ ]),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screens/albums_screen.dart b/lib/screens/albums_screen.dart
new file mode 100644
index 0000000..d376e35
--- /dev/null
+++ b/lib/screens/albums_screen.dart
@@ -0,0 +1,70 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
+import 'package:pmsna1/models/album.dart';
+import 'package:pmsna1/network/music_api.dart';
+import 'package:pmsna1/widgets/album_item.dart';
+import 'package:pmsna1/widgets/responsive.dart';
+
+import '../widgets/loading_modal_widget.dart';
+
+class AlbumsScreen extends StatefulWidget {
+ const AlbumsScreen({super.key});
+
+ @override
+ State<AlbumsScreen> createState() => _AlbumsScreenState();
+}
+
+class _AlbumsScreenState extends State<AlbumsScreen> {
+ MusicApi? musicApi;
+
+ @override
+ void initState() {
+ super.initState();
+ musicApi = MusicApi();
+ }
+
+ Widget getGridForCount(
+ int count,
+ int itemCount,
+ Widget Function(BuildContext, int) itemBuilder,
+ ) {
+ return MasonryGridView.builder(
+ gridDelegate: SliverSimpleGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: count,
+ ),
+ itemCount: itemCount,
+ itemBuilder: itemBuilder,
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: const Text('Música (Alvvays)')),
+ body: FutureBuilder(
+ future: musicApi!.getAlvvaysAlbums(),
+ builder: (context, AsyncSnapshot<List<Album>?> snapshot) {
+ int itemCount = snapshot.data != null ? snapshot.data!.length : 0;
+ itemBuilder(BuildContext context, int index) {
+ return AlbumItem(snapshot.data![index]);
+ }
+
+ if (snapshot.hasData) {
+ return Responsive(
+ mobile: getGridForCount(2, itemCount, itemBuilder),
+ tablet: getGridForCount(3, itemCount, itemBuilder),
+ desktop: getGridForCount(4, itemCount, itemBuilder),
+ );
+ } else if (snapshot.hasError) {
+ print(snapshot.error);
+ return const Center(
+ child: Text('Ocurrió un error'),
+ );
+ } else {
+ return const LoadingModal();
+ }
+ },
+ ),
+ );
+ }
+}
diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart
index 03b84e7..914749c 100644
--- a/lib/screens/dashboard_screen.dart
+++ b/lib/screens/dashboard_screen.dart
@@ -52,6 +52,13 @@ class DashboardScreen extends StatelessWidget {
},
),
ListTile(
+ title: const Text('Música'),
+ leading: const Icon(Icons.music_note),
+ onTap: () {
+ Navigator.of(context).pushNamed('/albums');
+ },
+ ),
+ ListTile(
title: const Text('Tema'),
trailing: SegmentedButton<ThemeData?>(
segments: [
diff --git a/lib/widgets/album_item.dart b/lib/widgets/album_item.dart
new file mode 100644
index 0000000..8794c24
--- /dev/null
+++ b/lib/widgets/album_item.dart
@@ -0,0 +1,72 @@
+import 'package:flutter/material.dart';
+import 'package:pmsna1/models/album.dart';
+import 'package:pmsna1/widgets/album_placeholder.dart';
+
+class AlbumItem extends StatelessWidget {
+ final Album album;
+
+ const AlbumItem(this.album, {super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ Color textColor = Theme.of(context).colorScheme.onSurface;
+ return Card(
+ margin: const EdgeInsets.all(5.0),
+ semanticContainer: true,
+ clipBehavior: Clip.antiAliasWithSaveLayer,
+ child: InkWell(
+ onTap: () {
+ Navigator.of(context).pushNamed('/album', arguments: album);
+ },
+ child: Column(
+ children: [
+ FadeInImage(
+ placeholder: const AssetImage('assets/loading.gif'),
+ imageErrorBuilder: (context, error, stackTrace) =>
+ const AspectRatio(
+ aspectRatio: 1.0,
+ child: AlbumPlaceholder(400),
+ ),
+ image: NetworkImage(album.coverUri.toString()),
+ fit: BoxFit.fill,
+ ),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Column(
+ children: [
+ Text(
+ album.title,
+ style: Theme.of(context)
+ .typography
+ .englishLike
+ .labelLarge
+ ?.copyWith(color: textColor),
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 4.0),
+ Text(
+ album.firstReleaseDate,
+ style: Theme.of(context)
+ .typography
+ .englishLike
+ .labelMedium
+ ?.copyWith(color: textColor),
+ ),
+ const SizedBox(height: 4.0),
+ Text(
+ "(${album.primaryType})",
+ style: Theme.of(context)
+ .typography
+ .englishLike
+ .labelSmall
+ ?.copyWith(color: textColor),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/album_placeholder.dart b/lib/widgets/album_placeholder.dart
new file mode 100644
index 0000000..420c2e9
--- /dev/null
+++ b/lib/widgets/album_placeholder.dart
@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+
+class AlbumPlaceholder extends StatelessWidget {
+ final double size;
+
+ const AlbumPlaceholder(this.size, {super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ height: size,
+ width: size,
+ color: Theme.of(context).colorScheme.secondaryContainer,
+ child: Icon(
+ Icons.album,
+ size: 60.0,
+ color: Theme.of(context).colorScheme.onSecondaryContainer,
+ ),
+ );
+ }
+}
diff --git a/pubspec.lock b/pubspec.lock
index f6a708d..2f6c37c 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -206,6 +206,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.8"
+ flutter_staggered_grid_view:
+ dependency: "direct main"
+ description:
+ name: flutter_staggered_grid_view
+ sha256: "1312314293acceb65b92754298754801b0e1f26a1845833b740b30415bbbcf07"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.2"
flutter_svg:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 5cfe56b..a189e04 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -24,6 +24,7 @@ dependencies:
firebase_core: ^2.8.0
firebase_auth: ^4.3.0
table_calendar: <=3.0.9
+ flutter_staggered_grid_view: ^0.6.2
dev_dependencies:
flutter_test: