diff --git a/assets/spotube-logo.png b/assets/spotube-logo.png index 0793df19..4a406c7f 100644 Binary files a/assets/spotube-logo.png and b/assets/spotube-logo.png differ diff --git a/lib/components/Home.dart b/lib/components/Home.dart index a6db845b..0f6cf3e8 100644 --- a/lib/components/Home.dart +++ b/lib/components/Home.dart @@ -1,3 +1,4 @@ +import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart' hide Page; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; @@ -122,6 +123,27 @@ class _HomeState extends State { return Scaffold( body: Column( children: [ + WindowTitleBarBox( + child: Row( + children: [ + Expanded( + child: Row( + children: [ + Container( + constraints: const BoxConstraints(maxWidth: 256), + color: + Theme.of(context).navigationRailTheme.backgroundColor, + child: MoveWindow(), + ), + Expanded(child: MoveWindow()) + ], + )), + MinimizeWindowButton(animate: true), + MaximizeWindowButton(animate: true), + CloseWindowButton(animate: true), + ], + ), + ), Expanded( child: Row( children: [ @@ -165,21 +187,25 @@ class _HomeState extends State { builder: (context, snapshot) { var avatarImg = snapshot.data?.images?.last.url; return Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (avatarImg != null) - CircleAvatar( - child: CachedNetworkImage( - imageUrl: avatarImg, + Row( + children: [ + if (avatarImg != null) + CircleAvatar( + backgroundImage: + CachedNetworkImageProvider(avatarImg), + ), + const SizedBox(width: 10), + Text( + snapshot.data?.displayName ?? "User's name", + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), - ), - Text( - snapshot.data?.displayName ?? "User's name", - style: const TextStyle( - fontWeight: FontWeight.bold, - ), + ], ), IconButton( icon: const Icon(Icons.settings_outlined), @@ -202,12 +228,15 @@ class _HomeState extends State { if (_selectedIndex == 0) Expanded( child: Scrollbar( - child: PagedListView( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return CategoryCard(item); - }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: PagedListView( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + return CategoryCard(item); + }, + ), ), ), ), diff --git a/lib/components/PageWindowTitleBar.dart b/lib/components/PageWindowTitleBar.dart new file mode 100644 index 00000000..861e4aa6 --- /dev/null +++ b/lib/components/PageWindowTitleBar.dart @@ -0,0 +1,23 @@ +import 'package:bitsdojo_window/bitsdojo_window.dart'; +import 'package:flutter/material.dart'; + +class PageWindowTitleBar extends StatelessWidget { + final Widget? leading; + final Widget? center; + const PageWindowTitleBar({Key? key, this.leading, this.center}) + : super(key: key); + @override + Widget build(BuildContext context) { + return WindowTitleBarBox( + child: Row( + children: [ + if (leading != null) leading!, + Expanded(child: MoveWindow(child: Center(child: center))), + MinimizeWindowButton(animate: true), + MaximizeWindowButton(animate: true), + CloseWindowButton(animate: true), + ], + ), + ); + } +} diff --git a/lib/components/PlaylistGenreView.dart b/lib/components/PlaylistGenreView.dart index 7b075f08..61f2facb 100644 --- a/lib/components/PlaylistGenreView.dart +++ b/lib/components/PlaylistGenreView.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/components/PageWindowTitleBar.dart'; import 'package:spotube/components/PlaylistCard.dart'; import 'package:spotube/provider/SpotifyDI.dart'; @@ -24,19 +25,13 @@ class _PlaylistGenreViewState extends State { return Scaffold( body: Column( children: [ - Row( - // mainAxisAlignment: MainAxisAlignment.center, - children: [ - const BackButton(), - // genre name - Expanded( - child: Text( - widget.genreName, - style: Theme.of(context).textTheme.headline4, - textAlign: TextAlign.center, - ), - ), - ], + const PageWindowTitleBar( + leading: BackButton(), + ), + Text( + widget.genreName, + style: Theme.of(context).textTheme.headline4, + textAlign: TextAlign.center, ), Consumer( builder: (context, data, child) => Expanded( diff --git a/lib/components/PlaylistView.dart b/lib/components/PlaylistView.dart index a500872d..a4845064 100644 --- a/lib/components/PlaylistView.dart +++ b/lib/components/PlaylistView.dart @@ -1,6 +1,5 @@ -import 'dart:ui'; - import 'package:cached_network_image/cached_network_image.dart'; +import 'package:spotube/components/PageWindowTitleBar.dart'; import 'package:spotube/helpers/zero-pad-num-str.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:flutter/material.dart'; @@ -98,42 +97,49 @@ class _PlaylistViewState extends State { const TextStyle(fontWeight: FontWeight.bold, fontSize: 16); return Column( children: [ - Row( - children: [ - // nav back - const BackButton(), - // heart playlist - IconButton( - icon: const Icon(Icons.favorite_outline_rounded), - onPressed: () {}, - ), - // play playlist - Consumer(builder: (context, playback, widget) { - var isPlaylistPlaying = playback.currentPlaylist?.id == - this.widget.playlist.id; - return IconButton( - icon: Icon( - isPlaylistPlaying - ? Icons.stop_rounded - : Icons.play_arrow_rounded, - ), - onPressed: snapshot.hasData - ? () { - if (!isPlaylistPlaying) { - playback.setCurrentPlaylist = - CurrentPlaylist( - tracks: tracks, - id: this.widget.playlist.id!, - name: this.widget.playlist.name!, - thumbnail: - this.widget.playlist.images![0].url!, - ); + PageWindowTitleBar( + leading: Row( + children: [ + // nav back + const BackButton(), + // heart playlist + IconButton( + icon: const Icon(Icons.favorite_outline_rounded), + onPressed: () {}, + ), + // play playlist + Consumer( + builder: (context, playback, widget) { + var isPlaylistPlaying = + playback.currentPlaylist?.id == + this.widget.playlist.id; + return IconButton( + icon: Icon( + isPlaylistPlaying + ? Icons.stop_rounded + : Icons.play_arrow_rounded, + ), + onPressed: snapshot.hasData + ? () { + if (!isPlaylistPlaying) { + playback.setCurrentPlaylist = + CurrentPlaylist( + tracks: tracks, + id: this.widget.playlist.id!, + name: this.widget.playlist.name!, + thumbnail: this + .widget + .playlist + .images![0] + .url!, + ); + } } - } - : null, - ); - }), - ], + : null, + ); + }), + ], + ), ), Center( child: Text(widget.playlist.name!, diff --git a/lib/components/Settings.dart b/lib/components/Settings.dart index a36e6267..dfcee303 100644 --- a/lib/components/Settings.dart +++ b/lib/components/Settings.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:spotube/components/PageWindowTitleBar.dart'; import 'package:spotube/provider/Auth.dart'; class Settings extends StatefulWidget { @@ -14,18 +15,16 @@ class _SettingsState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - backgroundColor: Colors.white, - elevation: 0, - iconTheme: Theme.of(context).iconTheme, - title: const Text( - "Settings", - ), - centerTitle: true, - titleTextStyle: Theme.of(context).textTheme.headline4, - ), body: Column( children: [ + PageWindowTitleBar( + leading: const BackButton(), + center: Text( + "Settings", + style: Theme.of(context).textTheme.headline5, + ), + ), + const SizedBox(height: 10), Builder(builder: (context) { var auth = context.read(); return ElevatedButton( diff --git a/lib/main.dart b/lib/main.dart index f3100765..16d71053 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -10,6 +11,12 @@ import 'package:spotube/provider/SpotifyDI.dart'; void main() { runApp(MyApp()); + doWhenWindowReady(() { + appWindow.minSize = const Size(900, 700); + appWindow.alignment = Alignment.center; + appWindow.maximize(); + appWindow.show(); + }); } class MyApp extends StatelessWidget { @@ -57,40 +64,46 @@ class MyApp extends StatelessWidget { debugShowCheckedModeBanner: false, title: 'Spotube', theme: ThemeData( - primaryColor: Colors.greenAccent[400], - primarySwatch: Colors.green, - buttonTheme: const ButtonThemeData( - buttonColor: Colors.green, - ), - textTheme: TextTheme( - bodyText1: TextStyle(color: Colors.grey[850]), - headline1: TextStyle(color: Colors.grey[850]), - headline2: TextStyle(color: Colors.grey[850]), - headline3: TextStyle(color: Colors.grey[850]), - headline4: TextStyle(color: Colors.grey[850]), - headline5: TextStyle(color: Colors.grey[850]), - headline6: TextStyle(color: Colors.grey[850]), - ), - listTileTheme: ListTileThemeData( - iconColor: Colors.grey[850], - horizontalTitleGap: 0, - ), - inputDecorationTheme: InputDecorationTheme( - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.green[400]!, - width: 2.0, - ), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.grey[800]!, - ), + primaryColor: Colors.greenAccent[400], + primarySwatch: Colors.green, + buttonTheme: const ButtonThemeData( + buttonColor: Colors.green, + ), + textTheme: TextTheme( + bodyText1: TextStyle(color: Colors.grey[850]), + headline1: TextStyle(color: Colors.grey[850]), + headline2: TextStyle(color: Colors.grey[850]), + headline3: TextStyle(color: Colors.grey[850]), + headline4: TextStyle(color: Colors.grey[850]), + headline5: TextStyle(color: Colors.grey[850]), + headline6: TextStyle(color: Colors.grey[850]), + ), + listTileTheme: ListTileThemeData( + iconColor: Colors.grey[850], + horizontalTitleGap: 0, + ), + inputDecorationTheme: InputDecorationTheme( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.green[400]!, + width: 2.0, ), ), - navigationRailTheme: NavigationRailThemeData( - backgroundColor: Colors.blueGrey[50], - )), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey[800]!, + ), + ), + ), + navigationRailTheme: NavigationRailThemeData( + backgroundColor: Colors.blueGrey[50], + unselectedIconTheme: + IconThemeData(color: Colors.grey[850], opacity: 1), + unselectedLabelTextStyle: TextStyle( + color: Colors.grey[850], + ), + ), + ), home: const Home(), ), ); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index f6f23bfe..bb3345d3 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin"); + bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 1fc8ed34..1d233e73 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + bitsdojo_window_linux url_launcher_linux ) diff --git a/linux/my_application.cc b/linux/my_application.cc index b48fbad7..e1aa9337 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -1,3 +1,4 @@ +#include #include "my_application.h" #include @@ -47,7 +48,9 @@ static void my_application_activate(GApplication* application) { gtk_window_set_title(window, "spotube"); } - gtk_window_set_default_size(window, 1280, 720); + auto bdw = bitsdojo_window_from(window); + bdw->setCustomFrame(true); + // gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); diff --git a/pubspec.lock b/pubspec.lock index 75f4bca8..9701cd21 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -8,6 +8,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.8.2" + bitsdojo_window: + dependency: "direct main" + description: + name: bitsdojo_window + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+1" + bitsdojo_window_linux: + dependency: transitive + description: + name: bitsdojo_window_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1" + bitsdojo_window_macos: + dependency: transitive + description: + name: bitsdojo_window_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" + bitsdojo_window_platform_interface: + dependency: transitive + description: + name: bitsdojo_window_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" + bitsdojo_window_windows: + dependency: transitive + description: + name: bitsdojo_window_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" boolean_selector: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index bf158dd5..82bdf4b3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,7 @@ dependencies: youtube_explode_dart: ^1.10.8 mpv_dart: ^0.0.1 infinite_scroll_pagination: ^3.1.0 + bitsdojo_window: ^0.1.1+1 dev_dependencies: flutter_test: