Tugas 12 Flutter - Music Application

Nama     : Helsa Nesta Dhaifullah
NRP       : 5025201005
Kelas      : PPB I - 2024

Di kesempatan ini, kita akan belajar framework baru untuk membuat aplikasi Android, yaitu Flutter. Untuk memulainya, perlu untuk download Flutter SDK dan Visual Studio Code, sebagai kode editorial (IDE). Project pertama untuk latihan Flutter adalah membuat sebuah aplikasi Musik MyArtist, sebuah aplikasi pemutar musik tempat penggemar dapat terus mengikuti kabar terbaru dari artis favoritnya. Kita akan membahas cara memodifikasi desain aplikasi agar terlihat bagus di berbagai platform.
Tampilan Desktop Aplikasi MyArtist

Tampilan Mobile Aplikasi MyArtist

Mendapatkan Kode Awal

Sebagai starter aplikasi yang akan dibangun, kita dapat meng-clone repository Github Codelab dengan menjalankan perintah berikut.
git clone https://github.com/flutter/codelabs.git
cd codelabs/boring_to_beautiful/step_01/
Untuk memastikan aplikasi berfungsi dengan baik, coba jalankan aplikasi, gunakan run & debug di VS Code.

Menerapkan Kode

File pertama yang akan kita modifikasi adalah "lib/src/features/home/view/home_screen.dart". Modifikasi kodenya dengan kode di bawah ini, sehingga file tersebut dapat mengimpor material.dart dan menerapkan widget stateful menggunakan dua class
  • pernyataan import akan menyediakan Komponen Material; 
  • class HomeScreen merepresentasikan seluruh halaman yang ditampilkan; 
  • metode build() class _HomeScreenState akan membuat root hierarki widget, yang memengaruhi cara pembuatan semua widget di UI
import 'package:adaptive_components/adaptive_components.dart';
import 'package:flutter/material.dart';

import '../../../shared/classes/classes.dart';
import '../../../shared/extensions.dart';
import '../../../shared/providers/providers.dart';
import '../../../shared/views/views.dart';
import '../../playlists/view/playlist_songs.dart';
import 'view.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    final PlaylistsProvider playlistProvider = PlaylistsProvider();
    final List<Playlist> playlists = playlistProvider.playlists;
    final Playlist topSongs = playlistProvider.topSongs;
    final Playlist newReleases = playlistProvider.newReleases;
    final ArtistsProvider artistsProvider = ArtistsProvider();
    final List<Artist> artists = artistsProvider.artists;
    return LayoutBuilder(
      builder: (context, constraints) {
        // Add conditional mobile layout

        return Scaffold(
          body: SingleChildScrollView(
            child: AdaptiveColumn(
              children: [
                AdaptiveContainer(
                  columnSpan: 12,
                  child: Padding(
                    padding: const EdgeInsets.all(2), // Modify this line
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Expanded(
                          child: Text(
                            'Good morning',
                            style: context.displaySmall,
                          ),
                        ),
                        const SizedBox(width: 20),
                        const BrightnessToggle(),
                      ],
                    ),
                  ),
                ),
                AdaptiveContainer(
                  columnSpan: 12,
                  child: Column(
                    children: [
                      const HomeHighlight(),
                      LayoutBuilder(
                        builder: (context, constraints) => HomeArtists(
                          artists: artists,
                          constraints: constraints,
                        ),
                      ),
                    ],
                  ),
                ),
                AdaptiveContainer(
                  columnSpan: 12,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Padding(
                        padding: const EdgeInsets.all(2), // Modify this line
                        child: Text(
                          'Recently played',
                          style: context.headlineSmall,
                        ),
                      ),
                      HomeRecent(
                        playlists: playlists,
                      ),
                    ],
                  ),
                ),
                AdaptiveContainer(
                  columnSpan: 12,
                  child: Padding(
                    padding: const EdgeInsets.all(2), // Modify this line
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Flexible(
                          flex: 10,
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.start,
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Padding(
                                padding:
                                    const EdgeInsets.all(2), // Modify this line
                                child: Text(
                                  'Top Songs Today',
                                  style: context.titleLarge,
                                ),
                              ),
                              LayoutBuilder(
                                builder: (context, constraints) =>
                                    PlaylistSongs(
                                  playlist: topSongs,
                                  constraints: constraints,
                                ),
                              ),
                            ],
                          ),
                        ),
                        // Add spacer between tables
                        Flexible(
                          flex: 10,
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.start,
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Padding(
                                padding:
                                    const EdgeInsets.all(2), // Modify this line
                                child: Text(
                                  'New Releases',
                                  style: context.titleLarge,
                                ),
                              ),
                              LayoutBuilder(
                                builder: (context, constraints) =>
                                    PlaylistSongs(
                                  playlist: newReleases,
                                  constraints: constraints,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

Modifikasi Tipografi

Teks digunakan di banyak tempat dan penting untuk komunikasi dengan pengguna. Apakah aplikasi kita ingin terlihat ramah dan menyenangkan atau tepercaya dan profesional? Tampilan teks membentuk kesan pertama pengguna tentang aplikasi kita.

  1. Mulai dengan menambahkan isyarat visual sehingga pengguna dapat melihat sekilas ikon utama dengan cepat untuk menemukan tab yang diinginkan. Buka file "lib/src/shared/router.dart" tambahkan ikon utama yang berbeda untuk setiap tujuan navigasi (beranda, playlist, dan artis):
    const List<NavigationDestination> destinations = [
      NavigationDestination(
        label: 'Home',
        icon: Icon(Icons.home), // Modify this line
        route: '/',
      ),
      NavigationDestination(
        label: 'Playlists',
        icon: Icon(Icons.playlist_add_check), // Modify this line
        route: '/playlists',
      ),
      NavigationDestination(
        label: 'Artists',
        icon: Icon(Icons.people), // Modify this line
        route: '/artists',
      ),
    ];
    Hasil modifikasi dari kode diatas akan seperti di bawah ini.
    Hasil Penambahan Ikon pada Tab Navigasi Bar

  2. Pilah jenis font dengan cermat. Setiap font mempunyai karakteristik masing-masing. Untuk aplikasi musik, sebaiknya gunakan jenis font sans-serif, seperti Montserrat, karena aplikasi musik ditujukan untuk memberikan kesan informal dan menyenangkan.

    Untuk menambahkan font Google ke aplikasi, jalankan perintah berikut di terminal VS Code. Tindakan ini juga memperbarui file pubspec untuk menambahkan font sebagai dependensi aplikasi.
    $ flutter pub add google_fonts
    Import paket tersebut ke dalam file "lib/src/shared/extensions.dart" dan tetapkan font Montserrat di TextTheme
    import 'package:google_fonts/google_fonts.dart';
    ...
    TextTheme get textTheme => GoogleFonts.montserratTextTheme(theme.textTheme);
    Lakukan hot reload pada  7f9a9e103c7b5e5.png  untuk mengaktifkan perubahan. (Gunakan tombol di IDE Anda atau, dari command line, masukkan r untuk melakukan hot reload). Kalian akan melihat ikon NavigationRail baru beserta teks yang ditampilkan dalam font Montserrat.

Mengganti Tema Aplikasi

Tema membantu memberikan desain yang konsisten pada aplikasi dengan menetapkan kumpulan warna dan gaya teks. Dengan tema, Anda bisa menerapkan UI dengan cepat tanpa harus khawatir tentang detail kecil seperti memilih warna yang tepat untuk setiap elemen.
  1. Untuk menggunakan penyedia tema, buat instance dan teruskan ke objek tema cakupan di MaterialApp, yang terletak di "lib/src/shared/app.dart". Tema akan diwarisi oleh objek Theme bertingkat:
    import 'package:dynamic_color/dynamic_color.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_bloc/flutter_bloc.dart';
    
    import 'playback/bloc/bloc.dart';
    import 'providers/theme.dart';
    import 'router.dart';
    
    class MyApp extends StatefulWidget {
     const MyApp({Key? key}) : super(key: key);
    
     @override
     State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
     final settings = ValueNotifier(ThemeSettings(
       sourceColor:  Colors.pink,
       themeMode: ThemeMode.system,
     ));
     @override
     Widget build(BuildContext context) {
       return BlocProvider<PlaybackBloc>(
         create: (context) => PlaybackBloc(),
         child: DynamicColorBuilder(
           builder: (lightDynamic, darkDynamic) => ThemeProvider(
               lightDynamic: lightDynamic,
               darkDynamic: darkDynamic,
               settings: settings,
               child: NotificationListener<ThemeSettingChange>(
                 onNotification: (notification) {
                   settings.value = notification.settings;
                   return true;
                 },
                 child: ValueListenableBuilder<ThemeSettings>(
                   valueListenable: settings,
                   builder: (context, value, _) {
                     final theme = ThemeProvider.of(context); // Add this line
                     return MaterialApp.router(
                       debugShowCheckedModeBanner: false,
                       title: 'Flutter Demo',
                       theme: theme.light(settings.value.sourceColor), // Add this line
                       routeInformationParser: appRouter.routeInformationParser,
                       routerDelegate: appRouter.routerDelegate,
                     );
                   },
                 ),
               )),
         ),
       );
     }
    }
  2. Untuk memilih warna aplikasi, buka Material Theme Builder dan coba berbagai warna untuk UI aplikasi Anda. Pastikan untuk memilih warna yang sesuai dengan estetika merek dan/atau preferensi pribadi Anda.
  3. Teruskan nilai hex warna primer ke penyedia tema. Misalnya, warna heksadesimal #00cbe6 ditentukan sebagai Color(0xff00cbe6). ThemeProvider akan menghasilkan ThemeData yang berisi kumpulan warna pelengkap yang Anda lihat di Builder Tema Material:
    final settings = ValueNotifier(ThemeSettings(
       sourceColor:  Color(0xff00cbe6), // Replace this color
       themeMode: ThemeMode.system,
     ));
    Lakukan hot restart pada aplikasi. Aplikasi akan mulai terlihat lebih ekspresif dengan warna primer. Akses semua warna baru dengan mereferensikan tema dalam konteks dan mengambil ColorScheme:
    final colors = Theme.of(context).colorScheme;
  4. Untuk menggunakan warna tertentu, akses peran warna di colorScheme. Buka "lib/src/shared/views/outlined_card.dart" dan beri OutlinedCard batas tepi:
    class _OutlinedCardState extends State<OutlinedCard> {
      @override
      Widget build(BuildContext context) {
        return MouseRegion(
          cursor: widget.clickable
              ? SystemMouseCursors.click
              : SystemMouseCursors.basic,
          child: Container(
            child: widget.child,
            // Add from here...
            decoration: BoxDecoration(
              border: Border.all(
                color: Theme.of(context).colorScheme.outline,
                width: 1,
              ),
            ),
            // ... To here.
          ),
        );
      }
    }
  5. Pengguna dapat menyesuaikan kecerahan aplikasi di setelan sistem perangkat. Di "lib/src/shared/app.dart", jika perangkat disetel ke mode gelap, tampilkan tema gelap dan mode tema ke MaterialApp.
    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: theme.light(settings.value.sourceColor),
      darkTheme: theme.dark(settings.value.sourceColor), // Add this line
      themeMode: theme.themeMode(), // Add this line
      routeInformationParser: appRouter.routeInformationParser,
      routerDelegate: appRouter.routerDelegate,
    );

Menambahkan Desain Adaptif

Dengan Flutter, Anda bisa membuat aplikasi yang bekerja di berbagai perangkat. Namun, aplikasi tidak selalu berperilaku sama di semua layar. Pengguna mengharapkan fitur yang berbeda di setiap platform. Oleh karena itu, untuk membuat aplikasi musik menjadi aplikasi multi-platform, Anda perlu memodifikasi aplikasi musik agar lebih adaptif.
  1. File "lib/src/shared/views/adaptive_navigation.dart" memiliki kelas navigasi untuk daftar tujuan dan konten. Tata letak dasar ini digunakan di berbagai layar. Kolom samping navigasi cocok untuk desktop dan layar besar, sementara di perangkat seluler, gunakan menu navigasi bawah.
    import 'package:flutter/material.dart';
    
    class AdaptiveNavigation extends StatelessWidget {
      const AdaptiveNavigation({
        Key? key,
        required this.destinations,
        required this.selectedIndex,
        required this.onDestinationSelected,
        required this.child,
      }) : super(key: key);
    
      final List<NavigationDestination> destinations;
      final int selectedIndex;
      final void Function(int index) onDestinationSelected;
      final Widget child;
    
      @override
      Widget build(BuildContext context) {
        return LayoutBuilder(
          builder: (context, dimens) {
            // Tablet Layout
            if (dimens.maxWidth >= 600) { // Add this line
              return Scaffold(
                body: Row(
                  children: [
                    NavigationRail(
                      extended: dimens.maxWidth >= 800,
                      minExtendedWidth: 180,
                      destinations: destinations
                          .map((e) => NavigationRailDestination(
                                icon: e.icon,
                                label: Text(e.label),
                              ))
                          .toList(),
                      selectedIndex: selectedIndex,
                      onDestinationSelected: onDestinationSelected,
                    ),
                    Expanded(child: child),
                  ],
                ),
              );
            } // Add this line
    
            // Mobile Layout
            // Add from here...
            return Scaffold(
              body: child,
              bottomNavigationBar: NavigationBar(
                destinations: destinations,
                selectedIndex: selectedIndex,
                onDestinationSelected: onDestinationSelected,
              ),
            );
            // ... To here.
          },
        );
      }
    }
  2. Agar aplikasi Anda responsif, gunakan titik henti adaptif untuk mengubah tata letak sesuai ukuran layar. Layar kecil tidak bisa menampilkan konten layar besar tanpa mengecilkannya, jadi buat tata letak khusus seluler dengan tab untuk membagi konten. Mulailah dengan metode ekstensi di "lib/src/shared/extensions.dart" dalam project MyArtist untuk desain yang dioptimalkan bagi berbagai perangkat.
    extension BreakpointUtils on BoxConstraints {
      bool get isTablet => maxWidth > 730;
      bool get isDesktop => maxWidth > 1200;
      bool get isMobile => !isTablet && !isDesktop;
    }
  3. Tata letak adaptif memerlukan dua tata letak: satu untuk perangkat seluler dan tata letak responsif untuk layar yang lebih besar. LayoutBuilder saat ini hanya menampilkan tata letak desktop. Di "lib/src/features/home/view/home_screen.dart", buat tata letak seluler sebagai TabBar dan TabBarView dengan 4 tab.
    import 'package:adaptive_components/adaptive_components.dart';
    import 'package:flutter/material.dart';
    
    import '../../../shared/classes/classes.dart';
    import '../../../shared/extensions.dart';
    import '../../../shared/providers/providers.dart';
    import '../../../shared/views/views.dart';
    import '../../playlists/view/playlist_songs.dart';
    import 'view.dart';
    
    class HomeScreen extends StatefulWidget {
     const HomeScreen({Key? key}) : super(key: key);
    
     @override
     State<HomeScreen> createState() => _HomeScreenState();
    }
    
    class _HomeScreenState extends State<HomeScreen> {
     @override
     Widget build(BuildContext context) {
       final PlaylistsProvider playlistProvider = PlaylistsProvider();
       final List<Playlist> playlists = playlistProvider.playlists;
       final Playlist topSongs = playlistProvider.topSongs;
       final Playlist newReleases = playlistProvider.newReleases;
       final ArtistsProvider artistsProvider = ArtistsProvider();
       final List<Artist> artists = artistsProvider.artists;
       return LayoutBuilder(
         builder: (context, constraints) {
           // Add from here...
           if (constraints.isMobile) {
              return DefaultTabController(
                length: 4,
                child: Scaffold(
                  appBar: AppBar(
                    centerTitle: false,
                    title: const Text('Good morning'),
                    actions: const [BrightnessToggle()],
                    bottom: const TabBar(
                      isScrollable: true,
                      tabs: [
                        Tab(text: 'Home'),
                        Tab(text: 'Recently Played'),
                        Tab(text: 'New Releases'),
                        Tab(text: 'Top Songs'),
                      ],
                    ),
                  ),
                  body: LayoutBuilder(
                    builder: (context, constraints) => TabBarView(
                      children: [
                        SingleChildScrollView(
                          child: Column(
                            children: [
                              const HomeHighlight(),
                              HomeArtists(
                                artists: artists,
                                constraints: constraints,
                              ),
                            ],
                          ),
                        ),
                        HomeRecent(
                          playlists: playlists,
                          axis: Axis.vertical,
                        ),
                        PlaylistSongs(
                          playlist: topSongs,
                          constraints: constraints,
                        ),
                        PlaylistSongs(
                          playlist: newReleases,
                          constraints: constraints,
                        ),
                      ],
                    ),
                  ),
                ),
              );
            }
           // ... To here.
    
           return Scaffold(
              body: SingleChildScrollView(
                child: AdaptiveColumn(
                  children: [
                    AdaptiveContainer(
                      columnSpan: 12,
                      child: Padding(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 20,
                          vertical: 40,
                        ),
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: [
                            Expanded(
                              child: Text(
                                'Good morning',
                                style: context.displaySmall,
                              ),
                            ),
                            const SizedBox(width: 20),
                            const BrightnessToggle(),
                          ],
                        ),
                      ),
                    ),
                    AdaptiveContainer(
                      columnSpan: 12,
                      child: Column(
                        children: [
                          const HomeHighlight(),
                          LayoutBuilder(
                            builder: (context, constraints) => HomeArtists(
                              artists: artists,
                              constraints: constraints,
                            ),
                          ),
                        ],
                      ),
                    ),
                    AdaptiveContainer(
                      columnSpan: 12,
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Padding(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 15,
                              vertical: 20,
                            ),
                            child: Text(
                              'Recently played',
                              style: context.headlineSmall,
                            ),
                          ),
                          HomeRecent(
                            playlists: playlists,
                          ),
                        ],
                      ),
                    ),
                    AdaptiveContainer(
                      columnSpan: 12,
                      child: Padding(
                        padding: const EdgeInsets.all(15),
                        child: Row(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Flexible(
                              flex: 10,
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.start,
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Padding(
                                    padding:
                                        const EdgeInsets.only(left: 8, bottom: 8),
                                    child: Text(
                                      'Top Songs Today',
                                      style: context.titleLarge,
                                    ),
                                  ),
                                  LayoutBuilder(
                                    builder: (context, constraints) =>
                                        PlaylistSongs(
                                      playlist: topSongs,
                                      constraints: constraints,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                            const SizedBox(width: 25),
                            Flexible(
                              flex: 10,
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.start,
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Padding(
                                    padding:
                                        const EdgeInsets.only(left: 8, bottom: 8),
                                    child: Text(
                                      'New Releases',
                                      style: context.titleLarge,
                                    ),
                                  ),
                                  LayoutBuilder(
                                    builder: (context, constraints) =>
                                        PlaylistSongs(
                                      playlist: newReleases,
                                      constraints: constraints,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            );
         },
       );
     }
    }
    Tampilan Home Screen dengan 4 Tab Hasil Modifikasi

  4. Atur jarak konten di "lib/src/features/home/view/home_screen.dart" sehingga lebih banyak ruang dan nyaman dilihat pengguna.
    Scaffold(
      body: SingleChildScrollView(
        child: AdaptiveColumn(
          children: [
            AdaptiveContainer(
               columnSpan: 12,
                 child: Padding(
                   padding: const EdgeInsets.fromLTRB(20, 25, 20, 10), // Modify this line
                   child: Row(
                     mainAxisAlignment: MainAxisAlignment.spaceBetween,
                       children: [
                         Expanded(
                           child: Text(
                             'Good morning',
                              style: context.displaySmall,
                           ),
                         ),
                         const SizedBox(width: 20),
                         const BrightnessToggle(),
                       ],
                     ),
                   ),
                 ),
                 AdaptiveContainer(
                   columnSpan: 12,
                   child: Column(
                     children: [
                       const HomeHighlight(),
                       LayoutBuilder(
                         builder: (context, constraints) => HomeArtists(
                           artists: artists,
                           constraints: constraints,
                         ),
                       ),
                     ],
                   ),
                 ),
                 AdaptiveContainer(
                   columnSpan: 12,
                   child: Column(
                     crossAxisAlignment: CrossAxisAlignment.start,
                     children: [
                       Padding(
                         padding: const EdgeInsets.symmetric(
                           horizontal: 15,
                           vertical: 10,
                         ), // Modify this line
                         child: Text(
                           'Recently played',
                           style: context.headlineSmall,
                         ),
                       ),
                       HomeRecent(
                         playlists: playlists,
                       ),
                     ],
                   ),
                 ),
                 AdaptiveContainer(
                   columnSpan: 12,
                   child: Padding(
                     padding: const EdgeInsets.all(15), // Modify this line
                     child: Row(
                       crossAxisAlignment: CrossAxisAlignment.start,
                       children: [
                         Flexible(
                           flex: 10,
                             child: Column(
                               mainAxisAlignment: MainAxisAlignment.start,
                               crossAxisAlignment: CrossAxisAlignment.start,
                               children: [
                                 Padding(
                                   padding: const EdgeInsets.only(left: 8, bottom: 8), // Modify this line
                                   child: Text(
                                     'Top Songs Today',
                                     style: context.titleLarge,
                                   ),
                                 ),
                                 LayoutBuilder(
                                   builder: (context, constraints) =>
                                        PlaylistSongs(
                                      playlist: topSongs,
                                      constraints: constraints,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                        const SizedBox(width: 25),
                        Flexible(
                          flex: 10,
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.start,
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Padding(
                                padding: const EdgeInsets.only(left: 8, bottom: 8), // Modify this line
                                child: Text(
                                  'New Releases',
                                   style: context.titleLarge,
                                ),
                              ),
                              LayoutBuilder(
                                builder: (context, constraints) =>
                                        PlaylistSongs(
                                      playlist: newReleases,
                                      constraints: constraints,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            );
  5. Atur jarak konten di "lib/src/features/home/view/home_highlight.dart" juga.
    class HomeHighlight extends StatelessWidget {
      const HomeHighlight({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Row(
          children: [
            Expanded(
              child: Padding(
                // Modify this line
                padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 5),
                child: Clickable(
                  child: SizedBox(
                    height: 275,
                    child: ClipRRect(
                      borderRadius: BorderRadius.circular(10),
                      child: Image.asset(
                        'assets/images/news/concert.jpeg',
                        fit: BoxFit.cover,
                      ),
                    ),
                  ),
                  onTap: () => launch('https://docs.flutter.dev'),
                ),
              ),
            ),
          ],
        );
      }
    }
  6. Lakukan hot reload pada aplikasi. Tata letak dan spasinya akan terlihat jauh lebih baik. Untuk sentuhan akhir, tambahkan gerakan dan animasi.
    Tampilan Home Screen Setelah Ada Pengaturan Jarak Antar Konten

Menambahkan gerakan dan animasi

Menganimasikan transisi antar-layar

ThemeProvider menentukan pageTransitionsTheme untuk platform seluler (iOS, Android), sementara untuk pengguna desktop, aplikasi menggunakan masukan dari klik mouse atau trackpad, sehingga animasi transisi tidak diperlukan. Flutter memungkinkan pengaturan animasi transisi layar yang disesuaikan berdasarkan platform yang ditargetkan, diimplementasikan dalam "lib/src/shared/providers/theme.dart".
final pageTransitionsTheme = const PageTransitionsTheme(
  builders: <TargetPlatform, PageTransitionsBuilder>{
    TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
    TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
    TargetPlatform.linux: NoAnimationPageTransitionsBuilder(),
    TargetPlatform.macOS: NoAnimationPageTransitionsBuilder(),
    TargetPlatform.windows: NoAnimationPageTransitionsBuilder(),
  },
);
Kemudian, teruskan PageTransitionsTheme ke tema terang dan gelap pada "lib/src/shared/providers/theme.dart".
ThemeData light([Color? targetColor]) {
  final _colors = colors(Brightness.light, targetColor);
  return ThemeData.light().copyWith(
    pageTransitionsTheme: pageTransitionsTheme, // Add this line
    colorScheme: ColorScheme.fromSeed(
      seedColor: source(targetColor),
      brightness: Brightness.light,
    ),
    appBarTheme: appBarTheme(_colors),
    cardTheme: cardTheme(),
    listTileTheme: listTileTheme(),
    tabBarTheme: tabBarTheme(_colors),
    scaffoldBackgroundColor: _colors.background,
  );
}

ThemeData dark([Color? targetColor]) {
  final _colors = colors(Brightness.dark, targetColor);
  return ThemeData.dark().copyWith(
    pageTransitionsTheme: pageTransitionsTheme, // Add this line
    colorScheme: ColorScheme.fromSeed(
      seedColor: source(targetColor),
      brightness: Brightness.dark,
    ),
    appBarTheme: appBarTheme(_colors),
    cardTheme: cardTheme(),
    listTileTheme: listTileTheme(),
    tabBarTheme: tabBarTheme(_colors),
    scaffoldBackgroundColor: _colors.background,
  );
}
Hasil Penambahan Animasi Transisi Antar Layar

Menambahkan status pengarahan kursor

Salah satu cara untuk menambahkan interaksi ke aplikasi desktop adalah dengan menggunakan pengarahan kursor, di mana widget dapat mengubah status kursor seperti warna, bentuk, atau konten saat kursor mengarah ke widget tersebut. Secara bawaan, dalam class _OutlinedCardState yang digunakan untuk kartu playlist "baru diputar", menggunakan MouseRegion untuk mengubah ikon kursor menjadi pointer saat kursor mengarah ke kartu. Namun, Anda dapat menambahkan lebih banyak elemen visual untuk meningkatkan pengalaman interaksi tersebut.
  1. Buka "lib/src/shared/views/outlined_card.dart" dan ganti kontennya dengan penerapan berikut untuk memperkenalkan status _hovered.
    import 'package:flutter/material.dart';
    
    class OutlinedCard extends StatefulWidget {
      const OutlinedCard({
        Key? key,
        required this.child,
        this.clickable = true,
      }) : super(key: key);
      final Widget child;
      final bool clickable;
      @override
      State<OutlinedCard> createState() => _OutlinedCardState();
    }
    
    class _OutlinedCardState extends State<OutlinedCard> {
      bool _hovered = false;
    
      @override
      Widget build(BuildContext context) {
        final borderRadius = BorderRadius.circular(_hovered ? 20 : 8);
        const animationCurve = Curves.easeInOut;
        return MouseRegion(
          onEnter: (_) {
            if (!widget.clickable) return;
            setState(() {
              _hovered = true;
            });
          },
          onExit: (_) {
            if (!widget.clickable) return;
            setState(() {
              _hovered = false;
            });
          },
          cursor: widget.clickable ? SystemMouseCursors.click : SystemMouseCursors.basic,
          child: AnimatedContainer(
            duration: kThemeAnimationDuration,
            curve: animationCurve,
            decoration: BoxDecoration(
              border: Border.all(
                color: Theme.of(context).colorScheme.outline,
                width: 1,
              ),
              borderRadius: borderRadius,
            ),
            foregroundDecoration: BoxDecoration(
              color: Theme.of(context).colorScheme.onSurface.withOpacity(
                    _hovered ? 0.12 : 0,
                  ),
              borderRadius: borderRadius,
            ),
            child: TweenAnimationBuilder<BorderRadius>(
              duration: kThemeAnimationDuration,
              curve: animationCurve,
              tween: Tween(begin: BorderRadius.zero, end: borderRadius),
              builder: (context, borderRadius, child) => ClipRRect(
                clipBehavior: Clip.antiAlias,
                borderRadius: borderRadius,
                child: child,
              ),
              child: widget.child,
            ),
          ),
        );
      }
    }
  2. Lakukan hot reload pada aplikasi, lalu arahkan kursor ke salah satu kartu playlist baru diputar.

    Animasi pada Outline Card saat Kartu Dihover

  3. Terakhir, animasikan nomor lagu pada playlist menjadi tombol putar menggunakan widget HoverableSongPlayButton yang ditentukan dalam "lib/src/shared/views/hoverable_song_play_button.dart". Di "lib/src/features/playlists/view/playlist_songs.dart", gabungkan widget Center (yang berisi nomor lagu) dengan HoverableSongPlayButton.
    HoverableSongPlayButton(      // Add this line
      hoverMode: HoverMode.overlay, // Add this line
      song: playlist.songs[index],  // Add this line
      child: Center(                // Modify this line
        child: Text(
          (index + 1).toString(),
           textAlign: TextAlign.center,
           ),
        ),
      ),                            // Add this line
  4. Lakukan hot reload pada aplikasi, lalu arahkan kursor ke nomor lagu di playlist Lagu Teratas Hari Ini atau Rilis Baru. Angka akan berubah menjadi tombol putar yang memutar lagu saat Anda mengkliknya.
    Animasi Tombol Putar saat Nomor Lagu Dihover

Mendapatkan Kode Solusi

Untuk mendapatkan kode solusi lengkap dari Latihan Flutter - modifikasi aplikasi MyArtist dapat di-download di link Github berikut.
https://github.com/helsanesta/tugas_flutter-MyArtist_App

Atau dapat langsung ke Github Codelab berikut.

Komentar

Postingan Populer