Commit aaf7362a authored by Camilo Higuita's avatar Camilo Higuita

work on android share tracks and initial work on notification

parent 71282d99
......@@ -21,7 +21,8 @@ include(kde/kde.pri)
android:
{
message(Building for helpers for Android)
include(android/android.pri)
include(android-openssl.pri)
include(3rdparty/kirigami/kirigami.pri)
}
......@@ -204,7 +205,6 @@ SOURCES += main.cpp \
taglib/tagunion.cpp \
babe.cpp \
settings/BabeSettings.cpp \
java/notificationclient.cpp \
db/conthread.cpp \
services/web/babeit.cpp \
utils/babeconsole.cpp
......@@ -238,7 +238,7 @@ DISTFILES += \
android/build.gradle \
android/gradle/wrapper/gradle-wrapper.properties \
android/gradlew.bat \
android/src/org/qtproject/babe/NotificationClient.java
android/android.pri
HEADERS += \
......@@ -365,7 +365,6 @@ HEADERS += \
taglib/taglib_config.h \
babe.h \
settings/BabeSettings.h \
java/notificationclient.h \
db/conthread.h \
services/web/babeit.h \
utils/babeconsole.h \
......
......@@ -2,6 +2,7 @@
<manifest package="org.qtproject.babe" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="-- %%INSERT_APP_NAME%% --" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop">
android:name="com.example.android.tools.NotificationClient"
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
......@@ -11,6 +12,7 @@
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
......@@ -59,6 +61,7 @@
-->
<meta-data android:name="android.app.extract_android_style" android:value="full"/>
<!-- extract android style -->
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
......@@ -76,4 +79,5 @@
Remove the comment if you do not require these default features. -->
<!-- %%INSERT_FEATURES -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>
#include "android.h"
Android::Android(QObject *parent) : QObject(parent)
{
}
#ifndef ANDROID_H
#define ANDROID_H
#include <QObject>
class Android : public QObject
{
Q_OBJECT
public:
explicit Android(QObject *parent = nullptr);
signals:
public slots:
};
#endif // ANDROID_H
\ No newline at end of file
QT += androidextras
HEADERS += \
$$PWD/android.h \
$$PWD/android.h \
$$PWD/notificationclient.h
SOURCES += \
$$PWD/android.cpp \
$$PWD/notificationclient.cpp
DISTFILES += \
$$PWD/src/SendIntent.java \
$$PWD/src/NotificationClient.java
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/src
......@@ -18,8 +18,14 @@ apply plugin: 'com.android.application'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
dependencies {
compile "com.android.support:support-compat:25.3.1"
}
android {
/*******************************************************
* The following variables:
......
#include "notificationclient.h"
#ifdef Q_OS_ANDROID
#if defined(Q_OS_ANDROID)
#include <QAndroidJniObject>
#include <QAndroidJniEnvironment>
#include <QtAndroid>
#include <QException>
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
NotificationClient::NotificationClient(QObject *parent)
: QObject(parent)
{
connect(this, SIGNAL(notificationChanged()), this, SLOT(updateAndroidNotification()));
}
void NotificationClient::setNotification(const QString &notification)
void NotificationClient::notify(const QString &notification)
{
if (m_notification == notification)
return;
......@@ -26,13 +41,13 @@ QString NotificationClient::notification() const
void NotificationClient::updateAndroidNotification()
{
#ifdef Q_OS_ANDROID
#if defined(Q_OS_ANDROID)
QAndroidJniObject javaNotification = QAndroidJniObject::fromString(m_notification);
QAndroidJniObject::callStaticMethod<void>("org/qtproject/babe/NotificationClient",
"notify",
"(Ljava/lang/String;)V",
javaNotification.object<jstring>());
QAndroidJniObject::callStaticMethod<void>("org/qtproject/example/notification/NotificationClient",
"notify",
"(Ljava/lang/String;)V",
javaNotification.object<jstring>());
#endif
}
#ifndef NOTIFICATIONCLIENT_H
#define NOTIFICATIONCLIENT_H
#include <QObject>
class NotificationClient : public QObject
{
Q_OBJECT
public:
explicit NotificationClient(QObject *parent = 0);
void notify(const QString &notification);
QString notification() const;
signals:
void notificationChanged();
private slots:
void updateAndroidNotification();
private:
QString m_notification;
};
#endif // NOTIFICATIONCLIENT_H
package org.qtproject.babe;
package org.qtproject.example.notification;
import android.app.Notification;
import android.app.NotificationManager;
......@@ -20,7 +20,6 @@ public class NotificationClient extends org.qtproject.qt5.android.bindings.QtAct
if (m_notificationManager == null) {
m_notificationManager = (NotificationManager)m_instance.getSystemService(Context.NOTIFICATION_SERVICE);
m_builder = new Notification.Builder(m_instance);
// m_builder.setSmallIcon(R.drawable.icon);
m_builder.setContentTitle("A message from Qt!");
}
......
package com.example.android.tools;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter;
import android.net.Uri;
import java.io.File;
public class SendIntent
{
public static void sendText(Activity context,String text)
{
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, text);
sendIntent.setType("text/plain");
context.startActivity(Intent.createChooser(sendIntent, text));
}
public static void sendUrl(Activity context, String text)
{
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, text);
sendIntent.setType("text/plain");
context.startActivity(Intent.createChooser(sendIntent, text));
}
public static void sendTrack(Activity context, String url)
{
File file = new File(url);
System.out.println(file.exists());
Uri uri = Uri.fromFile(file);
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
sendIntent.setType("audio/mp3");
context.startActivity(Intent.createChooser(sendIntent, "Share track"));
}
}
......@@ -14,12 +14,7 @@
#include <QDesktopWidget>
#include <QDirIterator>
#include <QtQml>
#ifdef Q_OS_ANDROID
#include <QAndroidJniObject>
#include <QAndroidJniEnvironment>
#include <QtAndroid>
#endif
#include <QDesktopServices>
//#include "Python.h"
......@@ -28,6 +23,24 @@
#include "kde/kdeconnect.h"
#endif
#if defined(Q_OS_ANDROID)
#include "../android/notificationclient.h"
#include <QAndroidJniObject>
#include <QAndroidJniEnvironment>
#include <QtAndroid>
#include <QException>
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)
......@@ -55,6 +68,8 @@ Babe::Babe(QObject *parent) : CollectionDB(parent)
{
emit this->skipTrack();
});
//#elif defined (Q_OS_ANDROID)
// this->nof = new NotificationClient(this);
#endif
}
......@@ -187,15 +202,15 @@ void Babe::fetchTrackLyrics(DB &song)
pulpo.setOntology(PULPO::ONTOLOGY::TRACK);
pulpo.setInfo(PULPO::INFO::LYRICS);
connect(&pulpo, &Pulpo::infoReady, [&](const BAE::DB &track, const PULPO::RESPONSE &res)
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 "+song[KEY::TITLE]+" "+song[KEY::ARTIST]);
emit this->trackLyricsReady(lyrics, song[KEY::URL]);
bDebug::Instance()->msg("Downloaded the lyrics for "+track[KEY::TITLE]+" "+track[KEY::ARTIST]);
emit this->trackLyricsReady(lyrics, track[KEY::URL]);
}
});
......@@ -232,9 +247,8 @@ void Babe::notify(const QString &title, const QString &body)
#if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID))
this->nof->notify(title, body);
#else
Q_UNUSED(title);
Q_UNUSED(body);
#elif defined (Q_OS_ANDROID)
this->nof->notify(title+" "+body);
#endif
}
......@@ -250,6 +264,62 @@ void Babe::notifySong(const QString &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<void>("com/example/android/tools/SendIntent",
"sendText",
"(Landroid/app/Activity;Ljava/lang/String;)V",
activity.object<jobject>(),
QAndroidJniObject::fromString(text).object<jstring>());
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<void>("com/example/android/tools/SendIntent",
"sendTrack",
"(Landroid/app/Activity;Ljava/lang/String;)V",
activity.object<jobject>(),
QAndroidJniObject::fromString(url).object<jstring>());
if (_env->ExceptionCheck()) {
_env->ExceptionClear();
throw InterfaceConnFailedException();
}
}else
throw InterfaceConnFailedException();
#endif
}
void Babe::scanDir(const QString &url)
{
emit this->settings->collectionPathChanged({url});
......@@ -265,6 +335,11 @@ bool Babe::brainzState()
return loadSetting("BRAINZ", "BABE", false).toBool();
}
void Babe::refreshCollection()
{
this->settings->refreshCollection();
}
QVariant Babe::loadSetting(const QString &key, const QString &group, const QVariant &defaultValue)
{
return BAE::loadSettings(key, group, defaultValue);
......@@ -301,6 +376,12 @@ 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::baseColor()
{
#if defined(Q_OS_ANDROID)
......
......@@ -8,6 +8,8 @@
#if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID))
class Notify;
#elif defined (Q_OS_ANDROID)
class NotificationClient;
#endif
class CollectionDB;
......@@ -31,7 +33,7 @@ public:
};
Q_ENUM(HINT)
// Q_INVOKABLE void runPy();
// Q_INVOKABLE void runPy();
/* DATABASE INTERFACES */
......@@ -56,6 +58,7 @@ public:
Q_INVOKABLE void scanDir(const QString &url);
Q_INVOKABLE void brainz(const bool &on);
Q_INVOKABLE bool brainzState();
Q_INVOKABLE void refreshCollection();
/* STATIC METHODS */
......@@ -69,6 +72,7 @@ public:
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 baseColor();
......@@ -84,7 +88,6 @@ public:
Q_INVOKABLE static QString altColor();
Q_INVOKABLE static QString babeColor();
Q_INVOKABLE static QString babeAltColor();
Q_INVOKABLE static void androidStatusBarColor(const QString &color);
/*UTILS*/
......@@ -114,6 +117,13 @@ public:
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 androidStatusBarColor(const QString &color);
public slots:
void debug(const QString &msg);
......@@ -123,6 +133,8 @@ private:
#if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID))
Notify *nof;
#elif defined (Q_OS_ANDROID)
NotificationClient *nof;
#endif
QString fetchCoverArt(DB &song);
......
......@@ -573,6 +573,15 @@ QVariantList CollectionDB::getDBDataQML(const QString &queryTxt)
return mapList;
}
QStringList CollectionDB::dataToList(const DB_LIST &list, const KEY &key)
{
QStringList response;
for(auto track : list)
response << track[key];
return response;
}
DB_LIST CollectionDB::getAlbumTracks(const QString &album, const QString &artist, const KEY &orderBy, const BAE::W &order)
{
......@@ -704,6 +713,12 @@ DB_LIST CollectionDB::getOnlineTracks(const KEY &orderBy, const BAE::W &order)
return this->getDBData(queryTxt);
}
QStringList CollectionDB::getSourcesFolders()
{
auto data = this->getDBData("select * from folders order by strftime(\"%s\", addDate) desc");
return this->dataToList(data, BAE::KEY::URL);
}
DB_LIST CollectionDB::getMostPlayedTracks(const int &greaterThan, const int &limit, const KEY &orderBy, const BAE::W &order)
{
auto queryTxt = QString("SELECT * FROM %1 WHERE %2 > %3 ORDER BY %4 %5 LIMIT %6" ).arg(TABLEMAP[TABLE::TRACKS],
......
......@@ -71,6 +71,7 @@ public:
BAE::DB_LIST getDBData(const QStringList &urls);
BAE::DB_LIST getDBData(const QString &queryTxt);
QVariantList getDBDataQML(const QString &queryTxt);
QStringList dataToList(const BAE::DB_LIST &list, const BAE::KEY &key);
BAE::DB_LIST getAlbumTracks(const QString &album, const QString &artist, const BAE::KEY &orderBy = BAE::KEY::TRACK, const BAE::W &order = BAE::W::ASC);
BAE::DB_LIST getArtistTracks(const QString &artist, const BAE::KEY &orderBy = BAE::KEY::ALBUM, const BAE::W &order = BAE::W::ASC);
......@@ -82,6 +83,7 @@ public:
BAE::DB_LIST getRecentTracks(const int &limit = 50, const BAE::KEY &orderBy = BAE::KEY::ADD_DATE, const BAE::W &order = BAE::W::DESC);
BAE::DB_LIST getOnlineTracks(const BAE::KEY &orderBy = BAE::KEY::ADD_DATE, const BAE::W &order = BAE::W::DESC);
Q_INVOKABLE QStringList getSourcesFolders();
QStringList getTrackTags(const QString &path);
Q_INVOKABLE int getTrackStars(const QString &path);
......
......@@ -11,12 +11,6 @@ linux:unix:!android {
SOURCES += kde/notify.cpp \
kde/mpris2.cpp
} else:android {
message(Building for Android)
} else {
message("Unknown configuration")
}
HEADERS += \
......
......@@ -142,7 +142,7 @@ void BabeSettings::on_remove_clicked()
{
if(this->connection->removeSource(this->pathToRemove))
{
this->refreshCollectionPaths();
this->refreshCollection();
this->dirs.clear();
this->collectionWatcher();
this->watcher->removePaths(watcher->directories());
......@@ -152,13 +152,9 @@ void BabeSettings::on_remove_clicked()
}
}
void BabeSettings::refreshCollectionPaths()
void BabeSettings::refreshCollection()
{
// auto queryTxt = QString("SELECT %1 FROM %2").arg(BAE::KEYMAP[BAE::KEY::URL], BAE::TABLEMAP[BAE::TABLE::SOURCES]);
// for (auto track : this->connection->getDBData(queryTxt))
// {
// }
this->populateDB(this->connection->getSourcesFolders());
}
void BabeSettings::addToWatcher(QStringList paths)
......
......@@ -33,6 +33,7 @@ public:
~BabeSettings();
void checkCollectionBrainz(const bool &state);
void collectionWatcher();
void refreshCollection();
private slots:
void handleDirectoryChanged(const QString &dir);
......@@ -42,6 +43,7 @@ public slots:
void startBrainz(const bool &on, const uint &speed = BAE::SEG::THREE);
void populateDB(const QStringList &paths);
private:
FileLoader *fileLoader;
CollectionDB *connection;
......@@ -56,7 +58,6 @@ private:
QStringList dirs;
QFileSystemWatcher *watcher;
void refreshCollectionPaths();
void addToWatcher(QStringList paths);
signals:
......
......@@ -48,8 +48,7 @@ function queueTracks(tracks)
onQueue++
console.log(onQueue)
appendTracksAt(tracks, currentTrackIndex+1)
if(!isMobile)
bae.notify("Queue", tracks.length + " tracks added put on queue")
bae.notify("Queue", tracks.length + " tracks added put on queue")
}
}
}
......@@ -262,10 +261,7 @@ function addToPlaylist(urls, playlist)
// bae.trackPlaylist(urls[i], playlist)
if(!isMobile)
bae.notify(playlist, urls.length + " tracks added to the playlist:\n"+urls.join("\n"))
// else
// babeNotify.notify(urls.length + " tracks added to " +playlist)
bae.notify(playlist, urls.length + " tracks added to the playlist:\n"+urls.join("\n"))
}
}
......
......@@ -64,7 +64,7 @@ BabeMenu
{
id: sendToPopup
parent: babeTableRoot
leftPadding: 1
leftPadding: 1
rightPadding: 1
topPadding: contentMargins
bottomPadding: contentMargins
......@@ -148,6 +148,13 @@ BabeMenu
}
}
BabeMenuItem
{
text: qsTr("Show in folder...")
visible: !isMobile
onTriggered: bae.showFolder(list.model.get(list.currentIndex).url)
}
BabeMenuItem
{
text: "Edit..."
......@@ -157,7 +164,7 @@ BabeMenu
BabeMenuItem
{
text: "Send to..."
onTriggered: sendToPopup.open()
onTriggered: isMobile ? bae.sendTrack(list.model.get(list.currentIndex).url) : sendToPopup.open()
}
BabeMenuItem
......
......@@ -83,7 +83,7 @@ ToolBar
{
Layout.fillWidth: true
Layout.fillHeight: true
}
}
Item
{
......@@ -182,6 +182,31 @@ ToolBar
}
}
Item
{
Layout.fillHeight: true
Layout.fillWidth: true
Layout.maximumWidth: toolBarIconSize*2
Layout.maximumHeight: toolBarIconSize
BabeButton
{
anchors.centerIn: parent
iconName: "love"
iconColor: accent && currentIndex === viewsIndex.babeit ? accentColor : textColor
onClicked: babeViewClicked()
hoverEnabled: !isMobile
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered && !isMobile
ToolTip.text: qsTr("Babe")
}
}
Item
{
Layout.fillWidth: true
......
......@@ -52,11 +52,12 @@ Page
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
Text
TextEdit
{
id: lyricsText
width: infoRoot.width // ensure correct width
height: implicitHeight
readOnly: true
padding: 20
text: ""
color: darkForegroundColor
......@@ -65,6 +66,10 @@ Page
verticalAlignment: Qt.AlignVCenter
textFormat: Text.RichText
wrapMode: Text.Wrap
activeFocusOnPress : true
selectByMouse : true
cursorPosition :0
cursorVisible: true
}
}
......
......@@ -52,8 +52,19 @@ Kirigami.GlobalDrawer
Kirigami.Action
{
text: "Sources"
onTriggered: sourcesDialog.open()
text: qsTr("Collection"
)
Kirigami.Action
{
text: qsTr("Refresh")
onTriggered: bae.refreshCollection();
}
Kirigami.Action
{
text: "Sources"
onTriggered: sourcesDialog.open()
}
},
Kirigami.Action
......
......@@ -96,12 +96,10 @@ BabePopup
onOpened:
{
var map = bae.get("select * from folders order by strftime(\"%s\", addDate) desc")
for(var i in map)
{
console.log(map[i].url)
sources.model.append(map[i])
}
var folders = bae.getSourcesFolders()
for(var i in folders)
sources.model.append({url : folders[i