diff --git a/babe.cpp b/babe.cpp index 0b880ff..8de659b 100644 --- a/babe.cpp +++ b/babe.cpp @@ -1,794 +1,733 @@ #include "babe.h" #include "db/collectionDB.h" #include "db/conthread.h" #include "settings/BabeSettings.h" #include "pulpo/pulpo.h" #include "utils/babeconsole.h" #include #include #include #include #include #include #include #include //#include "Python.h" #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) #include #include "kde/notify.h" #endif #if defined(Q_OS_ANDROID) #include "android/notificationclient.h" #include "android/android.h" #include #include #include #include #include #include class InterfaceConnFailedException : public QException { public: void raise() const { throw *this; } InterfaceConnFailedException *clone() const { return new InterfaceConnFailedException(*this); } }; #elif defined(Q_OS_WINDOWS) #elif defined(Q_OS_DARWIN) #else #endif using namespace BAE; Babe::Babe(QObject *parent) : CollectionDB(parent) { bDebug::Instance()->msg("CONSTRUCTING ABE INTERFACE"); this->settings = new BabeSettings(this); this->thread = new ConThread; connect(bDebug::Instance(), SIGNAL(debug(QString)), this, SLOT(debug(QString))); connect(settings, &BabeSettings::refreshTables, [this](int size) { emit this->refreshTables(size); }); connect(settings, &BabeSettings::refreshATable, [this](BAE::TABLE table) { switch(table) { case BAE::TABLE::TRACKS: emit this->refreshTracks(); break; case BAE::TABLE::ALBUMS: emit this->refreshAlbums(); break; case BAE::TABLE::ARTISTS: emit this->refreshArtists(); break; } }); connect(&link, &Linking::parseAsk, this, &Babe::linkDecoder); connect(&link, &Linking::bytesFrame, [this](QByteArray array) { this->player.appendBuffe(array); }); connect(&link, &Linking::arrayReady, [this](QByteArray array) { qDebug()<<"trying to play the array"; this->player.playBuffer(); }); #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) this->nof = new Notify(this); connect(this->nof,&Notify::babeSong,[this]() { emit this->babeIt(); }); connect(this->nof,&Notify::skipSong,[this]() { emit this->skipTrack(); }); #elif defined (Q_OS_ANDROID) this->nof = new NotificationClient(this); this->android = new Android(this); connect(android, &Android::folderPicked, [this](const QString &url) { qDebug()<< "Folder picked"<< url; }); #endif } Babe::~Babe() { delete this->thread; } //void Babe::runPy() //{ // QFile cat (BAE::CollectionDBPath+"cat"); // qDebug()<thread->start(table, wheres); } void Babe::trackPlaylist(const QStringList &urls, const QString &playlist) { QVariantList data; for(auto url : urls) { QVariantMap map {{KEYMAP[KEY::PLAYLIST],playlist}, {KEYMAP[KEY::URL],url}, {KEYMAP[KEY::ADD_DATE],QDateTime::currentDateTime()}}; data << map; } bDebug::Instance()->msg("Adding "+QString::number(urls.size())+" tracks to playlist : "+playlist); this->thread->start(BAE::TABLEMAP[TABLE::TRACKS_PLAYLISTS], data); } void Babe::trackLyrics(const QString &url) { auto track = getDBData(QString("SELECT * FROM %1 WHERE %2 = \"%3\"").arg(TABLEMAP[TABLE::TRACKS], KEYMAP[KEY::URL], url)); if(track.isEmpty()) return; if(!track.first()[KEY::LYRICS].isEmpty() && track.first()[KEY::LYRICS] != SLANG[W::NONE]) emit this->trackLyricsReady(track.first()[KEY::LYRICS], url); else this->fetchTrackLyrics(track.first()); } bool Babe::trackBabe(const QString &path) { auto babe = getDBData(QString("SELECT %1 FROM %2 WHERE %3 = \"%4\"").arg(KEYMAP[KEY::BABE], TABLEMAP[TABLE::TRACKS], KEYMAP[KEY::URL],path)); if(!babe.isEmpty()) return babe.first()[KEY::BABE].toInt(); return false; } QString Babe::artistArt(const QString &artist) { auto artwork = getDBData(QString("SELECT %1 FROM %2 WHERE %3 = \"%4\"").arg(KEYMAP[KEY::ARTWORK], TABLEMAP[TABLE::ARTISTS], KEYMAP[KEY::ARTIST],artist)); if(!artwork.isEmpty()) if(!artwork.first()[KEY::ARTWORK].isEmpty() && artwork.first()[KEY::ARTWORK] != SLANG[W::NONE]) return artwork.first()[KEY::ARTWORK]; return ""; } QString Babe::artistWiki(const QString &artist) { auto wiki = getDBData(QString("SELECT %1 FROM %2 WHERE %3 = \"%4\"").arg(KEYMAP[KEY::WIKI], TABLEMAP[TABLE::ARTISTS], KEYMAP[KEY::ARTIST],artist)); if(!wiki.isEmpty()) return wiki.first()[KEY::WIKI]; return ""; } QString Babe::albumArt(const QString &album, const QString &artist) { auto queryStr = QString("SELECT %1 FROM %2 WHERE %3 = \"%4\" AND %5 = \"%6\"").arg(KEYMAP[KEY::ARTWORK], TABLEMAP[TABLE::ALBUMS], KEYMAP[KEY::ALBUM],album, KEYMAP[KEY::ARTIST],artist); auto albumCover = getDBData(queryStr); if(!albumCover.isEmpty()) if(!albumCover.first()[KEY::ARTWORK].isEmpty() && albumCover.first()[KEY::ARTWORK] != SLANG[W::NONE]) return albumCover.first()[KEY::ARTWORK]; return ""; } void Babe::fetchTrackLyrics(DB &song) { Pulpo pulpo; pulpo.registerServices({SERVICES::LyricWikia}); pulpo.setOntology(PULPO::ONTOLOGY::TRACK); pulpo.setInfo(PULPO::INFO::LYRICS); connect(&pulpo, &Pulpo::infoReady, [this](const BAE::DB &track, const PULPO::RESPONSE &res) { if(!res[PULPO::ONTOLOGY::TRACK][PULPO::INFO::LYRICS].isEmpty()) { auto lyrics = res[PULPO::ONTOLOGY::TRACK][PULPO::INFO::LYRICS][PULPO::CONTEXT::LYRIC].toString(); lyricsTrack(track, lyrics); bDebug::Instance()->msg("Downloaded the lyrics for "+track[KEY::TITLE]+" "+track[KEY::ARTIST]); emit this->trackLyricsReady(lyrics, track[KEY::URL]); } }); pulpo.feed(song, PULPO::RECURSIVE::OFF); } void Babe::linkDecoder(QString json) { qDebug()<<"DECODING LINKER MSG"<(code)) { case LINK::CODE::CONNECTED : { this->link.deviceName = msg; emit this->link.serverConReady(msg); break; } case LINK::CODE::QUERY : case LINK::CODE::FILTER : case LINK::CODE::PLAYLISTS : { auto res = this->getDBDataQML(msg); link.sendToClient(link.packResponse(static_cast(code), res)); break; } case LINK::CODE::SEARCHFOR : { auto res = this->searchFor(msg.split(",")); link.sendToClient(link.packResponse(static_cast(code), res)); break; } case LINK::CODE::PLAY : { QFile file(msg); // sound dir file.open(QIODevice::ReadOnly); QByteArray arr = file.readAll(); qDebug()<<"Preparing track array"<nof->notify(title, body); #elif defined (Q_OS_ANDROID) this->nof->notify(title+" "+body); #endif } void Babe::notifySong(const QString &url) { #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) auto query = QString("select t.*, al.artwork from tracks t inner join albums al on al.album = t.album and al.artist = t.artist where url = \"%1\"").arg(url); auto track = getDBData(query); this->nof->notifySong(track.first()); #else Q_UNUSED(url); #endif } void Babe::sendText(const QString &text) { #if defined(Q_OS_ANDROID) QAndroidJniEnvironment _env; QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); //activity is valid if (_env->ExceptionCheck()) { _env->ExceptionClear(); throw InterfaceConnFailedException(); } if ( activity.isValid() ) { QAndroidJniObject::callStaticMethod("com/example/android/tools/SendIntent", "sendText", "(Landroid/app/Activity;Ljava/lang/String;)V", activity.object(), QAndroidJniObject::fromString(text).object()); if (_env->ExceptionCheck()) { _env->ExceptionClear(); throw InterfaceConnFailedException(); } }else throw InterfaceConnFailedException(); #endif } void Babe::sendTrack(const QString &url) { #if defined(Q_OS_ANDROID) bDebug::Instance()->msg("Sharing track "+ url); QAndroidJniEnvironment _env; QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); //activity is valid if (_env->ExceptionCheck()) { _env->ExceptionClear(); throw InterfaceConnFailedException(); } if ( activity.isValid() ) { QAndroidJniObject::callStaticMethod("com/example/android/tools/SendIntent", "sendTrack", "(Landroid/app/Activity;Ljava/lang/String;)V", activity.object(), QAndroidJniObject::fromString(url).object()); if (_env->ExceptionCheck()) { _env->ExceptionClear(); throw InterfaceConnFailedException(); } }else throw InterfaceConnFailedException(); #endif } void Babe::openFile(const QString &url) { #if defined(Q_OS_ANDROID) bDebug::Instance()->msg("Opening track "+ url); QAndroidJniEnvironment _env; QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); //activity is valid if (_env->ExceptionCheck()) { _env->ExceptionClear(); throw InterfaceConnFailedException(); } if ( activity.isValid() ) { QAndroidJniObject::callStaticMethod("com/example/android/tools/SendIntent", "openFile", "(Landroid/app/Activity;Ljava/lang/String;)V", activity.object(), QAndroidJniObject::fromString(url).object()); if (_env->ExceptionCheck()) { _env->ExceptionClear(); throw InterfaceConnFailedException(); } }else throw InterfaceConnFailedException(); #endif } void Babe::fileChooser() { #if defined(Q_OS_ANDROID) this->android->fileChooser(); #endif } void Babe::scanDir(const QString &url) { emit this->settings->collectionPathChanged({url}); } void Babe::brainz(const bool &on) { this->settings->checkCollectionBrainz(on); } bool Babe::brainzState() { return loadSetting("BRAINZ", "BABE", false).toBool(); } void Babe::refreshCollection() { this->settings->refreshCollection(); } void Babe::getYoutubeTrack(const QString &message) { this->settings->fetchYoutubeTrack(message); } QVariant Babe::loadSetting(const QString &key, const QString &group, const QVariant &defaultValue) { return BAE::loadSettings(key, group, defaultValue); } void Babe::saveSetting(const QString &key, const QVariant &value, const QString &group) { bDebug::Instance()->msg("Setting saved: "+ key+" "+value.toString()+" "+group); BAE::saveSettings(key, value, group); } void Babe::savePlaylist(const QStringList &list) { BAE::saveSettings("PLAYLIST", list, "MAINWINDOW"); } QStringList Babe::lastPlaylist() { return BAE::loadSettings("PLAYLIST","MAINWINDOW",{}).toStringList(); } void Babe::savePlaylistPos(const int &pos) { BAE::saveSettings("PLAYLIST_POS", pos, "MAINWINDOW"); } int Babe::lastPlaylistPos() { return BAE::loadSettings("PLAYLIST_POS","MAINWINDOW",QVariant(0)).toInt(); } bool Babe::fileExists(const QString &url) { return BAE::fileExists(url); } void Babe::showFolder(const QString &url) { QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(url).dir().absolutePath())); } QString Babe::babeColor() { return "#f84172"; } void Babe::androidStatusBarColor(const QString &color, const bool &contrast) { #if defined(Q_OS_ANDROID) QtAndroid::runOnAndroidThread([=]() { QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;"); window.callMethod("addFlags", "(I)V", 0x80000000); window.callMethod("clearFlags", "(I)V", 0x04000000); window.callMethod("setStatusBarColor", "(I)V", QColor(color).rgba()); if(contrast) { QAndroidJniObject decorView = window.callObjectMethod("getDecorView", "()Landroid/view/View;"); decorView.callMethod("setSystemUiVisibility", "(I)V", 0x00002000); } }); #endif } bool Babe::isMobile() { return BAE::isMobile(); } bool Babe::isAndroid() { return BAE::isAndroid(); } QString Babe::moodColor(const int &pos) { if(pos < BAE::MoodColors.size()) return BAE::MoodColors.at(pos); else return ""; } QString Babe::homeDir() { #if defined(Q_OS_ANDROID) QAndroidJniObject mediaDir = QAndroidJniObject::callStaticObjectMethod("android/os/Environment", "getExternalStorageDirectory", "()Ljava/io/File;"); QAndroidJniObject mediaPath = mediaDir.callObjectMethod( "getAbsolutePath", "()Ljava/lang/String;" ); // bDebug::Instance()->msg("HOMEDIR FROM ADNROID"+ mediaPath.toString()); if(BAE::fileExists("/mnt/extSdCard")) return "/mnt/sdcard"; else return mediaPath.toString(); // QAndroidJniObject mediaDir = QAndroidJniObject::callStaticObjectMethod("android.content.Context", "getExternalFilesDir", "()Ljava/io/File;"); // QAndroidJniObject mediaPath = mediaDir.callObjectMethod( "getAbsolutePath", "()Ljava/lang/String;" ); // return mediaPath.toString(); #else return BAE::HomePath; #endif } QString Babe::musicDir() { return BAE::MusicPath; } -QString Babe::sdDir() -{ -#if defined(Q_OS_ANDROID) - // QAndroidJniObject mediaDir = QAndroidJniObject::callStaticObjectMethod("android/os/Environment", "getExternalStorageDirectory", "()Ljava/io/File;"); - // QAndroidJniObject mediaPath = mediaDir.callObjectMethod( "getAbsolutePath", "()Ljava/lang/String;" ); - // QString dataAbsPath = mediaPath.toString()+"/Download/"; - // QAndroidJniEnvironment env; - // if (env->ExceptionCheck()) { - // // Handle exception here. - // env->ExceptionClear(); - // } - - // qbDebug::Instance()->msg()<<"TESTED SDPATH"<msg("Get directories for path: "+path); - QVariantList paths; - - if (QFileInfo(path).isDir()) - { - QDirIterator it(path, QDir::Dirs, QDirIterator::NoIteratorFlags); - while (it.hasNext()) - { - auto url = it.next(); - auto name = QDir(url).dirName(); - QVariantMap map = { {"url", url }, {"name", name} }; - paths << map; - } - } - - return paths; -} - -QVariantMap Babe::getParentDir(const QString &path) -{ - auto dir = QDir(path); - dir.cdUp(); - auto dirPath = dir.absolutePath(); - - if(dir.isReadable() && !dir.isRoot() && dir.exists()) - return {{"url", dirPath}, {"name", dir.dirName()}}; - else - return {{"url", path}, {"name", QFileInfo(path).dir().dirName()}}; -} - QStringList Babe::defaultSources() { return BAE::defaultSources; } void Babe::registerTypes() { qmlRegisterUncreatableType("Babe", 1, 0, "Babe", "ERROR ABE"); } QString Babe::loadCover(const QString &url) { auto map = getDBData(QStringList() << url); if(map.isEmpty()) return ""; auto track = map.first(); auto artist = track[KEY::ARTIST]; auto album = track[KEY::ALBUM]; auto title = track[KEY::TITLE]; auto artistImg = this->artistArt(artist); auto albumImg = this->albumArt(album, artist); if(!albumImg.isEmpty() && albumImg != BAE::SLANG[W::NONE]) return albumImg; else if (!artistImg.isEmpty() && artistImg != BAE::SLANG[W::NONE]) return artistImg; else return this->fetchCoverArt(track); } QVariantList Babe::searchFor(const QStringList &queries) { QVariantList mapList; bool hasKey = false; for(auto searchQuery : queries) { if(searchQuery.contains(BAE::SearchTMap[BAE::SearchT::LIKE]+":") || searchQuery.startsWith("#")) { if(searchQuery.startsWith("#")) searchQuery=searchQuery.replace("#","").trimmed(); else searchQuery=searchQuery.replace(BAE::SearchTMap[BAE::SearchT::LIKE]+":","").trimmed(); searchQuery = searchQuery.trimmed(); if(!searchQuery.isEmpty()) { mapList += getSearchedTracks(BAE::KEY::WIKI, searchQuery); mapList += getSearchedTracks(BAE::KEY::TAG, searchQuery); mapList += getSearchedTracks(BAE::KEY::LYRICS, searchQuery); } }else if(searchQuery.contains((BAE::SearchTMap[BAE::SearchT::SIMILAR]+":"))) { searchQuery=searchQuery.replace(BAE::SearchTMap[BAE::SearchT::SIMILAR]+":","").trimmed(); searchQuery=searchQuery.trimmed(); if(!searchQuery.isEmpty()) mapList += getSearchedTracks(BAE::KEY::TAG, searchQuery); }else { BAE::KEY key; QMapIterator k(BAE::KEYMAP); while (k.hasNext()) { k.next(); if(searchQuery.contains(QString(k.value()+":"))) { hasKey=true; key=k.key(); searchQuery = searchQuery.replace(k.value()+":","").trimmed(); } } searchQuery = searchQuery.trimmed(); if(!searchQuery.isEmpty()) { if(hasKey) mapList += getSearchedTracks(key, searchQuery); else { auto queryTxt = QString("SELECT t.*, al.artwork FROM tracks t INNER JOIN albums al ON t.album = al.album AND t.artist = al.artist WHERE t.title LIKE \"%"+searchQuery+"%\" OR t.artist LIKE \"%"+searchQuery+"%\" OR t.album LIKE \"%"+searchQuery+"%\"OR t.genre LIKE \"%"+searchQuery+"%\"OR t.url LIKE \"%"+searchQuery+"%\" ORDER BY strftime(\"%s\", t.addDate) desc LIMIT 1000"); mapList += getDBDataQML(queryTxt); } } } } return mapList; } void Babe::debug(const QString &msg) { emit this->message(msg); } QString Babe::fetchCoverArt(DB &song) { if(BAE::artworkCache(song, KEY::ALBUM)) return song[KEY::ARTWORK]; if(BAE::artworkCache(song, KEY::ARTIST)) return song[KEY::ARTWORK]; Pulpo pulpo; pulpo.registerServices({SERVICES::LastFm, SERVICES::Spotify}); pulpo.setOntology(PULPO::ONTOLOGY::ALBUM); pulpo.setInfo(PULPO::INFO::ARTWORK); QEventLoop loop; QTimer timer; timer.setSingleShot(true); timer.setInterval(1000); connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); connect(&pulpo, &Pulpo::infoReady, [&](const BAE::DB &track,const PULPO::RESPONSE &res) { Q_UNUSED(track); if(!res[PULPO::ONTOLOGY::ALBUM][PULPO::INFO::ARTWORK].isEmpty()) { auto artwork = res[PULPO::ONTOLOGY::ALBUM][PULPO::INFO::ARTWORK][PULPO::CONTEXT::IMAGE].toByteArray(); BAE::saveArt(song, artwork, BAE::CachePath); } loop.quit(); }); pulpo.feed(song, PULPO::RECURSIVE::OFF); timer.start(); loop.exec(); timer.stop(); return song[KEY::ARTWORK]; } QVariantList Babe::transformData(const DB_LIST &dbList) { QVariantList res; for(auto data : dbList) { QVariantMap map; for(auto key : data.keys()) map[BAE::KEYMAP[key]] = data[key]; res << map; } return res; } diff --git a/babe.h b/babe.h index d57bad9..52c2cd8 100644 --- a/babe.h +++ b/babe.h @@ -1,148 +1,144 @@ #ifndef BABE_H #define BABE_H #include #include #include "utils/bae.h" #include "db/collectionDB.h" #include "services/local/linking.h" #include "services/local/player.h" #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) class Notify; #elif defined (Q_OS_ANDROID) class NotificationClient; class Android; #endif class CollectionDB; class Pulpo; class BabeSettings; class ConThread; using namespace BAE; class Babe : public CollectionDB { Q_OBJECT public: explicit Babe(QObject *parent = nullptr); ~Babe(); BabeSettings *settings; Linking link; Player player; // Q_INVOKABLE void runPy(); /* DATABASE INTERFACES */ Q_INVOKABLE QVariantList get(const QString &queryTxt); Q_INVOKABLE QVariantList getList(const QStringList &urls); Q_INVOKABLE void set(const QString &table, const QVariantList &wheres); Q_INVOKABLE void trackPlaylist(const QStringList &urls, const QString &playlist); Q_INVOKABLE void trackLyrics(const QString &url); Q_INVOKABLE bool trackBabe(const QString &path); Q_INVOKABLE QString artistArt(const QString &artist); Q_INVOKABLE QString albumArt(const QString &album, const QString &artist); Q_INVOKABLE QString artistWiki(const QString &artist); Q_INVOKABLE QString albumWiki(const QString &album, const QString &artist); Q_INVOKABLE bool babeTrack(const QString &path, const bool &value); /* SETTINGS */ Q_INVOKABLE void scanDir(const QString &url); Q_INVOKABLE void brainz(const bool &on); Q_INVOKABLE bool brainzState(); Q_INVOKABLE void refreshCollection(); Q_INVOKABLE void getYoutubeTrack(const QString &message); /* STATIC METHODS */ Q_INVOKABLE static void saveSetting(const QString &key, const QVariant &value, const QString &group); Q_INVOKABLE static QVariant loadSetting(const QString &key, const QString &group, const QVariant &defaultValue); Q_INVOKABLE static void savePlaylist(const QStringList &list); Q_INVOKABLE static QStringList lastPlaylist(); Q_INVOKABLE static void savePlaylistPos(const int &pos); Q_INVOKABLE static int lastPlaylistPos(); Q_INVOKABLE static bool fileExists(const QString &url); Q_INVOKABLE static void showFolder(const QString &url); /*COLORS*/ Q_INVOKABLE static QString babeColor(); /*UTILS*/ Q_INVOKABLE static bool isMobile(); Q_INVOKABLE static bool isAndroid(); Q_INVOKABLE static QString moodColor(const int &pos); Q_INVOKABLE static QString homeDir(); Q_INVOKABLE static QString musicDir(); - Q_INVOKABLE static QString sdDir(); - - Q_INVOKABLE static QVariantList getDirs(const QString &pathUrl); - Q_INVOKABLE static QVariantMap getParentDir(const QString &path); Q_INVOKABLE static QStringList defaultSources(); static void registerTypes(); /*USEFUL*/ Q_INVOKABLE QString loadCover(const QString &url); Q_INVOKABLE QVariantList searchFor(const QStringList &queries); /*KDE*/ Q_INVOKABLE void notify(const QString &title, const QString &body); Q_INVOKABLE void notifySong(const QString &url); /*ANDROID*/ Q_INVOKABLE static void sendText(const QString &text); Q_INVOKABLE static void sendTrack(const QString &url); Q_INVOKABLE static void openFile(const QString &url); Q_INVOKABLE void fileChooser(); Q_INVOKABLE static void androidStatusBarColor(const QString &color, const bool &contrast); public slots: void debug(const QString &msg); private: ConThread *thread; #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) Notify *nof; #elif defined (Q_OS_ANDROID) NotificationClient *nof; Android *android; #endif QString fetchCoverArt(DB &song); static QVariantList transformData(const DB_LIST &dbList); void fetchTrackLyrics(DB &song); void linkDecoder(QString json); signals: void refreshTables(int size); void refreshTracks(); void refreshAlbums(); void refreshArtists(); void trackLyricsReady(QString lyrics, QString url); void skipTrack(); void babeIt(); void message(QString msg); }; #endif // BABE_H diff --git a/main.qml b/main.qml index bb4b327..2ba9a2e 100644 --- a/main.qml +++ b/main.qml @@ -1,1169 +1,1169 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import QtQuick.Controls.Material 2.1 import QtQuick.Window 2.3 import "utils" import "widgets" import "widgets/MyBeatView" import "widgets/PlaylistsView" import "widgets/MainPlaylist" import "widgets/SettingsView" import "widgets/SearchView" import "view_models" import "view_models/BabeDialog" import "services/local" import "services/web" import "services/web/Spotify" import "view_models/BabeGrid" import "db/Queries.js" as Q import "utils/Player.js" as Player import "utils/Help.js" as H import org.kde.kirigami 2.2 as Kirigami import org.kde.maui 1.0 as Maui import Link.Codes 1.0 Maui.ApplicationWindow { id: root visible: true width: Screen.width * (isMobile ? 1 : 0.4) minimumWidth: !isMobile ? columnWidth : 0 minimumHeight: !isMobile ? columnWidth + 64 : 0 height: Screen.height * (isMobile ? 1 : 0.4) // flags: Qt.FramelessWindowHint title: qsTr("vvave") // wideScreen: root.width > coverSize /*ALIASES*/ property alias playIcon: playIcon property alias babeBtnIcon: babeBtnIcon property alias progressBar: progressBar // property alias animFooter: animFooter property alias mainPlaylist: mainPlaylist /***************************************************/ /******************** PLAYBACK ********************/ /*************************************************/ property bool isShuffle: false property var currentTrack: ({ babe: "0", stars: "0" }) property int currentTrackIndex: 0 property int prevTrackIndex: 0 property string currentArtwork: !mainlistEmpty ? mainPlaylist.list.model.get( 0).artwork : "" property bool currentBabe: currentTrack.babe == "0" ? false : true property string durationTimeLabel: "00:00" property string progressTimeLabel: "00:00" property bool isPlaying: false property bool autoplay: bae.loadSetting("AUTOPLAY", "BABE", false) === "true" ? true : false property int onQueue: 0 property bool mainlistEmpty: !mainPlaylist.table.count > 0 /***************************************************/ /******************** UI PROPS ********************/ /*************************************************/ readonly property real opacityLevel: 0.8 iconSize: bae.loadSetting("ICON_SIZE", "BABE", iconSizes.medium) property int miniArtSize: iconSizes.large property int columnWidth: Kirigami.Units.gridUnit * 17 property int coverSize: focusMode ? columnWidth : (isAndroid ? Math.sqrt(root.width * root.height) * 0.4 : columnWidth * (isMobile ? 0.7 : 0.6)) /***************************************************/ /******************** HANDLERS ********************/ /*************************************************/ property int currentView: viewsIndex.tracks readonly property var viewsIndex: ({ tracks: 0, albums: 1, artists: 2, playlists: 3, search: 4, vvave: 5, linking: 6, youtube: 7, spotify: 8 }) property string syncPlaylist: "" property bool sync: false property string infoMsg: "" property bool infoLabels: bae.loadSetting("PLAYBACKINFO", "BABE", false) == "true" ? true : false property bool isLinked: false property bool isServing: false /* ANDROID THEMING*/ Material.theme: Material.Light Material.accent: babeColor Material.background: viewBackgroundColor Material.primary: backgroundColor Material.foreground: textColor /***************************************************/ /******************** UI UNITS ********************/ /*************************************************/ readonly property real screenWidth : Screen.width readonly property real screenHeight : Screen.height property bool focusMode : false /***************************************************/ /******************** UI COLORS *******************/ /*************************************************/ property string babeColor: bae.babeColor() readonly property string darkBackgroundColor: "#303030" readonly property string darkTextColor: "#FAFAFA" readonly property string darkHighlightColor: "#29B6F6" readonly property string darkHighlightedTextColor: darkTextColor readonly property string darkViewBackgroundColor: "#212121" readonly property string darkDarkColor: "#191919" readonly property string darkButtonBackgroundColor : "#191919" /***************************************************/ /**************************************************/ /*************************************************/ /*SIGNALS*/ signal missingAlert(var track) /*CONF*/ pageStack.defaultColumnWidth: columnWidth pageStack.initialPage: [mainPlaylist, views] pageStack.interactive: isMobile pageStack.separatorVisible: pageStack.wideMode overlay.modal: Rectangle { - color: isAndroid ? darkColor : "transparent" + color: isAndroid ? altColor : "transparent" opacity: 0.5 height: root.height - footBar.height - headBar.height } overlay.modeless: Rectangle { color: "transparent" } /*HANDLE EVENTS*/ onWidthChanged: if (isMobile) { if (width > height) mainPlaylist.cover.visible = false else mainPlaylist.cover.visible = true } onClosing: Player.savePlaylist() // pageStack.onCurrentIndexChanged: // { // if(pageStack.currentIndex === 0 && isMobile && !pageStack.wideMode) // { // bae.androidStatusBarColor(babeColor) // Material.background = babeColor // }else // { // bae.androidStatusBarColor(babeAltColor) // Material.background = babeAltColor // } // } onMissingAlert: { missingDialog.message = track.title + " by " + track.artist + " is missing" missingDialog.messageBody = "Do you want to remove it from your collection?" missingDialog.open() } /*COMPONENTS*/ BabeNotify { id: babeNotify } BabeMessage { id: missingDialog width: isMobile ? parent.width * 0.9 : parent.width * 0.4 title: "Missing file" onAccepted: { bae.removeTrack(currentTrack.url) mainPlaylist.table.model.remove(mainPlaylist.table.currentIndex) } } /* UI */ property bool accent : pageStack.wideMode || (!pageStack.wideMode && pageStack.currentIndex === 1) headBar.middleContent : Row { spacing: space.medium Maui.ToolButton { iconName: "view-media-track" iconColor: accent && currentView === viewsIndex.tracks ? babeColor : textColor display: pageStack.wideMode ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly onClicked: { pageStack.currentIndex = 1 currentView = viewsIndex.tracks } text: qsTr("Tracks") } Maui.ToolButton { text: qsTr("Albums") iconName: /*"album"*/ "view-media-album-cover" iconColor: accent && currentView === viewsIndex.albums ? babeColor : textColor display: pageStack.wideMode ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly onClicked: { pageStack.currentIndex = 1 albumsView.currentIndex = 0 currentView = viewsIndex.albums } } Maui.ToolButton { text: qsTr("Artists") iconName: "view-media-artist" iconColor: accent && currentView === viewsIndex.artists ? babeColor : textColor display: pageStack.wideMode ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly onClicked: { pageStack.currentIndex = 1 artistsView.currentIndex = 0 currentView = viewsIndex.artists } } Maui.ToolButton { text: qsTr("Playlists") iconName: "view-media-playlist" iconColor: accent && currentView === viewsIndex.playlists ? babeColor : textColor display: pageStack.wideMode ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly onClicked: { pageStack.currentIndex = 1 currentView = viewsIndex.playlists } } } onSearchButtonClicked: { pageStack.currentIndex = 1 currentView = viewsIndex.search searchView.searchInput.forceActiveFocus() } footBar.z : 0 pageStack.z: 0 footBar.middleContent: Row { spacing: space.medium Maui.ToolButton { id: babeBtnIcon iconName: "love" iconColor: currentBabe ? babeColor : darkTextColor onClicked: if (!mainlistEmpty) { var value = mainPlaylist.contextMenu.babeIt( currentTrackIndex) currentBabe = value } } Maui.ToolButton { iconName: "media-skip-backward" iconColor: darkTextColor onClicked: Player.previousTrack() onPressAndHold: Player.playAt(prevTrackIndex) } Maui.ToolButton { id: playIcon iconColor: darkTextColor iconName: isPlaying ? "media-playback-pause" : "media-playback-start" onClicked: { if (isPlaying) Player.pauseTrack() else Player.resumeTrack() } } Maui.ToolButton { id: nextBtn iconColor: darkTextColor iconName: "media-skip-forward" onClicked: Player.nextTrack() onPressAndHold: Player.playAt(Player.shuffle()) } Maui.PieButton { id: shuffleBtn iconColor: darkTextColor iconName: isShuffle ? "media-playlist-shuffle" : "media-playlist-repeat" delegateSize: iconSizes.large + space.big // onClicked: isShuffle = !isShuffle model: mainPlaylist.list.model delegate: BabeAlbum { id: delegate itemWidth: iconSizes.large + space.big itemHeight: itemWidth albumSize: iconSizes.large showIndicator: false showLabels: false albumRadius: itemWidth Connections { target: delegate onClicked: { shuffleBtn.close() Player.playAt(index) } } } } } footBar.background: Rectangle { id: footerBg color: darkViewBackgroundColor SequentialAnimation { id: animFooter PropertyAnimation { target: footerBg property: "color" easing.type: Easing.InOutQuad from: "black" to: darkViewBackgroundColor duration: 500 } } FastBlur { width: parent.width height: parent.height-1 y:1 source: mainPlaylist.artwork radius: 100 transparentBorder: false cached: true z: -999 } Slider { id: progressBar height: iconSizes.big width: parent.width anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top padding: 0 from: 0 to: 1000 value: 0 spacing: 0 focus: true onMoved: player.seek(player.duration() / 1000 * value) background: Rectangle { x: progressBar.leftPadding y: progressBar.y implicitWidth: 200 implicitHeight: Kirigami.Units.devicePixelRatio * 3 width: progressBar.availableWidth height: implicitHeight color: "transparent" Rectangle { width: progressBar.visualPosition * parent.width height: Kirigami.Units.devicePixelRatio * 3 color: babeColor } } handle: Rectangle { x: progressBar.leftPadding + progressBar.visualPosition * (progressBar.availableWidth - width) y: progressBar.y - (height / 2) implicitWidth: progressBar.pressed ? iconSizes.medium : 0 implicitHeight: progressBar.pressed ? iconSizes.medium : 0 radius: progressBar.pressed ? iconSizes.medium : 0 color: babeColor } } } FloatingDisk { id: floatingDisk z: pageStack.z +1 } Maui.ShareDialog { id: shareDialog } Maui.FileDialog { id: fmDialog } // Item // { // Layout.alignment: Qt.AlignCenter // Layout.fillWidth: true // Layout.fillHeight: true // Layout.row: 2 // Layout.column: 2 // Layout.maximumHeight: playbackInfo.visible ? playbackInfo.font.pointSize * 2 : 0 // Label // { // id: playbackInfo // visible: !mainlistEmpty && infoLabels // // anchors.top: playIcon.bottom // // anchors.horizontalCenter: playIcon.horizontalCenter // width: parent.width // height: parent.height // horizontalAlignment: Qt.AlignHCenter // verticalAlignment: Qt.AlignVCenter // text: progressTimeLabel + " / " + (currentTrack ? (currentTrack.title ? currentTrack.title + " - " + currentTrack.artist : "--- - " + currentTrack.artist) : "") + " / " + durationTimeLabel // color: darkTextColor // font.pointSize: fontSizes.small // elide: Text.ElideRight // } // } // background: Rectangle // { // anchors.fill: parent // color: altColor // z: -999 // } SourcesDialog { id: sourcesDialog } BabeConsole { id: babeConsole } menuDrawer.bannerImageSource: "qrc:/assets/banner.svg" menuDrawer.actions: [ Kirigami.Action { text: "Vvave Stream" iconName: "love" onTriggered: { pageStack.currentIndex = 1 currentView = viewsIndex.vvave } }, Kirigami.Action { text: qsTr("Linking") iconName: isMobile ? "computer-laptop" : "phone" onTriggered: { pageStack.currentIndex = 1 currentView = viewsIndex.linking if(!isLinked) linkingView.linkingConf.open() } }, Kirigami.Action { text: qsTr("YouTube") iconName: "im-youtube" onTriggered: { pageStack.currentIndex = 1 currentView = viewsIndex.youtube } }, Kirigami.Action { text: qsTr("Spotify") onTriggered: { pageStack.currentIndex = 1 currentView = viewsIndex.spotify } }, Kirigami.Action { text: qsTr("Collection") iconName: "database-index" Kirigami.Action { text: qsTr("Sources...") onTriggered: sourcesDialog.open() iconName: "folder-new" } Kirigami.Action { text: qsTr("Re-Scan") onTriggered: bae.refreshCollection(); } Kirigami.Action { text: qsTr("Refresh...") iconName: "view-refresh" Kirigami.Action { text: qsTr("Tracks") onTriggered: H.refreshTracks(); } Kirigami.Action { text: qsTr("Albums") onTriggered: H.refreshAlbums(); } Kirigami.Action { text: qsTr("Artists") onTriggered: H.refreshArtists(); } Kirigami.Action { text: qsTr("All") onTriggered: H.refreshCollection(); } } Kirigami.Action { text: qsTr("Clean") onTriggered: bae.removeMissingTracks(); iconName: "edit-clear" } }, Kirigami.Action { text: qsTr("Settings...") iconName: "view-media-config" Kirigami.Action { text: "Brainz" Kirigami.Action { id: brainzToggle text: checked ? "Turn OFF" : "Turn ON" checked: activeBrainz checkable: true onToggled: { bae.saveSetting("BRAINZ", checked === true ? true : false, "BABE") bae.brainz(checked === true ? true : false) } } } Kirigami.Action { text: "Appearance" Kirigami.Action { text: "Icon size" Kirigami.Action { text: iconSizes.small onTriggered : { bae.saveSetting("ICON_SIZE", text, "BABE") toolBarIconSize = text } } Kirigami.Action { text: iconSizes.medium onTriggered : { bae.saveSetting("ICON_SIZE", text, "BABE") iconSizeChanged(text) } } Kirigami.Action { text: iconSizes.big onTriggered : { bae.saveSetting("ICON_SIZE", text, "BABE") iconSizeChanged(text) } } } } Kirigami.Action { text: "Player" Kirigami.Action { text: "Info label" Kirigami.Action { text: checked ? "ON" : "OFF" checked: infoLabels checkable: true onToggled: { infoLabels = checked bae.saveSetting("PLAYBACKINFO", infoLabels ? true : false, "BABE") } } } Kirigami.Action { text: "Autoplay" checked: autoplay checkable: true onToggled: { autoplay = checked bae.saveSetting("AUTOPLAY", autoplay ? true : false, "BABE") } } } }, Kirigami.Action { text: "Developer" iconName: "code-context" Kirigami.Action { text: "Wiki" } Kirigami.Action { text: "Console log" onTriggered: babeConsole.open() } }, Kirigami.Action { text: "About..." iconName: "help-about" Kirigami.Action { text: "VVAVEIt" } Kirigami.Action { text: "VVAVE" } Kirigami.Action { text: "Pulpo" } Kirigami.Action { text: "Kirigami" } } ] Item { id: message visible: infoMsg.length > 0 && sync anchors.bottom: parent.bottom width: pageStack.wideMode ? columnWidth : parent.width height: iconSize z: 999 Rectangle { id: infoBg anchors.fill: parent z: -999 color: altColor opacity: opacityLevel SequentialAnimation { id: animBg PropertyAnimation { target: infoBg property: "color" easing.type: Easing.InOutQuad to: babeColor duration: 250 } PropertyAnimation { target: infoBg property: "color" easing.type: Easing.InOutQuad to: altColor duration: 500 } } } Label { id: infoTxt anchors.centerIn: parent anchors.fill: parent height: parent.height width: parent.width font.pointSize: fontSizes.medium text: infoMsg horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter color: textColor SequentialAnimation { id: animTxt PropertyAnimation { target: infoTxt property: "color" easing.type: Easing.InOutQuad to: "white" duration: 250 } PropertyAnimation { target: infoTxt property: "color" easing.type: Easing.InOutQuad to: textColor duration: 500 } } } } MainPlaylist { id: mainPlaylist anchors.fill: parent clip: true Connections { target: mainPlaylist onCoverPressed: Player.appendAll(tracks) onCoverDoubleClicked: Player.playAll(tracks) } } Page { id: views anchors.fill: parent clip: true // focusPolicy: Qt.WheelFocus // visualFocus: true Column { anchors.fill: parent SwipeView { id: swipeView width: parent.width height: parent.height Component.onCompleted: contentItem.interactive = isMobile currentIndex: currentView onCurrentItemChanged: currentItem.forceActiveFocus() onCurrentIndexChanged: { currentView = currentIndex if (!babeitView.isConnected && currentIndex === viewsIndex.vvave) babeitView.logginDialog.open() } TracksView { id: tracksView Connections { target: tracksView onRowClicked: Player.addTrack(tracksView.model.get(index)) onQuickPlayTrack: Player.quickPlay(tracksView.model.get(index)) onPlayAll: Player.playAll(bae.get(Q.GET.allTracks)) onAppendAll: Player.appendAll(bae.get(Q.GET.allTracks)) onQueueTrack: Player.queueTracks([tracksView.model.get(index)], index) } } AlbumsView { id: albumsView Connections { target: albumsView onRowClicked: Player.addTrack(track) onPlayTrack: Player.quickPlay(track) onAlbumCoverClicked: { var query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) albumsView.table.headBarTitle = album albumsView.populateTable(query) } onAlbumCoverPressedAndHold: { var query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) var map = bae.get(query) albumsView.playAlbum(map) } onPlayAll: { var query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) query = query.arg(data.artist) var tracks = bae.get(query) Player.playAll(tracks) } onAppendAll: { var query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) var tracks = bae.get(query) Player.appendAll(tracks) } } } AlbumsView { id: artistsView Connections { target: artistsView onRowClicked: Player.addTrack(track) onPlayTrack: Player.quickPlay(track) onAlbumCoverClicked: { var query = Q.GET.artistTracks_.arg(artist) artistsView.table.headBarTitle = artist artistsView.populateTable(query) } onAlbumCoverPressedAndHold: { var query = Q.GET.artistTracks_.arg(artist) var map = bae.get(query) artistsView.playAlbum(map) } onPlayAll: { var query = Q.GET.artistTracks_.arg(artist) query = query.arg(data.artist) var tracks = bae.get(query) Player.playAll(tracks) } onAppendAll: { var query = Q.GET.artistTracks_.arg(artist) var tracks = bae.get(query) Player.appendAll(tracks) } } } PlaylistsView { id: playlistsView Connections { target: playlistsView onRowClicked: Player.addTrack(track) onQuickPlayTrack: Player.quickPlay(track) onPlayAll: Player.playAll(tracks) onAppendAll: Player.appendAll(tracks) onPlaySync: { var tracks = bae.get(Q.GET.playlistTracks_.arg(playlist)) Player.playAll(tracks) root.sync = true root.syncPlaylist = playlist root.infoMsg = "Syncing to " + playlist } } } SearchTable { id: searchView Connections { target: searchView.searchTable onRowClicked: Player.addTrack(searchView.searchTable.model.get(index)) onQuickPlayTrack: Player.quickPlay(searchView.searchTable.model.get(index)) onPlayAll: Player.playAll(searchView.searchRes) onAppendAll: Player.appendAll(searchView.searchRes) onArtworkDoubleClicked: { var query = Q.GET.albumTracks_.arg( searchView.searchTable.model.get( index).album) query = query.arg(searchView.searchTable.model.get( index).artist) Player.playAll(bae.get(query)) } } } BabeitView { id: babeitView } LinkingView { id: linkingView } YouTube { id: youtubeView } Spotify { id: spotifyView } } } } /*animations*/ /*FUNCTIONS*/ function infoMsgAnim() { animBg.running = true animTxt.running = true } function toggleMaximized() { if (root.visibility === Window.Maximized) { root.showNormal(); } else { root.showMaximized(); } } function switchColorScheme(variant) { bae.saveSetting("THEME", variant, "BABE") if(variant === "Light") { backgroundColor = Kirigami.Theme.backgroundColor textColor = Kirigami.Theme.textColor highlightColor = Kirigami.Theme.highlightColor highlightedTextColor = Kirigami.Theme.highlightedTextColor buttonBackgroundColor = Kirigami.Theme.buttonBackgroundColor viewBackgroundColor = Kirigami.Theme.viewBackgroundColor altColor = Kirigami.Theme.complementaryBackgroundColor babeColor = bae.babeColor() }else if(variant === "Dark") { backgroundColor = darkBackgroundColor textColor = darkTextColor highlightColor = darkHighlightColor highlightedTextColor = darkHighlightedTextColor buttonBackgroundColor = darkButtonBackgroundColor viewBackgroundColor = darkViewBackgroundColor altColor = darkDarkColor } } Component.onCompleted: { var style = bae.loadSetting("THEME", "BABE", "Dark") if(isAndroid) { switchColorScheme(style) Maui.Android.statusbarColor(viewBackgroundColor, false) } } /*CONNECTIONS*/ Connections { target: player onPos: progressBar.value = pos onTiming: progressTimeLabel = time onDurationChanged: durationTimeLabel = time onFinished: if (!mainlistEmpty) { if (currentTrack.url) bae.playedTrack(currentTrack.url) Player.nextTrack() } onIsPlaying: isPlaying = playing } Connections { target: bae onRefreshTables: H.refreshCollection(size) onRefreshTracks: H.refreshTracks() onRefreshAlbums: H.refreshAlbums() onRefreshArtists: H.refreshArtists() onTrackLyricsReady: { if (url === currentTrack.url) Player.setLyrics(lyrics) } onSkipTrack: Player.nextTrack() onBabeIt: Player.babeTrack() } } diff --git a/view_models/BabeTable/TableDelegate.qml b/view_models/BabeTable/TableDelegate.qml index 2ef416a..19cc9ed 100644 --- a/view_models/BabeTable/TableDelegate.qml +++ b/view_models/BabeTable/TableDelegate.qml @@ -1,472 +1,472 @@ import QtQuick 2.9 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.3 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.2 as Kirigami import org.kde.maui 1.0 as Maui import "../../view_models" import "../../utils/Help.js" as H import "../../utils/Player.js" as PLAYER SwipeDelegate { id: delegateRoot readonly property int altHeight : rowHeight * 1.2 readonly property bool sameAlbum : { if(coverArt) { if(listModel.get(index-1)) { if(listModel.get(index-1).album === album && listModel.get(index-1).artist === artist) true else false }else false }else false } property bool isCurrentListItem : ListView.isCurrentItem property color bgColor : backgroundColor property string labelColor: isCurrentListItem ? highlightedTextColor : textColor property bool number : false property bool quickPlay : true property bool coverArt : false property bool menuItem : false property bool trackDurationVisible : false property bool trackRatingVisible: false property bool playingIndicator: false property string trackMood : art property bool remoteArtwork: false width: parent.width height: sameAlbum ? rowHeight : altHeight padding: 0 clip: true autoExclusive: true swipe.enabled: menuItem focus: true focusPolicy: Qt.StrongFocus hoverEnabled: true signal play() signal rightClicked() signal leftClicked() signal artworkCoverClicked() signal artworkCoverDoubleClicked() background: Rectangle { height: delegateRoot.height color: isCurrentListItem ? highlightColor : (trackMood.length > 0 ? Qt.lighter(trackMood, 1.5) : index % 2 === 0 ? Qt.lighter(bgColor) : bgColor) } swipe.right: Row { padding: 12 height: delegateRoot.height anchors.right: parent.right spacing: space.big Maui.ToolButton { iconName: "documentinfo" anchors.verticalCenter: parent.verticalCenter onClicked: swipe.close() } Maui.ToolButton { iconName: "love" anchors.verticalCenter: parent.verticalCenter iconColor: babe === "1" ? babeColor : textColor onClicked: { babe = babe === "1" ? "0" : "1" PLAYER.babeTrack(url, babe) swipe.close() } } Maui.ToolButton { iconName: "view-media-recent" anchors.verticalCenter: parent.verticalCenter onClicked: { swipe.close() queueTrack(index) } } Maui.ToolButton { iconName: "media-playback-start" anchors.verticalCenter: parent.verticalCenter onClicked: { swipe.close() play() } } } contentItem: Item { height: delegateRoot.height width: delegateRoot.width MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton pressAndHoldInterval: 3000 onClicked: if(!isMobile && mouse.button === Qt.RightButton) rightClicked() } RowLayout { id: gridLayout height: parent.height width: parent.width anchors.verticalCenter: parent.verticalCenter spacing: 0 Item { visible: coverArt Layout.fillHeight: true Layout.alignment: Qt.AlignLeft width: altHeight height: parent.height ToolButton { visible: !sameAlbum anchors.fill: parent flat: true Image { id: artworkCover anchors.centerIn: parent height: parent.height * 0.8 width: height sourceSize.width: parent.width sourceSize.height: parent.height source: typeof artwork === 'undefined' ? "qrc:/assets/cover.png" : remoteArtwork ? artwork : ((artwork && artwork.length > 0 && artwork !== "NONE")? "file://"+encodeURIComponent(artwork) : "qrc:/assets/cover.png") fillMode: Image.PreserveAspectFit cache: true // antialiasing: true // smooth: true layer.enabled: coverArt layer.effect: OpacityMask { maskSource: Item { width: artworkCover.width height: artworkCover.height Rectangle { anchors.centerIn: parent width: artworkCover.adapt ? artworkCover.width : Math.min(artworkCover.width, artworkCover.height) height: artworkCover.adapt ? artworkCover.height : width radius: Kirigami.Units.devicePixelRatio *3 border.color: altColor border.width: Kirigami.Units.devicePixelRatio *3 } } } } onDoubleClicked: artworkCoverDoubleClicked() onClicked: artworkCoverClicked() onPressAndHold: if(isMobile) artworkCoverDoubleClicked() } Item { visible : playingIndicator && (currentTrackIndex === index) && isPlaying height: parent.height * 0.5 width: height anchors.centerIn: parent AnimatedImage { source: "qrc:/assets/bars.gif" anchors.centerIn: parent height: parent.height width: parent.width playing: parent.visible } } } Item { visible: quickPlay Layout.fillHeight: true width: height * 0.5 height: parent.height - Layout.leftMargin: space.small + Layout.leftMargin: sameAlbum ? 0 : space.small Maui.ToolButton { id: playBtn anchors.centerIn: parent iconName: "media-playback-start" iconColor: labelColor onClicked: play() anim: true } } Item { Layout.fillHeight: true Layout.fillWidth: true Layout.alignment: Qt.AlignLeft Layout.margins: space.tiny Layout.leftMargin: space.small * (quickPlay ? 1 : 2) anchors.verticalCenter: parent.verticalCenter GridLayout { anchors.fill: parent rows: 2 columns: 4 rowSpacing: 0 Label { id: trackNumber visible: number width: 16 Layout.fillHeight: true Layout.row: 1 Layout.column: 1 Layout.alignment: Qt.AlignCenter verticalAlignment: Qt.AlignVCenter text: track + ". " font.bold: true elide: Text.ElideRight font.pointSize: fontSizes.default color: labelColor } Label { id: trackTitle Layout.maximumWidth: gridLayout.width *0.5 Layout.fillWidth: true Layout.fillHeight: true Layout.row: 1 Layout.column: 2 verticalAlignment: Qt.AlignVCenter text: title font.bold: !sameAlbum elide: Text.ElideRight font.pointSize: fontSizes.default color: labelColor } Label { id: trackInfo visible: coverArt ? !sameAlbum : true Layout.fillWidth: true Layout.fillHeight: true Layout.maximumWidth: gridLayout.width*0.4 Layout.row: 2 Layout.column: 2 verticalAlignment: Qt.AlignVCenter text: artist + " | " + album font.bold: false elide: Text.ElideRight font.pointSize: fontSizes.medium color: labelColor } // Item // { // Layout.row: 1 // Layout.rowSpan: 2 // Layout.column: 4 // height: 48 // width: height // Layout.fillWidth: true // Layout.fillHeight: true // Layout.alignment: Qt.AlignCenter // AnimatedImage // { // id: animation // cache: true // visible: playingIndicator // height: 22 // width: 22 // horizontalAlignment: Qt.AlignLeft // verticalAlignment: Qt.AlignVCenter // source: "qrc:/assets/bars.gif" // } // } // Label // { // id: trackDuration // visible: trackDurationVisible // Layout.alignment: Qt.AlignRight // Layout.fillWidth: true // Layout.fillHeight: true // Layout.row: 1 // Layout.column: 3 // horizontalAlignment: Qt.AlignRight // verticalAlignment: Qt.AlignVCenter // text: player.transformTime(duration) // font.bold: false // elide: Text.ElideRight // font.pointSize: 8 // color: labelColor // } Label { id: trackBabe font.family: "Material Design Icons" visible: babe == "1" Layout.alignment: Qt.AlignRight Layout.fillWidth: true Layout.fillHeight: true Layout.row: 1 Layout.column: /*trackDurationVisible &&*/ sameAlbum ? 4 : 3 horizontalAlignment: Qt.AlignRight verticalAlignment: Qt.AlignVCenter text: babe == "1" ? "\uf2D1" : "" font.bold: false elide: Text.ElideRight font.pointSize: fontSizes.small color: labelColor // onTextChanged: animBabe.start() // SequentialAnimation // { // id: animBabe // PropertyAnimation // { // target: trackBabe // property: "color" // easing.type: Easing.InOutQuad // to: babeColor // duration: 250 // } // PropertyAnimation // { // target: trackBabe // property: "color" // easing.type: Easing.InOutQuad // to: labelColor // duration: 500 // } // } } Label { font.family: "Material Design Icons" id: trackRating visible: trackRatingVisible Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignRight Layout.row: /*trackRatingVisible && */sameAlbum ? 1 : 2 Layout.column: 3 // Layout.columnSpan: trackRatingVisible && sameAlbum ? 4 : 3 horizontalAlignment: Qt.AlignRight verticalAlignment: Qt.AlignVCenter text: H.setStars(stars) font.bold: false elide: Text.ElideRight font.pointSize: fontSizes.small color: labelColor } } } Item { visible: menuItem Layout.fillHeight: true width: parent.height * 0.5 MouseArea { id: handle property var downTimestamp; property int startX property int startMouseX anchors.fill: parent preventStealing: true onPressed: { startX = delegateRoot.background.x; startMouseX = mouse.x; } onPositionChanged: swipe.position = Math.min(0, Math.max(-delegateRoot.width + height, delegateRoot.background.x - (startMouseX - mouse.x))); Maui.ToolButton { id: menuBtn visible: handle.pressed || swipe.position < 0 anchors.centerIn: parent iconName: "overflow-menu" iconColor: labelColor onClicked: swipe.position < 0 ? swipe.close() : swipe.open(SwipeDelegate.Right) } } } } } } diff --git a/widgets/SettingsView/SourcesDialog.qml b/widgets/SettingsView/SourcesDialog.qml index 3543cbe..a1893ff 100644 --- a/widgets/SettingsView/SourcesDialog.qml +++ b/widgets/SettingsView/SourcesDialog.qml @@ -1,122 +1,121 @@ import QtQuick 2.9 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 import Qt.labs.platform 1.0 import org.kde.maui 1.0 as Maui import "../../view_models" -import "../../view_models/FolderPicker" import "../../view_models/BabeDialog" BabePopup { property string pathToRemove : "" function scanDir(folderUrl) { bae.scanDir(folderUrl) } BabeMessage { id: confirmationDialog onAccepted: { if(pathToRemove.length>0) if(bae.removeSource(pathToRemove)) bae.refreshCollection() } } BabeList { id: sources anchors.fill: parent headBarVisible: true headBarExit: true headBarTitle: qsTr("Sources") Layout.fillWidth: true Layout.fillHeight: true width: parent.width onExit: close() ListModel { id: listModel } model: listModel delegate: BabeDelegate { id: delegate label: url Connections { target: delegate onClicked: sources.currentIndex = index } } headBar.rightContent: [ Maui.ToolButton { iconName: "list-remove" onClicked: { close() var index = sources.currentIndex var url = sources.list.model.get(index).url confirmationDialog.title = "Remove source" if(bae.defaultSources().indexOf(url)<0) { pathToRemove = url confirmationDialog.message = "Are you sure you want to remove the source: \n "+url } else { pathToRemove = "" confirmationDialog.message = url+"\nis a default source and cannot be removed" } confirmationDialog.open() } }, Maui.ToolButton { iconName: "list-add" onClicked: { close() fmDialog.onlyDirs = true fmDialog.show(function(paths) { for(var i in paths) { listModel.append({url: paths[i]}) scanDir(paths[i]) } close() }) } } ] } onOpened: getSources() function getSources() { sources.clearTable() var folders = bae.getSourcesFolders() for(var i in folders) sources.model.append({url : folders[i]}) } }