Commit 3fb53e20 authored by Johannes Schwab's avatar Johannes Schwab

client: Share recipes via intent on android

parent 64271bf0
......@@ -10,15 +10,44 @@
android:roundIcon=\"@mipmap/ic_launcher_round\"
android:label=\"OpenRecipes$$NAMEPOSTFIX\">
<activity android:configChanges=\"orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation\"
android:name=\"org.qtproject.qt5.android.bindings.QtActivity\"
android:name=\"org.jschwab.openrecipes.OpenRecipesActivity\"
android:label=\"@string/app_name\"
android:screenOrientation=\"unspecified\"
android:theme=\"@style/AppTheme\"
android:launchMode=\"singleTop\">
android:launchMode=\"singleTask\">
<intent-filter>
<action android:name=\"android.intent.action.MAIN\"/>
<category android:name=\"android.intent.category.LAUNCHER\"/>
</intent-filter>
<intent-filter>
<action android:name=\"android.intent.action.VIEW\"/>
<category android:name=\"android.intent.category.DEFAULT\"/>
<category android:name=\"android.intent.category.BROWSABLE\"/>
<data android:scheme=\"file\"/>
<data android:host=\"*\"/>
<data android:pathPattern=\".*\\\\.openrecipes\"/>
<data android:pathPattern=\".*\\\\..*\\\\.openrecipes\"/>
<data android:pathPattern=\".*\\\\..*\\\\..*\\\\.openrecipes\"/>
<data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\.openrecipes\"/>
</intent-filter>
<intent-filter>
<action android:name=\"android.intent.action.VIEW\"/>
<category android:name=\"android.intent.category.DEFAULT\"/>
<category android:name=\"android.intent.category.BROWSABLE\"/>
<data android:scheme=\"file\"/>
<data android:host=\"*\"/>
<data android:mimeType=\"*/*\"/>
<data android:pathPattern=\".*\\\\.openrecipes\"/>
<data android:pathPattern=\".*\\\\..*\\\\.openrecipes\"/>
<data android:pathPattern=\".*\\\\..*\\\\..*\\\\.openrecipes\"/>
<data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\.openrecipes\"/>
</intent-filter>
<intent-filter>
<action android:name=\"android.intent.action.VIEW\"/>
<category android:name=\"android.intent.category.DEFAULT\"/>
<category android:name=\"android.intent.category.BROWSABLE\"/>
<data android:mimeType=\"openrecipes/recipe_xml\"/>
</intent-filter>
<!-- Application arguments -->
<!-- meta-data android:name=\"android.app.arguments\" android:value=\"arg1 arg2 arg3\"/ -->
......@@ -72,6 +101,14 @@
<!-- extract android style -->
</activity>
<provider android:authorities=\"org.jschwab.openrecipes.provider\"
android:name=\"org.jschwab.openrecipes.XmlProvider\"
android:enabled=\"true\"
android:exported=\"true\"
android:icon=\"@mipmap/ic_launcher\"
android:roundIcon=\"@mipmap/ic_launcher_round\"
android:label=\"OpenRecipesXmlProvider$$NAMEPOSTFIX\" />
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
......
package org.jschwab.recipes;
package org.jschwab.openrecipes;
import android.content.ContentResolver;
import android.content.Context;
......@@ -11,9 +11,24 @@ import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.BufferedReader;
import java.lang.StringBuilder;
import java.lang.IllegalArgumentException;
import java.io.InputStreamReader;
public class Helpers {
public static String readFile(Uri uri, Context context) throws FileNotFoundException, IOException {
InputStream stream = context.getContentResolver().openInputStream(uri);
BufferedReader r = new BufferedReader(new InputStreamReader(stream));
StringBuilder sb = new StringBuilder();
String line;
while ((line = r.readLine()) != null) {
sb.append(line).append('\n');
}
stream.close();
return sb.toString();
}
public static String readImage(Uri uri, Context context) {
try {
InputStream stream = context.getContentResolver().openInputStream(uri);
......@@ -35,4 +50,14 @@ public class Helpers {
return "";
}
}
public static native boolean importRecipe(String recipeXml);
public static native String getRecipeXml(String recipeIdHex);
public static native String getRecipeName(String recipeIdHex);
static {
System.loadLibrary("openrecipes");
}
}
package org.jschwab.openrecipes;
import org.jschwab.openrecipes.Helpers;
import android.util.Log;
import android.os.Bundle;
import android.content.Intent;
import org.qtproject.qt5.android.bindings.QtActivity;
import java.io.FileNotFoundException;
import java.io.IOException;
import android.widget.Toast;
public class OpenRecipesActivity extends QtActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handleIntent(getIntent());
}
@Override
protected void onNewIntent(Intent i) {
handleIntent(i);
super.onNewIntent(i);
}
private void handleIntent(Intent i) {
if (i.getAction() == Intent.ACTION_VIEW) {
Log.d("OpenRecipes", "Got Intent.ACTION_VIEW");
boolean succ;
try {
String recipeXml = Helpers.readFile(i.getData(), this);
succ = Helpers.importRecipe(recipeXml);
} catch (FileNotFoundException e) {
succ = false;
} catch (IOException e) {
succ = false;
}
if (!succ) {
Toast.makeText(this, "Can't import recipe", 5).show();
//TODO translate
}
}
}
}
package org.jschwab.openrecipes;
import android.content.ContentProvider;
import android.os.ParcelFileDescriptor;
import android.net.Uri;
import android.content.ContentValues;
import android.os.Bundle;
import android.database.Cursor;
import java.io.FileNotFoundException;
import java.io.IOException;
import android.content.UriMatcher;
import java.lang.Thread;
import java.io.FileWriter;
import android.util.Log;
import java.util.List;
import android.database.MatrixCursor;
import android.provider.OpenableColumns;
import org.jschwab.openrecipes.Helpers;
public class XmlProvider extends ContentProvider {
private static Thread writeThread;
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.d("OpenRecipes", "XmlParser::query");
Matcher m = new Matcher();
if (m.match(uri) == Matcher.RECIPE_XML) {
List<String> seg = uri.getPathSegments();
if (seg.size() != 2 || !seg.get(0).equals("recipe_xml")) return null;
String recipeName = Helpers.getRecipeName(seg.get(1));
if (recipeName.length() == 0) recipeName = new String("unnamed");
MatrixCursor c = new MatrixCursor(new String[] {"_id", "_data", "mime_type", OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE});
c.addRow(new Object[] {0, uri, "openrecipes/recipe_xml", recipeName + ".openrecipes", 0 /*recipeXml.length()*/});
return c;
} else {
Log.i("OpenRecipes", "Got invalid uri");
return null;
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public String getType(Uri uri) {
Log.d("OpenRecipes", "XmlParser::getType for " + uri.toString());
Matcher m = new Matcher();
if (m.match(uri) == Matcher.RECIPE_XML) {
Log.d("OpenRecipes", "return type openrecipes/recipe_xml");
return "openrecipes/recipe_xml";
} else {
Log.d("OpenRecipes", "no type match");
return null;
}
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Log.d("OpenRecipes", "XmlParser::openFile " + uri.toString());
Matcher m = new Matcher();
if (m.match(uri) != Matcher.RECIPE_XML) throw new FileNotFoundException();
List<String> seg = uri.getPathSegments();
if (seg.size() != 2 || !seg.get(0).equals("recipe_xml")) throw new FileNotFoundException();
String recipeXml = Helpers.getRecipeXml(seg.get(1));
if (recipeXml.length() == 0) throw new FileNotFoundException();
ParcelFileDescriptor[] fd;
try {
fd = ParcelFileDescriptor.createPipe();
} catch (IOException e) {
return null;
}
writeThread = new WriteThread(fd[1], recipeXml);
writeThread.start();
return fd[0];
}
class Matcher extends UriMatcher {
static final int RECIPE_XML = 1;
public Matcher() {
super(NO_MATCH);
addURI("org.jschwab.openrecipes.provider", "recipe_xml/*", RECIPE_XML);
}
}
class WriteThread extends Thread {
ParcelFileDescriptor fd;
String recipeXml;
public WriteThread(ParcelFileDescriptor fd, String recipeXml) {
this.fd = fd;
this.recipeXml = recipeXml;
}
public void run() {
FileWriter fw = new FileWriter(fd.getFileDescriptor());
try {
fw.write(recipeXml, 0, recipeXml.length());
fw.close();
fd.close();
} catch (IOException e) {
Log.w("OpenRecipes", "Can't write recipe to pipe");
}
}
}
}
......@@ -15,9 +15,7 @@ HEADERS += src/SqlBackend.h \
src/SecureClientConnection.h \
src/DataStructs.h \
src/EncryptedItem.h \
src/Globals.h \
src/RecipeTcpServer.h \
src/RecipeTcpClient.h
src/Globals.h
SOURCES += src/main.cpp \
src/SqlBackend.cpp \
src/Backend.cpp \
......@@ -30,11 +28,12 @@ SOURCES += src/main.cpp \
src/RecvSyncKeyAsync.cpp \
src/SecureClientConnection.cpp \
src/EncryptedItem.cpp \
src/Globals.cpp \
src/RecipeTcpServer.cpp \
src/RecipeTcpClient.cpp
src/Globals.cpp
RESOURCES += client.qrc
android: HEADERS += src/Jni.h
android: SOURCES += src/Jni.cpp
QMAKE_SUBSTITUTES += \
../android-sources/AndroidManifest.xml.in
......
......@@ -50,55 +50,6 @@ Page {
}
}
ModalDialog {
id: confirmShareDialog
x: (mainWindow.width - width) / 2 - (Globals.mobile ? 0 : recipesListView.width)
standardButtons: Dialog.Ok | Dialog.Cancel
title: qsTr("Share recipe?")
ColumnLayout {
anchors.fill: parent
Label {
Layout.maximumWidth: mainWindow.width - 50
Layout.alignment: Qt.AlignHCenter
text: qsTr("Share this recipe with all people in the local network?")
wrapMode: Text.Wrap
}
}
onAccepted: shareDialog.open()
}
ModalDialog {
id: shareDialog
x: (mainWindow.width - width) / 2 - (Globals.mobile ? 0 : recipesListView.width)
standardButtons: Dialog.Close
title: qsTr("Sharing recipe…")
ColumnLayout {
anchors.fill: parent
ProgressBar {
Layout.alignment: Qt.AlignHCenter
indeterminate: true
}
Label {
Layout.maximumWidth: mainWindow.width - 50
Layout.alignment: Qt.AlignHCenter
text: qsTr("Close this dialog when sharing is finished.")
wrapMode: Text.Wrap
}
}
onOpened: backend.shareRecipe(recipeView.recipe)
onRejected: backend.stopSharing()
}
header: ToolBar {
RowLayout {
anchors.fill: parent
......@@ -114,7 +65,7 @@ Page {
ToolButton {
icon.name: "document-share"
icon.source: "qrc:/img/fallback-icons/document-share.svg"
onClicked: confirmShareDialog.open()
onClicked: backend.shareRecipe(recipeView.recipe)
ToolTip.text: qsTr("Share via network")
ToolTip.delay: 500
ToolTip.visible: hovered
......
......@@ -19,8 +19,8 @@
#include "Backend.h"
#include "SqlBackend.h"
#include "Recipe.h"
#include "RecipeTcpServer.h"
#include "RecipeTcpClient.h"
//#include "RecipeTcpServer.h"
//#include "RecipeTcpClient.h"
#include "SynchronizeAsync.h"
#include "SendSyncKeyAsync.h"
#include "RecvSyncKeyAsync.h"
......@@ -34,8 +34,6 @@
Backend::Backend()
:
recipeTcpServer(nullptr),
recipeTcpClient(nullptr),
synchronizeAsync(nullptr),
sendSyncKeyAsync(nullptr),
recvSyncKeyAsync(nullptr),
......@@ -62,19 +60,6 @@ QQmlListProperty<Recipe> Backend::getRecipesList() {
);
}
QQmlListProperty<FoundRecipe> Backend::getFoundRecipesList() {
return QQmlListProperty<FoundRecipe>(
this,
this,
[](QQmlListProperty<FoundRecipe> *list) {
return reinterpret_cast<Backend*>(list->data)->getFoundRecipesCount();
},
[](QQmlListProperty<FoundRecipe> *list, int pos) {
return reinterpret_cast<Backend*>(list->data)->getFoundRecipeAt(pos);
}
);
}
Recipe* Backend::getNewRecipe() {
return SqlBackend::addEmptyRecipe();
//TODO Who frees the memory?
......@@ -89,6 +74,7 @@ bool Backend::deleteRecipe(Recipe *recipe) {
}
}
/*
void Backend::shareRecipe(Recipe *recipe) {
if (recipeTcpServer) {
qDebug("Allready sharing a recipe...");
......@@ -99,59 +85,7 @@ void Backend::shareRecipe(Recipe *recipe) {
recipeTcpServer->setParent(this);
//TODO connect failed signal
}
void Backend::stopSharing() {
qDebug("Stop sharing...");
delete recipeTcpServer;
recipeTcpServer = nullptr;
}
void Backend::searchSharedRecipes() {
if (recipeTcpClient) {
qDebug("Allready searching for recipes...");
return;
}
qDebug() << "Start to search recipes...";
recipeTcpClient = new RecipeTcpClient();
recipeTcpClient->setParent(this);
connect(recipeTcpClient, &RecipeTcpClient::foundRecipe, [=](unsigned int id, const QString &name) {
foundRecipes.push_back(new FoundRecipe(this, id, name));
qDebug("foundRecipesListChanged()");
emit foundRecipesListChanged();
});
connect(recipeTcpClient, &RecipeTcpClient::lostRecipe, [=](unsigned int id) {
for (auto fr = foundRecipes.begin(); fr != foundRecipes.end(); ++fr) {
if ((*fr)->getId() == id) {
foundRecipes.erase(fr);
break;
}
}
emit foundRecipesListChanged();
});
//connect(recipeTcpClient, &RecipeTcpClient::failed, [=]() {}); TODO
}
void Backend::stopSearching() {
qDebug("Stop searching...");
if (recipeTcpClient) {
recipeTcpClient->deleteLater();
recipeTcpClient = nullptr;
}
if (!foundRecipes.empty()) {
for (auto &fr : foundRecipes) fr->deleteLater();
foundRecipes.clear();
emit foundRecipesListChanged();
}
}
void Backend::getRecipeFromServer(unsigned int id) {
assert(recipeTcpClient);
recipeTcpClient->getRecipeFromServer(id);
connect(recipeTcpClient, &RecipeTcpClient::done, [=]() {
stopSearching();
});
}
*/
void Backend::stopSynchronizing() {
assert(synchronizeAsync);
......@@ -333,14 +267,6 @@ int Backend::getRecipesCount() const {
return recipes.size();
}
FoundRecipe* Backend::getFoundRecipeAt(int pos) {
return foundRecipes.at(pos);
}
int Backend::getFoundRecipesCount() const {
return foundRecipes.size();
}
bool Backend::getSyncAvailable() const {
try {
SqlBackend::getSyncKey();
......@@ -381,7 +307,7 @@ void Backend::AndroidResultReceiver::handleActivityResult(int receiverRequestCod
qDebug() << "GetImage not successfull";
} else {
QAndroidJniObject uri = data.callObjectMethod("getData", "()Landroid/net/Uri;");
QAndroidJniObject img = QAndroidJniObject::callStaticObjectMethod("org/jschwab/recipes/Helpers", "readImage", "(Landroid/net/Uri;Landroid/content/Context;)Ljava/lang/String;", uri.object(), QtAndroid::androidContext().object());
QAndroidJniObject img = QAndroidJniObject::callStaticObjectMethod("org/jschwab/openrecipes/Helpers", "readImage", "(Landroid/net/Uri;Landroid/content/Context;)Ljava/lang/String;", uri.object(), QtAndroid::androidContext().object());
emit backend->openImage(img.toString());
}
}
......@@ -402,4 +328,31 @@ void Backend::requestImage() {
QtAndroid::startActivity(intent.handle(), static_cast<int>(AndroidRequestCodes::GetImage), &androidResultReceiver);
}
void Backend::shareRecipe(Recipe *recipe) {
const QString idHex = QString::fromLatin1(recipe->getId().toHex()).toUpper();
/*
QAndroidJniObject idHexJni = QAndroidJniObject::fromString(idHex);
QAndroidJniObject nameJni = QAndroidJniObject::fromString(recipe->getName());
QAndroidJniObject xmlJni = QAndroidJniObject::fromString(QString::fromUtf8(recipe->toXML()));
QAndroidJniObject::callStaticMethod<void>("org/jschwab/openrecipes/XmlProvider", "provideRecipe", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", idHexJni.object<jstring>(), nameJni.object<jstring>(), xmlJni.object<jstring>());
*/
QAndroidIntent intent("android.intent.action.SEND");
QAndroidJniObject metaType = QAndroidJniObject::fromString("openrecipes/recipe_xml");
intent.handle().callObjectMethod("setType", "(Ljava/lang/String;)Landroid/content/Intent;", metaType.object<jstring>());
QAndroidJniObject contentUriString = QAndroidJniObject::fromString(QString("content://org.jschwab.openrecipes.provider/recipe_xml/%1").arg(idHex));
QAndroidJniObject contentUri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", contentUriString.object<jstring>());
QAndroidJniObject extraStream = QAndroidJniObject::getStaticObjectField<jstring>("android/content/Intent", "EXTRA_STREAM");
intent.handle().callObjectMethod("putExtra", "(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;", extraStream.object<jstring>(), contentUri.object());
QAndroidJniObject title = QAndroidJniObject::fromString(tr("Share recipe"));
QAndroidJniObject chooser = QAndroidJniObject::callStaticObjectMethod("android/content/Intent", "createChooser", "(Landroid/content/Intent;Ljava/lang/CharSequence;)Landroid/content/Intent;", intent.handle().object(), title.object<jstring>());
QtAndroid::startActivity(chooser, 0);
}
#endif
......@@ -30,8 +30,8 @@
#include "Recipe.h"
#include "SqlBackend.h"
class RecipeTcpServer;
class RecipeTcpClient;
//class RecipeTcpServer;
//class RecipeTcpClient;
class SynchronizeAsync;
class SendSyncKeyAsync;
class RecvSyncKeyAsync;
......@@ -70,7 +70,6 @@ class FoundRecipe : public QObject {
class Backend : public QObject {
Q_OBJECT
Q_PROPERTY(QQmlListProperty<Recipe> recipesList READ getRecipesList NOTIFY recipesListChanged)
Q_PROPERTY(QQmlListProperty<FoundRecipe> foundRecipesList READ getFoundRecipesList NOTIFY foundRecipesListChanged)
Q_PROPERTY(QString syncKeyHex READ getSyncKeyHex NOTIFY syncSettingsChanged);
Q_PROPERTY(bool syncAvailable READ getSyncAvailable NOTIFY syncSettingsChanged);
Q_PROPERTY(QString customSyncServerAddr READ getCustomSyncServerAddr WRITE setCustomSyncServerAddr NOTIFY syncSettingsChanged);
......@@ -79,14 +78,11 @@ class Backend : public QObject {
Q_PROPERTY(bool useCustomSyncServer READ getUseCustomSyncServer WRITE setUseCustomSyncServer NOTIFY syncSettingsChanged);
private:
RecipeTcpServer *recipeTcpServer;
RecipeTcpClient *recipeTcpClient;
SynchronizeAsync *synchronizeAsync;
SendSyncKeyAsync *sendSyncKeyAsync;
RecvSyncKeyAsync *recvSyncKeyAsync;
std::unique_ptr<SqlTransaction> transaction;
QList<Recipe*> recipes;
QList<FoundRecipe*> foundRecipes;
void sortRecipes();
......@@ -110,11 +106,8 @@ class Backend : public QObject {
public:
Backend();
QQmlListProperty<Recipe> getRecipesList();
QQmlListProperty<FoundRecipe> getFoundRecipesList();
Recipe* getRecipeAt(int pos);
int getRecipesCount() const;
FoundRecipe* getFoundRecipeAt(int pos);
int getFoundRecipesCount() const;
QString getSyncKeyHex() const;
bool getUseCustomSyncServer() const;
QString getCustomSyncServerAddr() const;
......@@ -131,12 +124,8 @@ class Backend : public QObject {
Q_INVOKABLE Recipe* getNewRecipe();
//Q_INVOKABLE Ingredient* getNewIngredient();
//Q_INVOKABLE bool recipeToDB(Recipe *recipe);
Q_INVOKABLE bool deleteRecipe(Recipe *recipe);
Q_INVOKABLE void shareRecipe(Recipe *recipe);
Q_INVOKABLE void stopSharing();
Q_INVOKABLE void searchSharedRecipes();
Q_INVOKABLE void stopSearching();
Q_INVOKABLE void getRecipeFromServer(unsigned int id);
Q_INVOKABLE bool deleteRecipe(Recipe *recipe);
Q_INVOKABLE void synchronize();
Q_INVOKABLE void stopSynchronizing();
Q_INVOKABLE void startTransaction();
......
#include "Jni.h"
#include "SqlBackend.h"
#include "Recipe.h"
#include <QAndroidJniObject>
#include <QCoreApplication>
JNIEXPORT jboolean JNICALL Java_org_jschwab_openrecipes_Helpers_importRecipe (JNIEnv *, jclass, jstring recipeXml) {
QAndroidJniObject xml = QAndroidJniObject::fromLocalRef(recipeXml);
try {
if (QCoreApplication::startingUp()) {
Jni::importBufferMutex.lock();
Jni::importBuffer.push_front(xml.toString());
Jni::importBufferMutex.unlock();
} else {
qInfo() << "Importing recipes";
Recipe *r = new Recipe(xml.toString(), nullptr);
delete r;
}
} catch (const InternalError &e) {
return false;
}
return true;
}
JNIEXPORT jstring JNICALL Java_org_jschwab_openrecipes_Helpers_getRecipeXml (JNIEnv *env, jclass, jstring recipeIdHex) {
if (QCoreApplication::startingUp()) return reinterpret_cast<jstring>(env->NewLocalRef(QAndroidJniObject::fromString("").object<jstring>()));
QAndroidJniObject idHex = QAndroidJniObject::fromLocalRef(recipeIdHex);
try {
Recipe *r = SqlBackend::getRecipe(QByteArray::fromHex(idHex.toString().toLatin1()));
QAndroidJniObject xml = QAndroidJniObject::fromString(r->toXML());
delete r;
return reinterpret_cast<jstring>(env->NewLocalRef(xml.object<jstring>()));
} catch (const SqlNoResult &e) {
return reinterpret_cast<jstring>(env->NewLocalRef(QAndroidJniObject::fromString("").object<jstring>()));
}
}
JNIEXPORT jstring JNICALL Java_org_jschwab_openrecipes_Helpers_getRecipeName (JNIEnv *env, jclass, jstring recipeIdHex) {
if (QCoreApplication::startingUp()) return reinterpret_cast<jstring>(env->NewLocalRef(QAndroidJniObject::fromString("").object<jstring>()));
QAndroidJniObject idHex = QAndroidJniObject::fromLocalRef(recipeIdHex);
try {
QString name = SqlBackend::getNameForRecipe(QByteArray::fromHex(idHex.toString().toLatin1()));
return reinterpret_cast<jstring>(env->NewLocalRef(QAndroidJniObject::fromString(name).object<jstring>()));
} catch (const SqlNoResult &e) {
return reinterpret_cast<jstring>(env->NewLocalRef(QAndroidJniObject::fromString("").object<jstring>()));
}
}
std::forward_list<QString> Jni::importBuffer;
QMutex Jni::importBufferMutex;
#ifndef JNI_H
#define JNI_H
#include <jni.h>
#include <forward_list>
#include <QString>
#include <QMutex>
class Jni {
public:
static std::forward_list<QString> importBuffer;
static QMutex importBufferMutex;
};
extern "C" {
JNIEXPORT jboolean JNICALL Java_org_jschwab_openrecipes_Helpers_importRecipe (JNIEnv *, jclass, jstring);
JNIEXPORT jstring JNICALL Java_org_jschwab_openrecipes_Helpers_getRecipeXml (JNIEnv *, jclass, jstring);
JNIEXPORT jstring JNICALL Java_org_jschwab_openrecipes_Helpers_getRecipeName (JNIEnv *, jclass, jstring);
}
#endif //JNI_H
......@@ -44,7 +44,7 @@ Recipe::Recipe(const QByteArray &id, const QList<Ingredient*> ingredients)
for (auto &i : ingredients) i->setParent(this);
}
Recipe::Recipe(const QByteArray &xml, QObject *parent)
Recipe::Recipe(const QString &xml, QObject *parent)
:
QObject(parent)
{
......@@ -282,13 +282,13 @@ void Recipe::refreshIngredients() {
emit dataChanged();
}
QByteArray Recipe::toXML() const {
QString Recipe::toXML() const {
QString xml;
QTextStream x(&xml);
x << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>";
x << "<recipe ";
x << "name=\"" << getName() << "\" ";
x << "image=\"" << getImage() << "\" ";
x << "image=\"" << (hasImage() ? getImage() : "") << "\" ";
x << "portions=\"" << getPortions() << "\" ";
x << "instruction=\"" << getInstruction() << "\">";
for (const auto &i : ingredients) {
......@@ -298,5 +298,5 @@ QByteArray Recipe::toXML() const {
x << "article=\"" << i->getArticle() << "\" /> ";
}
x << "</recipe>";
return xml.toUtf8();
return xml;
}
......@@ -48,7 +48,7 @@ class Recipe : public QObject {
public:
Recipe(const QByteArray &id, QList<Ingredient*> ingredi