Add simple desktop app

This commit is contained in:
Elias Projahn 2020-07-18 11:58:03 +02:00
parent 5312bad52d
commit 4bdd1873c4
17 changed files with 540 additions and 1 deletions

View file

@ -18,7 +18,7 @@ developed using the [Aqueduct framework](https://aqueduct.io).
`client` A client library for the Musicus server.
`common` Common building blocks for Musicus client apps. This includes shared
UI and backend code for the mobile app and the (future) desktop app.
UI and backend code for the mobile and desktop app.
`mobile` The Musicus mobile app. It is being developed using
[Flutter toolkit](https://flutter.dev) and only runs on Android for now.
@ -26,6 +26,9 @@ UI and backend code for the mobile app and the (future) desktop app.
`player` The simplest possible audio player plugin. This is used by the
mobile app for playback.
`desktop` A simple Musicus desktop app. At the moment this is basically the
mobile app with some adaptions in the background.
## Hacking
Picking up Dart as a programming language and Flutter as an UI toolkit should

45
desktop/.gitignore vendored Normal file
View file

@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# 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
pubspec.lock
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

10
desktop/.metadata Normal file
View file

@ -0,0 +1,10 @@
# 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: f25bd9c55c48c139524139b477d04b13e9f36b2c
channel: dev
project_type: app

22
desktop/lib/main.dart Normal file
View file

@ -0,0 +1,22 @@
import 'package:flutter/widgets.dart';
import 'package:musicus_common/musicus_common.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart' as pp;
import 'settings.dart';
import 'platform.dart';
import 'playback.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final dir = await pp.getApplicationSupportDirectory();
final dbPath = p.join(dir.path, 'db.sqlite');
runApp(MusicusApp(
dbPath: dbPath,
settingsStorage: SettingsStorage(),
platform: MusicusDesktopPlatform(),
playback: MusicusDesktopPlayback(),
));
}

66
desktop/lib/platform.dart Normal file
View file

@ -0,0 +1,66 @@
import 'dart:io';
import 'package:file_chooser/file_chooser.dart';
import 'package:musicus_common/musicus_common.dart';
import 'package:path/path.dart' as p;
class MusicusDesktopPlatform extends MusicusPlatform {
@override
Future<String> chooseBasePath() async {
final result = await showOpenPanel(
canSelectDirectories: true,
);
if (result != null && !result.canceled) {
return result.paths.first;
} else {
return null;
}
}
@override
Future<List<Document>> getChildren(String parentId) async {
final List<Document> result = [];
final parent = Directory(parentId ?? basePath);
await for (final fse in parent.list()) {
result.add(Document(
id: fse.path,
name: p.basename(fse.path),
parent: parentId,
isDirectory: fse is Directory,
));
}
return result;
}
@override
Future<String> getIdentifier(String parentId, String fileName) async {
return p.absolute(parentId, fileName);
}
@override
Future<String> readDocument(String id) async {
try {
return await File(id).readAsString();
} on FileSystemException {
return null;
}
}
@override
Future<String> readDocumentByName(String parentId, String fileName) async {
try {
return await File(p.absolute(parentId, fileName)).readAsString();
} on FileSystemException {
return null;
}
}
@override
Future<void> writeDocumentByName(
String parentId, String fileName, String contents) async {
await File(p.absolute(parentId, fileName)).writeAsString(contents);
}
}

43
desktop/lib/playback.dart Normal file
View file

@ -0,0 +1,43 @@
import 'package:musicus_common/musicus_common.dart';
class MusicusDesktopPlayback extends MusicusPlayback {
@override
Future<void> addTracks(List<InternalTrack> tracks) async {
// TODO: implement addTracks
}
@override
Future<void> playPause() async {
// TODO: implement playPause
}
@override
Future<void> removeTrack(int index) async {
// TODO: implement removeTrack
}
@override
Future<void> seekTo(double pos) async {
// TODO: implement seekTo
}
@override
Future<void> setup() async {
// TODO: implement setup
}
@override
Future<void> skipTo(int index) async {
// TODO: implement skipTo
}
@override
Future<void> skipToNext() async {
// TODO: implement skipToNext
}
@override
Future<void> skipToPrevious() async {
// TODO: implement skipToPrevious
}
}

30
desktop/lib/settings.dart Normal file
View file

@ -0,0 +1,30 @@
import 'package:musicus_common/musicus_common.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SettingsStorage extends MusicusSettingsStorage {
SharedPreferences _pref;
Future<void> load() async {
_pref = await SharedPreferences.getInstance();
}
@override
Future<int> getInt(String key) {
return Future.value(_pref.getInt(key));
}
@override
Future<String> getString(String key) {
return Future.value(_pref.getString(key));
}
@override
Future<void> setInt(String key, int value) async {
await _pref.setInt(key, value);
}
@override
Future<void> setString(String key, String value) async {
await _pref.setString(key, value);
}
}

1
desktop/linux/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
flutter/ephemeral

View file

@ -0,0 +1,95 @@
cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX)
set(BINARY_NAME "musicus_desktop")
cmake_policy(SET CMP0063 NEW)
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Configure build options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
# Flutter library and tool build rules.
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# Application build
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
apply_standard_settings(${BINARY_NAME})
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
add_dependencies(${BINARY_NAME} flutter_assemble)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

View file

@ -0,0 +1,86 @@
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
linux-x64 ${CMAKE_BUILD_TYPE}
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)

View file

@ -0,0 +1,17 @@
//
// Generated file. Do not edit.
//
#include "generated_plugin_registrant.h"
#include <file_chooser/file_chooser_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_chooser_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileChooserPlugin");
file_chooser_plugin_register_with_registrar(file_chooser_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);
}

View file

@ -0,0 +1,13 @@
//
// Generated file. Do not edit.
//
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View file

@ -0,0 +1,17 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
file_chooser
url_launcher_linux
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)

10
desktop/linux/main.cc Normal file
View file

@ -0,0 +1,10 @@
#include "my_application.h"
int main(int argc, char** argv) {
// Only X11 is currently supported.
// Wayland support is being developed: https://github.com/flutter/flutter/issues/57932.
gdk_set_allowed_backends("x11");
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}

View file

@ -0,0 +1,40 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
gtk_window_set_title(window, "Musicus");
gtk_window_set_default_size(window, 640, 453);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(), nullptr));
}

View file

@ -0,0 +1,18 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_

23
desktop/pubspec.yaml Normal file
View file

@ -0,0 +1,23 @@
name: musicus_desktop
version: 0.1.0
description: Desktop version of the classical music player and organizer.
environment:
sdk: ">=2.3.0 <3.0.0"
dependencies:
file_chooser:
git:
url: git://github.com/google/flutter-desktop-embedding.git
path: plugins/file_chooser
ref: bd722fbdb6d491d4b7781c240543ed3b3b181063
flutter:
sdk: flutter
musicus_common:
path: ../common
path:
path_provider:
shared_preferences:
flutter:
uses-material-design: true