diff --git a/libresapi/src/api/ChatHandler.cpp b/libresapi/src/api/ChatHandler.cpp index 1a5fd6e6c..00c5c7fb2 100644 --- a/libresapi/src/api/ChatHandler.cpp +++ b/libresapi/src/api/ChatHandler.cpp @@ -161,6 +161,7 @@ ChatHandler::ChatHandler(StateTokenServer *sts, RsNotify *notify, RsMsgs *msgs, mMsgStateToken = mStateTokenServer->getNewToken(); mLobbiesStateToken = mStateTokenServer->getNewToken(); mUnreadMsgsStateToken = mStateTokenServer->getNewToken(); + mInvitationsStateToken = mStateTokenServer->getNewToken(); addResourceHandler("*", this, &ChatHandler::handleWildcard); addResourceHandler("lobbies", this, &ChatHandler::handleLobbies); @@ -169,6 +170,9 @@ ChatHandler::ChatHandler(StateTokenServer *sts, RsNotify *notify, RsMsgs *msgs, addResourceHandler("unsubscribe_lobby", this, &ChatHandler::handleUnsubscribeLobby); addResourceHandler("autosubscribe_lobby", this, &ChatHandler::handleAutoSubsribeLobby); addResourceHandler("clear_lobby", this, &ChatHandler::handleClearLobby); + addResourceHandler("invite_to_lobby", this, &ChatHandler::handleInviteToLobby); + addResourceHandler("get_invitations_to_lobby", this, &ChatHandler::handleGetInvitationsToLobby); + addResourceHandler("answer_to_invitation", this, &ChatHandler::handleAnswerToInvitation); addResourceHandler("lobby_participants", this, &ChatHandler::handleLobbyParticipants); addResourceHandler("messages", this, &ChatHandler::handleMessages); addResourceHandler("send_message", this, &ChatHandler::handleSendMessage); @@ -229,6 +233,15 @@ void ChatHandler::notifyChatLobbyEvent(uint64_t lobby_id, uint32_t event_type, } } +void ChatHandler::notifyListChange(int list, int type) +{ + if(list == NOTIFY_LIST_CHAT_LOBBY_INVITATION) + { + RS_STACK_MUTEX(mMtx); /********** LOCKED **********/ + mStateTokenServer->replaceToken(mInvitationsStateToken); + } +} + void ChatHandler::tick() { RS_STACK_MUTEX(mMtx); /********** LOCKED **********/ @@ -936,6 +949,70 @@ void ChatHandler::handleClearLobby(Request &req, Response &resp) resp.setOk(); } +void ChatHandler::handleInviteToLobby(Request& req, Response& resp) +{ + std::string chat_id; + std::string pgp_id; + req.mStream << makeKeyValueReference("chat_id", chat_id); + req.mStream << makeKeyValueReference("pgp_id", pgp_id); + + ChatId chatId(chat_id); + RsPgpId pgpId(pgp_id); + + std::list peerIds; + mRsPeers->getAssociatedSSLIds(pgpId, peerIds); + + for(std::list::iterator it = peerIds.begin(); it != peerIds.end(); it++) + mRsMsgs->invitePeerToLobby(chatId.toLobbyId(), (*it)); + + resp.setOk(); +} + +void ChatHandler::handleGetInvitationsToLobby(Request& req, Response& resp) +{ + std::list invites; + mRsMsgs->getPendingChatLobbyInvites(invites); + + resp.mDataStream.getStreamToMember(); + for(std::list::const_iterator it = invites.begin(); it != invites.end(); ++it) + { + resp.mDataStream.getStreamToMember() + << makeKeyValue("peer_id", (*it).peer_id.toStdString()) + << makeKeyValue("lobby_id", (*it).lobby_id) + << makeKeyValue("lobby_name", (*it).lobby_name) + << makeKeyValue("lobby_topic", (*it).lobby_topic); + } + + resp.mStateToken = mInvitationsStateToken; + resp.setOk(); +} + +void ChatHandler::handleAnswerToInvitation(Request& req, Response& resp) +{ + ChatLobbyId lobbyId = 0; + req.mStream << makeKeyValueReference("lobby_id", lobbyId); + + bool join; + req.mStream << makeKeyValueReference("join", join); + + std::string gxs_id; + req.mStream << makeKeyValueReference("gxs_id", gxs_id); + RsGxsId gxsId(gxs_id); + + if(join) + { + if(rsMsgs->acceptLobbyInvite(lobbyId, gxsId)) + resp.setOk(); + else + resp.setFail(); + } + else + { + rsMsgs->denyLobbyInvite(lobbyId); + resp.setOk(); + } +} + ResponseTask* ChatHandler::handleLobbyParticipants(Request &req, Response &resp) { RS_STACK_MUTEX(mMtx); /********** LOCKED **********/ diff --git a/libresapi/src/api/ChatHandler.h b/libresapi/src/api/ChatHandler.h index 9cda3e551..cac28271d 100644 --- a/libresapi/src/api/ChatHandler.h +++ b/libresapi/src/api/ChatHandler.h @@ -35,6 +35,8 @@ public: virtual void notifyChatLobbyEvent (uint64_t /* lobby id */, uint32_t /* event type */ , const RsGxsId& /* nickname */,const std::string& /* any string */); + virtual void notifyListChange(int list, int type); + // from tickable virtual void tick(); @@ -122,6 +124,9 @@ private: void handleSubscribeLobby(Request& req, Response& resp); void handleUnsubscribeLobby(Request& req, Response& resp); void handleAutoSubsribeLobby(Request& req, Response& resp); + void handleInviteToLobby(Request& req, Response& resp); + void handleGetInvitationsToLobby(Request& req, Response& resp); + void handleAnswerToInvitation(Request& req, Response& resp); void handleClearLobby(Request& req, Response& resp); ResponseTask* handleLobbyParticipants(Request& req, Response& resp); void handleMessages(Request& req, Response& resp); @@ -162,6 +167,7 @@ private: std::map mLobbyParticipantsInfos; StateToken mUnreadMsgsStateToken; + StateToken mInvitationsStateToken; }; } // namespace resource_api diff --git a/libresapi/src/api/PeersHandler.cpp b/libresapi/src/api/PeersHandler.cpp index 4631ccf7c..d71ceaf81 100644 --- a/libresapi/src/api/PeersHandler.cpp +++ b/libresapi/src/api/PeersHandler.cpp @@ -601,19 +601,30 @@ void PeersHandler::handleWildcard(Request &req, Response &resp) } RsPeerId peer_id; RsPgpId pgp_id; + std::string cleanCert; + int error_code; std::string error_string; - if(mRsPeers->loadCertificateFromString(cert_string, peer_id, pgp_id, error_string) - && mRsPeers->addFriend(peer_id, pgp_id, flags)) - { - ok = true; - resp.mDataStream << makeKeyValueReference("pgp_id", pgp_id); - resp.mDataStream << makeKeyValueReference("peer_id", peer_id); - } - else - { - resp.mDebug << "Error: failed to add peer" << std::endl; - resp.mDebug << error_string << std::endl; - } + + if (mRsPeers->cleanCertificate(cert_string, cleanCert, error_code)) + { + if(mRsPeers->loadCertificateFromString(cert_string, peer_id, pgp_id, error_string) + && mRsPeers->addFriend(peer_id, pgp_id, flags)) + { + ok = true; + resp.mDataStream << makeKeyValueReference("pgp_id", pgp_id); + resp.mDataStream << makeKeyValueReference("peer_id", peer_id); + } + else + { + resp.mDebug << "Error: failed to add peer" << std::endl; + resp.mDebug << error_string << std::endl; + } + } + else + { + resp.mDebug << "Error: failed to add peer" << std::endl; + resp.mDebug << error_code << std::endl; + } } } if(ok) diff --git a/libretroshare/src/ft/ftcontroller.cc b/libretroshare/src/ft/ftcontroller.cc index b50a52ed3..a349f8d1b 100644 --- a/libretroshare/src/ft/ftcontroller.cc +++ b/libretroshare/src/ft/ftcontroller.cc @@ -868,7 +868,7 @@ bool ftController::alreadyHaveFile(const RsFileHash& hash, FileInfo &info) return true ; // check for file lists - if (mSearch) return false; + if (!mSearch) return false; if (mSearch->search(hash, RS_FILE_HINTS_LOCAL | RS_FILE_HINTS_EXTRA | RS_FILE_HINTS_SPEC_ONLY, info)) return true ; diff --git a/libretroshare/src/gxs/gxstokenqueue.cc b/libretroshare/src/gxs/gxstokenqueue.cc index e0cbdf46d..b857ee1a8 100644 --- a/libretroshare/src/gxs/gxstokenqueue.cc +++ b/libretroshare/src/gxs/gxstokenqueue.cc @@ -31,7 +31,7 @@ bool GxsTokenQueue::queueRequest(uint32_t token, uint32_t req_type) { - RsStackMutex stack(mQueueMtx); /********** STACK LOCKED MTX ******/ + RS_STACK_MUTEX(mQueueMtx); mQueue.push_back(GxsTokenQueueItem(token, req_type)); return true; } diff --git a/libretroshare/src/gxs/rsgenexchange.cc b/libretroshare/src/gxs/rsgenexchange.cc index e48988e33..eae9dab4d 100644 --- a/libretroshare/src/gxs/rsgenexchange.cc +++ b/libretroshare/src/gxs/rsgenexchange.cc @@ -1188,26 +1188,30 @@ bool RsGenExchange::getGroupMeta(const uint32_t &token, std::list metaL; bool ok = mDataAccess->getGroupSummary(token, metaL); - std::list::iterator lit = metaL.begin(); RsGroupMetaData m; - for(; lit != metaL.end(); ++lit) + + for( std::list::iterator lit = metaL.begin(); lit != metaL.end(); ++lit) { RsGxsGrpMetaData& gMeta = *(*lit); + m = gMeta; RsGroupNetworkStats sts ; - if(mNetService != NULL && mNetService->getGroupNetworkStats((*lit)->mGroupId,sts)) - { - m.mPop = sts.mSuppliers ; - m.mVisibleMsgCount = sts.mMaxVisibleCount ; - } - else - { - m.mPop= 0 ; - m.mVisibleMsgCount = 0 ; - } + if(mNetService != NULL && mNetService->getGroupNetworkStats(gMeta.mGroupId,sts)) + { + m.mPop = sts.mSuppliers ; + m.mVisibleMsgCount = sts.mMaxVisibleCount ; - groupInfo.push_back(m); + if((!(IS_GROUP_SUBSCRIBED(gMeta.mSubscribeFlags))) || gMeta.mLastPost == 0) + m.mLastPost = sts.mLastGroupModificationTS ; + } + else + { + m.mPop= 0 ; + m.mVisibleMsgCount = 0 ; + } + + groupInfo.push_back(m); delete (*lit); } @@ -1369,6 +1373,14 @@ bool RsGenExchange::getGroupData(const uint32_t &token, std::vectormeta.mPop = sts.mSuppliers; gItem->meta.mVisibleMsgCount = sts.mMaxVisibleCount; + + // When the group is not subscribed, the last post value is not updated, because there's no message stored. As a consequence, + // we rely on network statistics to give this value, but it is not as accurate as if it was locally computed, because of blocked + // posts, friends not available, sync delays, etc. Similarly if the group has just been subscribed, the last post info is probably + // uninitialised, so we will it too. + + if((!(IS_GROUP_SUBSCRIBED(gItem->meta.mSubscribeFlags))) || gItem->meta.mLastPost == 0) + gItem->meta.mLastPost = sts.mLastGroupModificationTS ; } else { @@ -1376,12 +1388,6 @@ bool RsGenExchange::getGroupData(const uint32_t &token, std::vectormeta.mVisibleMsgCount = 0; } - // When the group is not subscribed, the last post value is not updated, because there's no message stored. As a consequence, - // we rely on network statistics to give this value, but it is not as accurate as if it was locally computed, because of blocked - // posts, friends not available, sync delays, etc. - - if(!(IS_GROUP_SUBSCRIBED(gItem->meta.mSubscribeFlags))) - gItem->meta.mLastPost = sts.mLastGroupModificationTS ; // Also check the group privacy flags. A while ago, it as possible to publish a group without privacy flags. Now it is not possible anymore. // As a consequence, it's important to supply a correct value in this flag before the data can be edited/updated. diff --git a/libretroshare/src/services/p3idservice.cc b/libretroshare/src/services/p3idservice.cc index 2e3da2a2b..a1dfc51d5 100644 --- a/libretroshare/src/services/p3idservice.cc +++ b/libretroshare/src/services/p3idservice.cc @@ -4397,32 +4397,31 @@ void p3IdService::handleResponse(uint32_t token, uint32_t req_type) // stuff. switch(req_type) { - case GXSIDREQ_CACHEOWNIDS: - cache_load_ownids(token); - break; - case GXSIDREQ_CACHELOAD: - cache_load_for_token(token); - break; - case GXSIDREQ_PGPHASH: - pgphash_handlerequest(token); - break; - case GXSIDREQ_RECOGN: - recogn_handlerequest(token); - break; - case GXSIDREQ_CACHETEST: - cachetest_handlerequest(token); - break; - case GXSIDREQ_OPINION: - opinion_handlerequest(token); - break; - case GXSIDREQ_SERIALIZE_TO_MEMORY: - handle_get_serialized_grp(token) ; - - default: - /* error */ - std::cerr << "p3IdService::handleResponse() Unknown Request Type: " << req_type; - std::cerr << std::endl; - break; + case GXSIDREQ_CACHEOWNIDS: + cache_load_ownids(token); + break; + case GXSIDREQ_CACHELOAD: + cache_load_for_token(token); + break; + case GXSIDREQ_PGPHASH: + pgphash_handlerequest(token); + break; + case GXSIDREQ_RECOGN: + recogn_handlerequest(token); + break; + case GXSIDREQ_CACHETEST: + cachetest_handlerequest(token); + break; + case GXSIDREQ_OPINION: + opinion_handlerequest(token); + break; + case GXSIDREQ_SERIALIZE_TO_MEMORY: + handle_get_serialized_grp(token); + break; + default: + std::cerr << "p3IdService::handleResponse() Unknown Request Type: " + << req_type << std::endl; + break; } } diff --git a/retroshare-gui/src/gui/Identity/IdDialog.ui b/retroshare-gui/src/gui/Identity/IdDialog.ui index 1c52802ef..b75154361 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.ui +++ b/retroshare-gui/src/gui/Identity/IdDialog.ui @@ -6,8 +6,8 @@ 0 0 - 1048 - 779 + 1573 + 1177 @@ -283,8 +283,8 @@ 0 0 - 717 - 692 + 1372 + 1000 @@ -569,8 +569,9 @@ border-image: url(:/images/closepressed.png) <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Your own opinion about an identity rules the visibility of that identity for yourself and your friend nodes. Your own opinion is shared among friends and used to compute a reputation score: If your opinion about an identity is neutral, the reputation score is the average of your friend's opinions. If not, your own opinion gives the score.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The overall score is used in chat lobbies, forums and channels to decide on the actions to take for each specific identity. When the overall score is lower than -0.6, the identity is banned, which prevents all messages and forums/channels authored by this identity to be forwarded, both ways. Some forums also have special anti-spam flags that require a higher reputation level, making them more sensitive to bad opinions. Banned identities gradually lose their activity and eventually disappear (after 30 days). </p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Your own opinion about an identity rules the visibility of that identity for yourself and your friend nodes. Your own opinion is shared among friends and used to compute a reputation score: If your opinion about an identity is neutral, the reputation score is the difference between friend's positive and negative opinions. If not, your own opinion gives the score.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The overall score is used in chat lobbies, forums and channels to decide on the actions to take for each specific identity. When the overall score is lower than -1, the identity is banned, which prevents all messages and forums/channels authored by this identity to be forwarded, both ways. Some forums also have special anti-spam flags that require a non negative reputation level, making them more sensitive to bad opinions. Banned identities gradually lose their activity and eventually disappear (after 5 days).</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">You can change the thresholds and the time of inactivity to delete identities in preferences -&gt; people. </p> <p style="-qt-paragraph-type:empty; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> diff --git a/retroshare-gui/src/gui/RetroShareLink.cpp b/retroshare-gui/src/gui/RetroShareLink.cpp index c3d2c0450..3c64e9021 100644 --- a/retroshare-gui/src/gui/RetroShareLink.cpp +++ b/retroshare-gui/src/gui/RetroShareLink.cpp @@ -1275,8 +1275,10 @@ static void processList(const QStringList &list, const QString &textSingular, co /* make path for downloaded file */ std::string path; path = fi.path;//Shared files has path with filename included - if (fi.downloadStatus == FT_STATE_COMPLETE) - path = fi.path + "/" + fi.fname; + + //Seems that all FileInfo get .path==filepath+filename + //if (fi.downloadStatus == FT_STATE_COMPLETE) + // path = fi.path + "/" + fi.fname; QFileInfo qinfo; qinfo.setFile(QString::fromUtf8(path.c_str())); diff --git a/retroshare-gui/src/gui/settings/ChannelPage.cpp b/retroshare-gui/src/gui/settings/ChannelPage.cpp index 199dd5b3a..27f71e8e6 100644 --- a/retroshare-gui/src/gui/settings/ChannelPage.cpp +++ b/retroshare-gui/src/gui/settings/ChannelPage.cpp @@ -22,6 +22,7 @@ #include "ChannelPage.h" #include "rsharesettings.h" #include "util/misc.h" +#include "gui/notifyqt.h" ChannelPage::ChannelPage(QWidget * parent, Qt::WindowFlags flags) : ConfigPage(parent, flags) @@ -33,10 +34,14 @@ ChannelPage::ChannelPage(QWidget * parent, Qt::WindowFlags flags) ui.groupFrameSettingsWidget->setOpenAllInNewTabText(tr("Open each channel in a new tab")); ui.groupFrameSettingsWidget->setType(GroupFrameSettings::Channel) ; - connect(ui.loadThreadCheckBox,SIGNAL(toggled(bool)),this,SLOT(updateLoadThread)) ; + connect(ui.loadThreadCheckBox,SIGNAL(toggled(bool)),this,SLOT(updateLoadThread())) ; } -void ChannelPage::updateLoadThread() { Settings->setChannelLoadThread(ui.loadThreadCheckBox->isChecked()); } +void ChannelPage::updateLoadThread() +{ + Settings->setChannelLoadThread(ui.loadThreadCheckBox->isChecked()); + NotifyQt::getInstance()->notifySettingsChanged(); +} ChannelPage::~ChannelPage() { diff --git a/retroshare-gui/src/gui/settings/rsharesettings.cpp b/retroshare-gui/src/gui/settings/rsharesettings.cpp index f550c66be..1ad2a76d3 100644 --- a/retroshare-gui/src/gui/settings/rsharesettings.cpp +++ b/retroshare-gui/src/gui/settings/rsharesettings.cpp @@ -1066,7 +1066,7 @@ void RshareSettings::setForumLoadEmoticons(bool value) /* Channel */ bool RshareSettings::getChannelLoadThread() { - return valueFromGroup("Channel", "LoadThread", true).toBool(); + return valueFromGroup("Channel", "LoadThread", false).toBool(); } void RshareSettings::setChannelLoadThread(bool value) diff --git a/retroshare-qml-app/src/ContactDetails.qml b/retroshare-qml-app/src/ContactDetails.qml index 99f718f74..5f384eb2e 100644 --- a/retroshare-qml-app/src/ContactDetails.qml +++ b/retroshare-qml-app/src/ContactDetails.qml @@ -29,26 +29,51 @@ Item property bool is_contact: cntDt.md.is_contact property bool isOwn: cntDt.md.own - - Text + Button { - id: meText + id: avatarPicker - text: "Yourself" + text: "Change your Avatar" visible: isOwn anchors.top: parent.top - anchors.topMargin: 6 anchors.horizontalCenter: parent.horizontalCenter + + onClicked: + { + fileChooser.open() + } + CustomFileChooser + { + id: fileChooser + onResultFileChanged: + { + console.log("Result file changed! " , resultFile) + + var base64Image = androidImagePicker.imageToBase64(resultFile) + + rsApi.request("/identity/set_avatar", JSON.stringify({"gxs_id": cntDt.md.gxs_id, "avatar": base64Image }), + function (par) + { + var jP = JSON.parse(par.response) + if (jP.returncode === "ok") + { + console.log("Avatar changed! ") + topFace.getDetails() + } + }) + } + } } + AvatarOrColorHash { id: topFace gxs_id: cntDt.md.gxs_id - anchors.top: meText.bottom + anchors.top: (isOwn)? avatarPicker.bottom : parent.top anchors.topMargin: 6 anchors.horizontalCenter: parent.horizontalCenter } @@ -82,22 +107,12 @@ Item Image { - source: - { - if (isOwn) - { - "qrc:/icons/keyring.svg" - } - else - { - cntDt.is_contact ? - "qrc:/icons/rating.svg" : - "qrc:/icons/rating-unrated.svg" - } - } - height: parent.height - 4 - sourceSize.height: height + source: cntDt.is_contact ? + "qrc:/icons/rating.svg" : + "qrc:/icons/rating-unrated.svg" + height: parent.height -4 fillMode: Image.PreserveAspectFit + sourceSize.height: height anchors.verticalCenter: parent.verticalCenter MouseArea @@ -106,13 +121,10 @@ Item onClicked: { - if (!isOwn) - { - var jDt = JSON.stringify({gxs_id: cntDt.md.gxs_id}) - if(cntDt.is_contact) - rsApi.request("/identity/remove_contact", jDt, tgCt) - else rsApi.request("/identity/add_contact", jDt, tgCt) - } + var jDt = JSON.stringify({gxs_id: cntDt.md.gxs_id}) + if(cntDt.is_contact) + rsApi.request("/identity/remove_contact", jDt, tgCt) + else rsApi.request("/identity/add_contact", jDt, tgCt) } function tgCt() { cntDt.is_contact = !cntDt.is_contact } diff --git a/retroshare-qml-app/src/android/AndroidManifest.xml b/retroshare-qml-app/src/android/AndroidManifest.xml index 321e1bd15..fb102300e 100644 --- a/retroshare-qml-app/src/android/AndroidManifest.xml +++ b/retroshare-qml-app/src/android/AndroidManifest.xml @@ -207,4 +207,8 @@ + + + + diff --git a/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java index 8d3479f95..365778142 100644 --- a/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java +++ b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java @@ -19,17 +19,25 @@ package org.retroshare.android.qml_app; import android.app.ActivityManager; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.os.Bundle; +import android.provider.MediaStore; import android.util.Log; +import android.net.Uri; + import org.qtproject.qt5.android.bindings.QtActivity; import org.retroshare.android.qml_app.jni.NativeCalls; public class RetroShareQmlActivity extends QtActivity { + + static final int PICK_PHOTO = 1; + @Override public void onCreate(Bundle savedInstanceState) { @@ -84,4 +92,88 @@ public class RetroShareQmlActivity extends QtActivity return true; return false; } + + private Uri capturedImageURI; + + public void openImagePicker() + { + Log.i("RetroShareQmlActivity", "openImagePicker()"); + + Intent pickIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + pickIntent.setType("image/*"); + + ContentValues values = new ContentValues(); + values.put(MediaStore.Images.Media.TITLE, "Retroshare Avatar"); + capturedImageURI = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + Intent takePicture = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + takePicture.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageURI); + + Intent chooserIntent = Intent.createChooser(pickIntent, "Select Image"); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] {takePicture}); + + startActivityForResult( chooserIntent, PICK_PHOTO); + }; + + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + Log.i("RetroShareQmlActivity", "onActivityResult()" + String.valueOf(requestCode)); + + if (resultCode == RESULT_OK) + { + if (requestCode == PICK_PHOTO) + { + final boolean isCamera; + + if (data == null) + { + isCamera = true; + } + else + { + final String action = data.getAction(); + if (action == null) + { + isCamera = false; + } + else + { + isCamera = action.equals(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); + } + } + + Uri selectedImageUri; + if (isCamera) + { + selectedImageUri = capturedImageURI; + } + else + { + selectedImageUri = data == null ? null : data.getData(); + } + + String uri = getRealPathFromURI(selectedImageUri); + if (uri != null) + { + Log.i("RetroShareQmlActivity", "Image path from uri found!" + uri); + NativeCalls.notifyIntentUri("//file"+uri); // Add the authority for get it on qml code + } + } + } + } + + public String getRealPathFromURI(Uri uri) { + String[] projection = { MediaStore.Images.Media.DATA }; + @SuppressWarnings("deprecation") + Cursor cursor = managedQuery(uri, projection, null, null, null); + int column_index = cursor + .getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + String result = cursor.getString(column_index); + return result; + } + + + + + } diff --git a/retroshare-qml-app/src/androidimagepicker.h b/retroshare-qml-app/src/androidimagepicker.h new file mode 100644 index 000000000..ad5f0e302 --- /dev/null +++ b/retroshare-qml-app/src/androidimagepicker.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + + +#ifdef __ANDROID__ +# include +# include +#endif // __ANDROID__ + +struct AndroidImagePicker : QObject +{ + Q_OBJECT + +public slots: + + static void openPicker() + { + qDebug() << "Starting image picker intent"; + +#ifdef __ANDROID__ + QtAndroid::androidActivity().callMethod( + "openImagePicker", + "()V" ); +#endif // __ANDROID__ + + } + + // Used to convert a given image path into a png base64 string + static QString imageToBase64 (QString const& path) + { + // Get local path from uri + QUrl url (path); + QString localPath = url.toLocalFile(); + + qDebug() << "imageToBase64() local path:" << localPath ; + + // Read the image + QImageReader reader; + reader.setFileName(localPath); + QImage image = reader.read(); + + image = image.scaled(96,96,Qt::KeepAspectRatio,Qt::SmoothTransformation); + + // Transform image into PNG format + QByteArray ba; + QBuffer buffer( &ba ); + buffer.open( QIODevice::WriteOnly ); + image.save( &buffer, "png" ); + + // Get Based 64 image string + QString encoded = QString(ba.toBase64()); + + qDebug() << "imageToBase64() encoded" ; + + return encoded; + + } +}; diff --git a/retroshare-qml-app/src/components/CustomFileChooser.qml b/retroshare-qml-app/src/components/CustomFileChooser.qml new file mode 100644 index 000000000..e7451dee5 --- /dev/null +++ b/retroshare-qml-app/src/components/CustomFileChooser.qml @@ -0,0 +1,60 @@ +import QtQuick 2.7 +import QtQuick.Dialogs 1.2 + +import "../URI.js" as UriJs + +Item +{ + id: compRoot + + property var resultFile + + FileDialog + { + id: fileDialog + title: "Please choose a file" + folder: shortcuts.pictures + nameFilters: [ "Image files (*.png *.jpg)"] + visible: false + selectMultiple: false + onAccepted: { + console.log("You chose: " + fileDialog.fileUrl) + resultFile = fileDialog.fileUrl + } + onRejected: { + console.log("Canceled") + } + } + + + function open() + { + if (Qt.platform.os === "android") + { + console.log("ImagePicker Android platform detected") + mainWindow.addUriHandler("file", androidResult) + androidImagePicker.openPicker() + } + else + { + fileDialog.visible = true + } + } + + function androidResult (uri) + { + console.log("QML Android image uri found" , uri) + resultFile = normalizeUriToFilePath (uri) + mainWindow.delUriHandler("media", androidResult) + } + + function normalizeUriToFilePath (uriStr) + { + var uri = new UriJs.URI(uriStr) + var hPath = uri.path() + return "file:///"+hPath + + } + + +} diff --git a/retroshare-qml-app/src/main-app.cpp b/retroshare-qml-app/src/main-app.cpp index 5e5fd532f..b52febcfb 100644 --- a/retroshare-qml-app/src/main-app.cpp +++ b/retroshare-qml-app/src/main-app.cpp @@ -38,9 +38,9 @@ #include "libresapilocalclient.h" #include "rsqmlappengine.h" +#include "androidimagepicker.h" #include "platforminteracions.h" - int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); @@ -52,6 +52,7 @@ int main(int argc, char *argv[]) "org.retroshare.qml_components.LibresapiLocalClient", 1, 0, "LibresapiLocalClient"); + QString sockPath = QDir::homePath() + "/.retroshare"; sockPath.append("/libresapi.sock"); @@ -61,6 +62,14 @@ int main(int argc, char *argv[]) RsQmlAppEngine engine(true); QQmlContext& rootContext = *engine.rootContext(); + qmlRegisterType( + "org.retroshare.qml_components.AndroidImagePicker", 1, 0, + "AndroidImagePicker"); + + AndroidImagePicker androidImagePicker; + engine.rootContext()->setContextProperty("androidImagePicker", + &androidImagePicker); + QStringList mainArgs = app.arguments(); #ifdef Q_OS_ANDROID diff --git a/retroshare-qml-app/src/main-app.qml b/retroshare-qml-app/src/main-app.qml index 889bf7ace..17634d741 100644 --- a/retroshare-qml-app/src/main-app.qml +++ b/retroshare-qml-app/src/main-app.qml @@ -364,7 +364,7 @@ ApplicationWindow function handleIntentUri(uriStr) { - console.log("handleIntentUri(uriStr)") + console.log("handleIntentUri(uriStr)", uriStr) if(!Array.isArray(uriStr.match(/:\/\/[a-zA-Z.-]*\//g))) { @@ -382,7 +382,10 @@ ApplicationWindow var uri = new UriJs.URI(uriStr) var hPath = uri.path() // no nesting ATM segmentCoded() - console.log(hPath) + console.log("hPath", hPath) + + var authority = uri.authority() + console.log("authority", authority) if(typeof uriHandlersRegister[hPath] == "function") { @@ -390,6 +393,13 @@ ApplicationWindow hPath, uriHandlersRegister[hPath]) uriHandlersRegister[hPath](uriStr) } + + else if (typeof uriHandlersRegister[authority] == "function" ) + { + console.log("handleIntentUri(uriStr)", "found handler for path", + authority, uriHandlersRegister[authority]) + uriHandlersRegister[authority](uriStr) + } } function certificateLinkHandler(uriStr) diff --git a/retroshare-qml-app/src/qml.qrc b/retroshare-qml-app/src/qml.qrc index 6332dc6da..c55abfa51 100644 --- a/retroshare-qml-app/src/qml.qrc +++ b/retroshare-qml-app/src/qml.qrc @@ -53,5 +53,6 @@ components/emoji/emoji.js icons/network-connect.svg icons/network-disconnect.svg + components/CustomFileChooser.qml diff --git a/retroshare-qml-app/src/retroshare-qml-app.pro b/retroshare-qml-app/src/retroshare-qml-app.pro index 8dc2db67f..ff10bbc6b 100644 --- a/retroshare-qml-app/src/retroshare-qml-app.pro +++ b/retroshare-qml-app/src/retroshare-qml-app.pro @@ -6,6 +6,7 @@ CONFIG += c++11 HEADERS += libresapilocalclient.h \ rsqmlappengine.h \ + androidimagepicker.h \ platforminteracions.h SOURCES += main-app.cpp \ libresapilocalclient.cpp \