HaikuDepot: Multiple improvements for user-ratings

* Display of the user-ratings listing improved
* When a user-rating is created / edited, the pkg is updated
* Creation date of the user-rating is unpacked shown
* Ability to create a user-rating with a comment, but no numerical rating
* Stars display show grey if no numerical rating present
* Improvements to error reporting when problem arise
* Parsing of the 'revision' field of the version working
* Removed debug logging for the text engine
* Other minor tweaks

Change-Id: I99f881ab1426641ef4177eec2d3bcacc7cb74e95
This commit is contained in:
Andrew Lindesay
2018-06-09 17:07:03 +00:00
committed by waddlesplash
parent 466255e324
commit a9edb9bffa
24 changed files with 913 additions and 384 deletions
Binary file not shown.
Binary file not shown.
+9 -18
View File
@@ -65,32 +65,23 @@ resource vector_icon {
$"010C00" $"010C00"
}; };
resource(501, "star") #'VICN' array { resource(510, "starblue") #'VICN' array {
$"6E636966010300AAFF010A0AB43438BC6638BEF3B4C9C18338C9B238C311C165" $"6E636966010300AAFF010A0A40223A38213835432C5A4049545A4B435F384638"
$"C59EC928BEF3C457B84FC928BAD5C165010A00010000" $"010A00010000"
}; };
resource(502, "thumbs up") #'VICN' array { resource(520, "stargray") #'VICN' array {
$"6E63696601020016020000003DC000BD80000000004B30004960000056FF4A01" $"6E6369660105AB010A0A40223A38213835432C5A4049545A4B435F384638010A"
$"060DF6FFFF0358404C4E4C524CC3AF4C3E5046503A50364EBC65C5F5344C3041" $"00010000"
$"3046303F383C343C3C3C3F3A3F3A3F383C323CBB103C303E2C3C2C402C403040"
$"2E40344638C16DBB23483C50404C405440010A00010000"
}; };
resource(503, "thumbs down") #'VICN' array { resource(530, "installed") #'VICN' array {
$"6E63696601020016020000003DC000BD80000000004B30004960000056FF4A01"
$"060DF6FFFF035440344A344E34C217343A30423036303232BACDB98A30342C3F"
$"2C3A2C413444304438443B463B463B48384E38C46F38503A5438543C543C503C"
$"523C4C4248BFD5C45C44444C4048405040010A00010000"
};
resource(504, "installed") #'VICN' array {
$"6E636966030369D90504011704011F0202043E24C4AC24B93B24243E24B93B24" $"6E636966030369D90504011704011F0202043E24C4AC24B93B24243E24B93B24"
$"C4AC3E58B93B58C4AC58583E58C4AC58B93B0A06333A3C42493051363E4F2D41" $"C4AC3E58B93B58C4AC58583E58C4AC58B93B0A06333A3C42493051363E4F2D41"
$"010A0002000100" $"010A0002000100"
}; };
resource(505, "arrow left") #'VICN' array { resource(540, "arrow left") #'VICN' array {
$"6E6369660304006603005900020006020000003C6000C000000000004C000048" $"6E6369660304006603005900020006020000003C6000C000000000004C000048"
$"A0000080FF80FF00B300010A0722353622362C482C483E363E3648030A000100" $"A0000080FF80FF00B300010A0722353622362C482C483E363E3648030A000100"
$"1240A32D00000000000040A32444CEA044D04B01178322040A0101001240A32D" $"1240A32D00000000000040A32444CEA044D04B01178322040A0101001240A32D"
@@ -98,7 +89,7 @@ resource(505, "arrow left") #'VICN' array {
$"000040A32442FA1242FD72" $"000040A32442FA1242FD72"
}; };
resource(506, "arrow right") #'VICN' array { resource(550, "arrow right") #'VICN' array {
$"6E6369660304006603005900020006020000003C6000C000000000004C000048" $"6E6369660304006603005900020006020000003C6000C000000000004C000048"
$"A0000080FF80FF00B300010A0748353448343E223E222C342C3422030A000100" $"A0000080FF80FF00B300010A0748353448343E223E222C342C3422030A000100"
$"1240A32D00000000000040A32444D19644D04B01178322040A0101001240A32D" $"1240A32D00000000000040A32444D19644D04B01178322040A0101001240A32D"
+24 -2
View File
@@ -2,7 +2,8 @@
* Copyright 2018, Andrew Lindesay <[email protected]>. * Copyright 2018, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef HAIKU_DEPOT_CONSTANTS_H
#define HAIKU_DEPOT_CONSTANTS_H
enum { enum {
MSG_MAIN_WINDOW_CLOSED = 'mwcl', MSG_MAIN_WINDOW_CLOSED = 'mwcl',
@@ -12,11 +13,32 @@ enum {
MSG_ADD_VISIBLE_PACKAGES = 'avpk', MSG_ADD_VISIBLE_PACKAGES = 'avpk',
MSG_UPDATE_SELECTED_PACKAGE = 'uspk', MSG_UPDATE_SELECTED_PACKAGE = 'uspk',
MSG_CLIENT_TOO_OLD = 'oldc', MSG_CLIENT_TOO_OLD = 'oldc',
MSG_NETWORK_TRANSPORT_ERROR = 'nett',
MSG_SERVER_ERROR = 'svre',
MSG_SERVER_DATA_CHANGED = 'svdc',
MSG_DID_ADD_USER_RATING = 'adur',
MSG_DID_UPDATE_USER_RATING = 'upur'
}; };
#define RATING_MISSING -1.0f
#define RATING_MIN 0.0f
#define HD_ERROR_BASE (B_ERRORS_END + 1) #define HD_ERROR_BASE (B_ERRORS_END + 1)
#define HD_NETWORK_INACCESSIBLE (HD_ERROR_BASE + 1) #define HD_NETWORK_INACCESSIBLE (HD_ERROR_BASE + 1)
#define HD_CLIENT_TOO_OLD (HD_ERROR_BASE + 2) #define HD_CLIENT_TOO_OLD (HD_ERROR_BASE + 2)
#define HD_ERR_NOT_MODIFIED (HD_ERROR_BASE + 3) #define HD_ERR_NOT_MODIFIED (HD_ERROR_BASE + 3)
#define HD_ERR_NO_DATA (HD_ERROR_BASE + 4) #define HD_ERR_NO_DATA (HD_ERROR_BASE + 4)
// These constants reference resources in 'HaikuDepot.ref'
enum {
RSRC_STAR_BLUE = 510,
RSRC_STAR_GREY = 520,
RSRC_INSTALLED = 530,
RSRC_ARROW_LEFT = 540,
RSRC_ARROW_RIGHT = 550,
};
#endif // HAIKU_DEPOT_CONSTANTS_H
+52 -8
View File
@@ -651,6 +651,11 @@ Model::SetShowDevelopPackages(bool show)
// #pragma mark - information retrieval // #pragma mark - information retrieval
/*! Initially only superficial data is loaded from the server into the data
model of the packages. When the package is viewed, additional data needs
to be populated including ratings. This method takes care of that.
*/
void void
Model::PopulatePackage(const PackageInfoRef& package, uint32 flags) Model::PopulatePackage(const PackageInfoRef& package, uint32 flags)
{ {
@@ -697,7 +702,7 @@ Model::PopulatePackage(const PackageInfoRef& package, uint32 flags)
BAutolock locker(&fLock); BAutolock locker(&fLock);
package->ClearUserRatings(); package->ClearUserRatings();
int index = 0; int32 index = 0;
while (true) { while (true) {
BString name; BString name;
name << index++; name << index++;
@@ -706,11 +711,19 @@ Model::PopulatePackage(const PackageInfoRef& package, uint32 flags)
if (items.FindMessage(name, &item) != B_OK) if (items.FindMessage(name, &item) != B_OK)
break; break;
BString code;
if (item.FindString("code", &code) != B_OK) {
printf("corrupt user rating at index %" B_PRIi32 "\n",
index);
continue;
}
BString user; BString user;
BMessage userInfo; BMessage userInfo;
if (item.FindMessage("user", &userInfo) != B_OK if (item.FindMessage("user", &userInfo) != B_OK
|| userInfo.FindString("nickname", &user) != B_OK) { || userInfo.FindString("nickname", &user) != B_OK) {
// Ignore, we need the user name printf("ignored user rating [%s] without a user "
"nickname\n", code.String());
continue; continue;
} }
@@ -723,7 +736,8 @@ Model::PopulatePackage(const PackageInfoRef& package, uint32 flags)
if (item.FindDouble("rating", &rating) != B_OK) if (item.FindDouble("rating", &rating) != B_OK)
rating = -1; rating = -1;
if (comment.Length() == 0 && rating == -1) { if (comment.Length() == 0 && rating == -1) {
// No useful information given. printf("rating [%s] has no comment or rating so will be"
"ignored\n", code.String());
continue; continue;
} }
@@ -731,11 +745,13 @@ Model::PopulatePackage(const PackageInfoRef& package, uint32 flags)
BString major = "?"; BString major = "?";
BString minor = "?"; BString minor = "?";
BString micro = ""; BString micro = "";
double revision = -1;
BMessage version; BMessage version;
if (item.FindMessage("pkgVersion", &version) == B_OK) { if (item.FindMessage("pkgVersion", &version) == B_OK) {
version.FindString("major", &major); version.FindString("major", &major);
version.FindString("minor", &minor); version.FindString("minor", &minor);
version.FindString("micro", &micro); version.FindString("micro", &micro);
version.FindDouble("revision", &revision);
} }
BString versionString = major; BString versionString = major;
versionString << "."; versionString << ".";
@@ -744,15 +760,43 @@ Model::PopulatePackage(const PackageInfoRef& package, uint32 flags)
versionString << "."; versionString << ".";
versionString << micro; versionString << micro;
} }
if (revision > 0) {
versionString << "-";
versionString << (int) revision;
}
BDateTime createTimestamp;
double createTimestampMillisF;
if (item.FindDouble("createTimestamp",
&createTimestampMillisF) == B_OK) {
double createTimestampSecsF =
createTimestampMillisF / 1000.0;
time_t createTimestampSecs =
(time_t) createTimestampSecsF;
createTimestamp.SetTime_t(createTimestampSecs);
}
// Add the rating to the PackageInfo // Add the rating to the PackageInfo
package->AddUserRating( UserRating userRating = UserRating(UserInfo(user), rating,
UserRating(UserInfo(user), rating, comment, languageCode, versionString, 0, 0,
comment, languageCode, versionString, 0, 0) createTimestamp);
); package->AddUserRating(userRating);
if (Logger::IsDebugEnabled()) {
printf("rating [%s] retrieved from server\n",
code.String());
}
}
if (Logger::IsDebugEnabled()) {
printf("did retrieve %" B_PRIi32 " user ratings for [%s]\n",
index - 1, packageName.String());
} }
} else { } else {
_MaybeLogJsonRpcError(info, "retrieve user ratings"); _MaybeLogJsonRpcError(info, "retrieve user ratings");
} }
} else {
printf("unable to retrieve user ratings\n");
} }
} }
@@ -808,7 +852,7 @@ Model::_PopulatePackageChangelog(const PackageInfoRef& package)
} }
} else { } else {
fprintf(stdout, "unable to obtain the changelog for the package" fprintf(stdout, "unable to obtain the changelog for the package"
"[%s]\n", packageName.String()); " [%s]\n", packageName.String());
} }
} }
+13 -5
View File
@@ -86,14 +86,16 @@ UserRating::UserRating()
fLanguage(), fLanguage(),
fPackageVersion(), fPackageVersion(),
fUpVotes(0), fUpVotes(0),
fDownVotes(0) fDownVotes(0),
fCreateTimestamp()
{ {
} }
UserRating::UserRating(const UserInfo& userInfo, float rating, UserRating::UserRating(const UserInfo& userInfo, float rating,
const BString& comment, const BString& language, const BString& comment, const BString& language,
const BString& packageVersion, int32 upVotes, int32 downVotes) const BString& packageVersion, int32 upVotes, int32 downVotes,
const BDateTime& createTimestamp)
: :
fUserInfo(userInfo), fUserInfo(userInfo),
fRating(rating), fRating(rating),
@@ -101,8 +103,10 @@ UserRating::UserRating(const UserInfo& userInfo, float rating,
fLanguage(language), fLanguage(language),
fPackageVersion(packageVersion), fPackageVersion(packageVersion),
fUpVotes(upVotes), fUpVotes(upVotes),
fDownVotes(downVotes) fDownVotes(downVotes),
fCreateTimestamp()
{ {
fCreateTimestamp.SetTime_t(createTimestamp.Time_t());
} }
@@ -114,8 +118,10 @@ UserRating::UserRating(const UserRating& other)
fLanguage(other.fLanguage), fLanguage(other.fLanguage),
fPackageVersion(other.fPackageVersion), fPackageVersion(other.fPackageVersion),
fUpVotes(other.fUpVotes), fUpVotes(other.fUpVotes),
fDownVotes(other.fDownVotes) fDownVotes(other.fDownVotes),
fCreateTimestamp()
{ {
fCreateTimestamp.SetTime_t(other.CreateTimestamp().Time_t());
} }
@@ -129,6 +135,7 @@ UserRating::operator=(const UserRating& other)
fPackageVersion = other.fPackageVersion; fPackageVersion = other.fPackageVersion;
fUpVotes = other.fUpVotes; fUpVotes = other.fUpVotes;
fDownVotes = other.fDownVotes; fDownVotes = other.fDownVotes;
fCreateTimestamp.SetTime_t(other.fCreateTimestamp.Time_t());
return *this; return *this;
} }
@@ -142,7 +149,8 @@ UserRating::operator==(const UserRating& other) const
&& fLanguage == other.fLanguage && fLanguage == other.fLanguage
&& fPackageVersion == other.fPackageVersion && fPackageVersion == other.fPackageVersion
&& fUpVotes == other.fUpVotes && fUpVotes == other.fUpVotes
&& fDownVotes == other.fDownVotes; && fDownVotes == other.fDownVotes
&& fCreateTimestamp == other.fCreateTimestamp;
} }
+7 -2
View File
@@ -1,6 +1,6 @@
/* /*
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>. * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2016-2017, Andrew Lindesay <apl@lindesay.co.nz>. * Copyright 2016-2018, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef PACKAGE_INFO_H #ifndef PACKAGE_INFO_H
@@ -12,6 +12,7 @@
#include <Referenceable.h> #include <Referenceable.h>
#include <package/PackageInfo.h> #include <package/PackageInfo.h>
#include "DateTime.h"
#include "List.h" #include "List.h"
#include "PackageInfoListener.h" #include "PackageInfoListener.h"
#include "SharedBitmap.h" #include "SharedBitmap.h"
@@ -51,7 +52,8 @@ public:
const BString& comment, const BString& comment,
const BString& language, const BString& language,
const BString& packageVersion, const BString& packageVersion,
int32 upVotes, int32 downVotes); int32 upVotes, int32 downVotes,
const BDateTime& createTimestamp);
UserRating(const UserRating& other); UserRating(const UserRating& other);
UserRating& operator=(const UserRating& other); UserRating& operator=(const UserRating& other);
@@ -73,6 +75,8 @@ public:
{ return fUpVotes; } { return fUpVotes; }
int32 DownVotes() const int32 DownVotes() const
{ return fDownVotes; } { return fDownVotes; }
const BDateTime& CreateTimestamp() const
{ return fCreateTimestamp; }
private: private:
UserInfo fUserInfo; UserInfo fUserInfo;
float fRating; float fRating;
@@ -81,6 +85,7 @@ private:
BString fPackageVersion; BString fPackageVersion;
int32 fUpVotes; int32 fUpVotes;
int32 fDownVotes; int32 fDownVotes;
BDateTime fCreateTimestamp;
}; };
@@ -1,5 +1,5 @@
/* /*
* Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>. * Copyright 2017-2018, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
@@ -22,6 +22,7 @@
#include "DumpExportPkgJsonListener.h" #include "DumpExportPkgJsonListener.h"
#include "DumpExportPkgScreenshot.h" #include "DumpExportPkgScreenshot.h"
#include "DumpExportPkgVersion.h" #include "DumpExportPkgVersion.h"
#include "HaikuDepotConstants.h"
/*! This package listener (not at the JSON level) is feeding in the /*! This package listener (not at the JSON level) is feeding in the
@@ -145,11 +146,13 @@ PackageFillingPkgListener::ConsumePackage(const PackageInfoRef& package,
} }
} }
if (!pkg->DerivedRatingIsNull()) { RatingSummary summary;
RatingSummary summary; summary.averageRating = RATING_MISSING;
if (!pkg->DerivedRatingIsNull())
summary.averageRating = pkg->DerivedRating(); summary.averageRating = pkg->DerivedRating();
package->SetRatingSummary(summary);
} package->SetRatingSummary(summary);
if (!pkg->ProminenceOrderingIsNull()) if (!pkg->ProminenceOrderingIsNull())
package->SetProminence(pkg->ProminenceOrdering()); package->SetProminence(pkg->ProminenceOrdering());
+115
View File
@@ -16,6 +16,7 @@
#include "HaikuDepotConstants.h" #include "HaikuDepotConstants.h"
#include "ServerSettings.h" #include "ServerSettings.h"
#include "WebAppInterface.h"
#undef B_TRANSLATION_CONTEXT #undef B_TRANSLATION_CONTEXT
@@ -24,6 +25,120 @@
#define KEY_HEADER_MINIMUM_VERSION "X-Desktop-Application-Minimum-Version" #define KEY_HEADER_MINIMUM_VERSION "X-Desktop-Application-Minimum-Version"
/*! This method will cause an alert to be shown to the user regarding a
JSON-RPC error that has been sent from the application server. It will
send a message to the application looper which will then relay the message
to the looper and then onto the user to see.
*/
void
ServerHelper::NotifyServerJsonRpcError(BMessage& error)
{
BMessage message(MSG_SERVER_ERROR);
message.AddMessage("error", &error);
be_app->PostMessage(&message);
}
void
ServerHelper::AlertServerJsonRpcError(BMessage* message)
{
BMessage error;
int32 errorCode = 0;
if (message->FindMessage("error", &error) == B_OK)
errorCode = WebAppInterface::ErrorCodeFromResponse(error);
BString alertText;
switch (errorCode) {
case ERROR_CODE_VALIDATION:
// TODO; expand on that message.
alertText = B_TRANSLATE("A validation error has occurred");
break;
case ERROR_CODE_OBJECTNOTFOUND:
alertText = B_TRANSLATE("A requested object or an object involved"
" in the request was not found on the server.");
break;
case ERROR_CODE_CAPTCHABADRESPONSE:
alertText = B_TRANSLATE("The response to the captcha was incorrect.");
break;
case ERROR_CODE_AUTHORIZATIONFAILURE:
case ERROR_CODE_AUTHORIZATIONRULECONFLICT:
alertText = B_TRANSLATE("Authorization or security issue");
break;
default:
alertText.SetToFormat(
B_TRANSLATE("An unexpected error has been sent from the"
" HaikuDepot server [%" B_PRIi32 "]"), errorCode);
break;
}
BAlert* alert = new BAlert(
B_TRANSLATE("Server Error"),
alertText,
B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
void
ServerHelper::NotifyTransportError(status_t error)
{
switch (error) {
case HD_CLIENT_TOO_OLD:
// this is handled earlier on because it requires some
// information from the HTTP request to create a sensible
// error message.
break;
default:
{
BMessage message(MSG_NETWORK_TRANSPORT_ERROR);
message.AddInt64("errno", (int64) error);
be_app->PostMessage(&message);
break;
}
}
}
void
ServerHelper::AlertTransportError(BMessage* message)
{
status_t errno = B_OK;
int64 errnoInt64;
message->FindInt64("errno", &errnoInt64);
errno = (status_t) errnoInt64;
BString errorDescription("?");
BString alertText;
switch (errno) {
case HD_NETWORK_INACCESSIBLE:
errorDescription = B_TRANSLATE("Network Error");
break;
default:
errorDescription.SetTo(strerror(errno));
break;
}
alertText.SetToFormat(B_TRANSLATE("A network transport error has arisen"
" communicating with the HaikuDepot server system: %s"),
errorDescription.String());
BAlert* alert = new BAlert(
B_TRANSLATE("Network Transport Error"),
alertText,
B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
void void
ServerHelper::NotifyClientTooOld(const BHttpHeaders& responseHeaders) ServerHelper::NotifyClientTooOld(const BHttpHeaders& responseHeaders)
{ {
+11 -1
View File
@@ -7,7 +7,9 @@
#define SERVER_HELPER_H #define SERVER_HELPER_H
#include <HttpHeaders.h> #include <HttpHeaders.h>
#include <Message.h>
class BMessage;
class ServerHelper { class ServerHelper {
@@ -19,6 +21,14 @@ public:
const BHttpHeaders& responseHeaders const BHttpHeaders& responseHeaders
); );
static void AlertClientTooOld(BMessage* message); static void AlertClientTooOld(BMessage* message);
static void NotifyTransportError(status_t error);
static void AlertTransportError(BMessage* message);
static void NotifyServerJsonRpcError(
BMessage& error);
static void AlertServerJsonRpcError(
BMessage* message);
}; };
#endif // SERVER_HELPER_H #endif // SERVER_HELPER_H
+209 -64
View File
@@ -10,11 +10,13 @@
#include <AppFileInfo.h> #include <AppFileInfo.h>
#include <Application.h> #include <Application.h>
#include <AutoDeleter.h>
#include <Autolock.h> #include <Autolock.h>
#include <File.h> #include <File.h>
#include <HttpHeaders.h> #include <HttpHeaders.h>
#include <HttpRequest.h> #include <HttpRequest.h>
#include <Json.h> #include <Json.h>
#include <JsonTextWriter.h>
#include <Message.h> #include <Message.h>
#include <Roster.h> #include <Roster.h>
#include <Url.h> #include <Url.h>
@@ -363,56 +365,138 @@ WebAppInterface::RetrieveUserRating(const BString& packageName,
const BString &repositoryCode, const BString& username, const BString &repositoryCode, const BString& username,
BMessage& message) BMessage& message)
{ {
BString jsonString = JsonBuilder() // BHttpRequest later takes ownership of this.
.AddValue("jsonrpc", "2.0") BMallocIO* requestEnvelopeData = new BMallocIO();
.AddValue("id", ++fRequestIndex) BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
.AddValue("method", "getUserRatingByUserAndPkgVersion")
.AddArray("params")
.AddObject()
.AddValue("userNickname", username)
.AddValue("pkgName", packageName)
.AddValue("pkgVersionArchitectureCode", architecture)
.AddValue("pkgVersionMajor", version.Major(), true)
.AddValue("pkgVersionMinor", version.Minor(), true)
.AddValue("pkgVersionMicro", version.Micro(), true)
.AddValue("pkgVersionPreRelease", version.PreRelease(), true)
.AddValue("pkgVersionRevision", (int)version.Revision())
.AddValue("repositoryCode", repositoryCode)
.EndObject()
.EndArray()
.End();
return _SendJsonRequest("userrating", jsonString, 0, message); requestEnvelopeWriter.WriteObjectStart();
_WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
"getUserRatingByUserAndPkgVersion");
requestEnvelopeWriter.WriteObjectName("params");
requestEnvelopeWriter.WriteArrayStart();
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("userNickname");
requestEnvelopeWriter.WriteString(username.String());
requestEnvelopeWriter.WriteObjectName("pkgName");
requestEnvelopeWriter.WriteString(packageName.String());
requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
requestEnvelopeWriter.WriteString(architecture.String());
requestEnvelopeWriter.WriteObjectName("repositoryCode");
requestEnvelopeWriter.WriteString(repositoryCode.String());
if (version.Major().Length() > 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
requestEnvelopeWriter.WriteString(version.Major().String());
}
if (version.Minor().Length() > 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
requestEnvelopeWriter.WriteString(version.Minor().String());
}
if (version.Micro().Length() > 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
requestEnvelopeWriter.WriteString(version.Micro().String());
}
if (version.PreRelease().Length() > 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
requestEnvelopeWriter.WriteString(version.PreRelease().String());
}
if (version.Revision() != 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
requestEnvelopeWriter.WriteInteger(version.Revision());
}
requestEnvelopeWriter.WriteObjectEnd();
requestEnvelopeWriter.WriteArrayEnd();
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("userrating", requestEnvelopeData,
requestEnvelopeData->Position(), NEEDS_AUTHORIZATION, message);
} }
status_t status_t
WebAppInterface::CreateUserRating(const BString& packageName, WebAppInterface::CreateUserRating(const BString& packageName,
const BPackageVersion& version,
const BString& architecture, const BString& repositoryCode, const BString& architecture, const BString& repositoryCode,
const BString& languageCode, const BString& comment, const BString& languageCode, const BString& comment,
const BString& stability, int rating, BMessage& message) const BString& stability, int rating, BMessage& message)
{ {
BString jsonString = JsonBuilder() // BHttpRequest later takes ownership of this.
.AddValue("jsonrpc", "2.0") BMallocIO* requestEnvelopeData = new BMallocIO();
.AddValue("id", ++fRequestIndex) BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
.AddValue("method", "createUserRating")
.AddArray("params")
.AddObject()
.AddValue("pkgName", packageName)
.AddValue("pkgVersionArchitectureCode", architecture)
.AddValue("pkgVersionType", "LATEST")
.AddValue("userNickname", fUsername)
.AddValue("rating", rating)
.AddValue("userRatingStabilityCode", stability, true)
.AddValue("comment", comment)
.AddValue("repositoryCode", repositoryCode)
.AddValue("naturalLanguageCode", languageCode)
.EndObject()
.EndArray()
.End();
return _SendJsonRequest("userrating", jsonString, NEEDS_AUTHORIZATION, requestEnvelopeWriter.WriteObjectStart();
message); _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
"createUserRating");
requestEnvelopeWriter.WriteObjectName("params");
requestEnvelopeWriter.WriteArrayStart();
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("pkgName");
requestEnvelopeWriter.WriteString(packageName.String());
requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
requestEnvelopeWriter.WriteString(architecture.String());
requestEnvelopeWriter.WriteObjectName("repositoryCode");
requestEnvelopeWriter.WriteString(repositoryCode.String());
requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
requestEnvelopeWriter.WriteString(languageCode.String());
requestEnvelopeWriter.WriteObjectName("pkgVersionType");
requestEnvelopeWriter.WriteString("SPECIFIC");
requestEnvelopeWriter.WriteObjectName("userNickname");
requestEnvelopeWriter.WriteString(fUsername.String());
if (!version.Major().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
requestEnvelopeWriter.WriteString(version.Major());
}
if (!version.Minor().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
requestEnvelopeWriter.WriteString(version.Minor());
}
if (!version.Micro().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
requestEnvelopeWriter.WriteString(version.Micro());
}
if (!version.PreRelease().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
requestEnvelopeWriter.WriteString(version.PreRelease());
}
if (version.Revision() != 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
requestEnvelopeWriter.WriteInteger(version.Revision());
}
if (rating > 0.0f) {
requestEnvelopeWriter.WriteObjectName("rating");
requestEnvelopeWriter.WriteInteger(rating);
}
if (stability.Length() > 0) {
requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
requestEnvelopeWriter.WriteString(stability);
}
if (comment.Length() > 0) {
requestEnvelopeWriter.WriteObjectName("comment");
requestEnvelopeWriter.WriteString(comment.String());
}
requestEnvelopeWriter.WriteObjectEnd();
requestEnvelopeWriter.WriteArrayEnd();
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("userrating", requestEnvelopeData,
requestEnvelopeData->Position(), NEEDS_AUTHORIZATION, message);
} }
@@ -421,31 +505,56 @@ WebAppInterface::UpdateUserRating(const BString& ratingID,
const BString& languageCode, const BString& comment, const BString& languageCode, const BString& comment,
const BString& stability, int rating, bool active, BMessage& message) const BString& stability, int rating, bool active, BMessage& message)
{ {
BString jsonString = JsonBuilder() // BHttpRequest later takes ownership of this.
.AddValue("jsonrpc", "2.0") BMallocIO* requestEnvelopeData = new BMallocIO();
.AddValue("id", ++fRequestIndex) BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
.AddValue("method", "updateUserRating")
.AddArray("params")
.AddObject()
.AddValue("code", ratingID)
.AddValue("rating", rating)
.AddValue("userRatingStabilityCode", stability, true)
.AddValue("comment", comment)
.AddValue("naturalLanguageCode", languageCode)
.AddValue("active", active)
.AddArray("filter")
.AddItem("ACTIVE")
.AddItem("NATURALLANGUAGE")
.AddItem("USERRATINGSTABILITY")
.AddItem("COMMENT")
.AddItem("RATING")
.EndArray()
.EndObject()
.EndArray()
.End();
return _SendJsonRequest("userrating", jsonString, NEEDS_AUTHORIZATION, requestEnvelopeWriter.WriteObjectStart();
message); _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
"updateUserRating");
requestEnvelopeWriter.WriteObjectName("params");
requestEnvelopeWriter.WriteArrayStart();
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("code");
requestEnvelopeWriter.WriteString(ratingID.String());
requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
requestEnvelopeWriter.WriteString(languageCode.String());
requestEnvelopeWriter.WriteObjectName("active");
requestEnvelopeWriter.WriteBoolean(active);
requestEnvelopeWriter.WriteObjectName("filter");
requestEnvelopeWriter.WriteArrayStart();
requestEnvelopeWriter.WriteString("ACTIVE");
requestEnvelopeWriter.WriteString("NATURALLANGUAGE");
requestEnvelopeWriter.WriteString("USERRATINGSTABILITY");
requestEnvelopeWriter.WriteString("COMMENT");
requestEnvelopeWriter.WriteString("RATING");
requestEnvelopeWriter.WriteArrayEnd();
if (rating >= 0) {
requestEnvelopeWriter.WriteObjectName("rating");
requestEnvelopeWriter.WriteInteger(rating);
}
if (stability.Length() > 0) {
requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
requestEnvelopeWriter.WriteString(stability);
}
if (comment.Length() > 0) {
requestEnvelopeWriter.WriteObjectName("comment");
requestEnvelopeWriter.WriteString(comment);
}
requestEnvelopeWriter.WriteObjectEnd();
requestEnvelopeWriter.WriteArrayEnd();
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("userrating", requestEnvelopeData,
requestEnvelopeData->Position(), NEEDS_AUTHORIZATION, message);
} }
@@ -555,9 +664,45 @@ WebAppInterface::AuthenticateUser(const BString& nickName,
} }
/*! JSON-RPC invocations return a response. The response may be either
a result or it may be an error depending on the response structure.
If it is an error then there may be additional detail that is the
error code and message. This method will extract the error code
from the response. This method will return 0 if the payload does
not look like an error.
*/
int32
WebAppInterface::ErrorCodeFromResponse(BMessage& response)
{
BMessage error;
double code;
if (response.FindMessage("error", &error) == B_OK
&& error.FindDouble("code", &code) == B_OK) {
return (int32) code;
}
return 0;
}
// #pragma mark - private // #pragma mark - private
void
WebAppInterface::_WriteStandardJsonRpcEnvelopeValues(BJsonWriter& writer,
const char* methodName)
{
writer.WriteObjectName("jsonrpc");
writer.WriteString("2.0");
writer.WriteObjectName("id");
writer.WriteInteger(++fRequestIndex);
writer.WriteObjectName("method");
writer.WriteString(methodName);
}
status_t status_t
WebAppInterface::_SendJsonRequest(const char* domain, BDataIO* requestData, WebAppInterface::_SendJsonRequest(const char* domain, BDataIO* requestData,
size_t requestDataSize, uint32 flags, BMessage& reply) const size_t requestDataSize, uint32 flags, BMessage& reply) const
@@ -658,10 +803,10 @@ WebAppInterface::_SendJsonRequest(const char* domain, BString jsonString,
if (Logger::IsTraceEnabled()) if (Logger::IsTraceEnabled())
printf("_SendJsonRequest(%s)\n", jsonString.String()); printf("_SendJsonRequest(%s)\n", jsonString.String());
// gets 'adopted' by the subsequent http request.
BMemoryIO* data = new BMemoryIO( BMemoryIO* data = new BMemoryIO(
jsonString.String(), jsonString.Length() - 1); jsonString.String(), jsonString.Length() - 1);
return _SendJsonRequest(domain, data, jsonString.Length() - 1, flags, return _SendJsonRequest(domain, data, jsonString.Length() - 1, flags,
reply); reply);
} }
@@ -8,6 +8,7 @@
#include <Application.h> #include <Application.h>
#include <JsonWriter.h>
#include <String.h> #include <String.h>
#include <package/PackageVersion.h> #include <package/PackageVersion.h>
@@ -21,6 +22,18 @@ using BPackageKit::BPackageVersion;
typedef List<BString, false> StringList; typedef List<BString, false> StringList;
/*! These are error codes that are sent back to the client from the server */
#define ERROR_CODE_NONE 0
#define ERROR_CODE_VALIDATION -32800
#define ERROR_CODE_OBJECTNOTFOUND -32801
#define ERROR_CODE_CAPTCHABADRESPONSE -32802
#define ERROR_CODE_AUTHORIZATIONFAILURE -32803
#define ERROR_CODE_BADPKGICON -32804
#define ERROR_CODE_LIMITEXCEEDED -32805
#define ERROR_CODE_AUTHORIZATIONRULECONFLICT -32806
class WebAppInterface { class WebAppInterface {
public: public:
WebAppInterface(); WebAppInterface();
@@ -57,6 +70,7 @@ public:
status_t CreateUserRating( status_t CreateUserRating(
const BString& packageName, const BString& packageName,
const BPackageVersion& version,
const BString& architecture, const BString& architecture,
const BString& repositoryCode, const BString& repositoryCode,
const BString& languageCode, const BString& languageCode,
@@ -92,7 +106,12 @@ public:
const BString& passwordClear, const BString& passwordClear,
BMessage& message); BMessage& message);
static int32 ErrorCodeFromResponse(BMessage& response);
private: private:
void _WriteStandardJsonRpcEnvelopeValues(
BJsonWriter& writer,
const char* methodName);
status_t _SendJsonRequest(const char* domain, status_t _SendJsonRequest(const char* domain,
BString jsonString, uint32 flags, BString jsonString, uint32 flags,
BMessage& reply) const; BMessage& reply) const;
@@ -25,9 +25,9 @@ public:
virtual void TextChanged(const TextChangedEvent& event) virtual void TextChanged(const TextChangedEvent& event)
{ {
printf("TextChanged(%" B_PRIi32 ", %" B_PRIi32 ")\n", // printf("TextChanged(%" B_PRIi32 ", %" B_PRIi32 ")\n",
event.FirstChangedParagraph(), // event.FirstChangedParagraph(),
event.ChangedParagraphCount()); // event.ChangedParagraphCount());
// TODO: The event does not contain useful data. Make the event // TODO: The event does not contain useful data. Make the event
// system work so only the affected paragraphs are updated. // system work so only the affected paragraphs are updated.
// I think we need "first affected", "last affected" (both relative // I think we need "first affected", "last affected" (both relative
@@ -257,8 +257,6 @@ TextEditor::Insert(int32 offset, const BString& string)
if (ret == B_OK) { if (ret == B_OK) {
_SetCaretOffset(offset + string.CountChars(), true, false, true); _SetCaretOffset(offset + string.CountChars(), true, false, true);
fDocument->PrintToStream();
} }
return ret; return ret;
@@ -275,8 +273,6 @@ TextEditor::Remove(int32 offset, int32 length)
if (ret == B_OK) { if (ret == B_OK) {
_SetCaretOffset(offset, true, false, true); _SetCaretOffset(offset, true, false, true);
fDocument->PrintToStream();
} }
return ret; return ret;
@@ -293,8 +289,6 @@ TextEditor::Replace(int32 offset, int32 length, const BString& string)
if (ret == B_OK) { if (ret == B_OK) {
_SetCaretOffset(offset + string.CountChars(), true, false, true); _SetCaretOffset(offset + string.CountChars(), true, false, true);
fDocument->PrintToStream();
} }
return ret; return ret;
+12
View File
@@ -108,6 +108,18 @@ App::MessageReceived(BMessage* message)
ServerHelper::AlertClientTooOld(message); ServerHelper::AlertClientTooOld(message);
break; break;
case MSG_NETWORK_TRANSPORT_ERROR:
ServerHelper::AlertTransportError(message);
break;
case MSG_SERVER_ERROR:
ServerHelper::AlertServerJsonRpcError(message);
break;
case MSG_SERVER_DATA_CHANGED:
fMainWindow->PostMessage(message);
break;
default: default:
BApplication::MessageReceived(message); BApplication::MessageReceived(message);
break; break;
@@ -17,6 +17,7 @@
#include <SpaceLayoutItem.h> #include <SpaceLayoutItem.h>
#include "BitmapView.h" #include "BitmapView.h"
#include "HaikuDepotConstants.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "MarkupTextView.h" #include "MarkupTextView.h"
#include "MessagePackageListener.h" #include "MessagePackageListener.h"
@@ -30,7 +31,8 @@
static const rgb_color kLightBlack = (rgb_color){ 60, 60, 60, 255 }; static const rgb_color kLightBlack = (rgb_color){ 60, 60, 60, 255 };
static BitmapRef sInstalledIcon(new(std::nothrow) SharedBitmap(504), true); static BitmapRef sInstalledIcon(new(std::nothrow)
SharedBitmap(RSRC_INSTALLED), true);
// #pragma mark - PackageView // #pragma mark - PackageView
+92 -11
View File
@@ -390,6 +390,26 @@ MainWindow::MessageReceived(BMessage* message)
_AdoptModel(); _AdoptModel();
break; break;
// this may be triggered by, for example, a user rating being added
// or having been altered.
case MSG_SERVER_DATA_CHANGED:
{
BString name;
if (message->FindString("name", &name) == B_OK) {
BAutolock locker(fModel.Lock());
if (fPackageInfoView->Package()->Name() == name) {
_PopulatePackageAsync(true);
} else {
if (Logger::IsDebugEnabled()) {
printf("pkg [%s] is updated on the server, but is "
"not selected so will not be updated.\n",
name.String());
}
}
}
break;
}
case MSG_PACKAGE_SELECTED: case MSG_PACKAGE_SELECTED:
{ {
BString name; BString name;
@@ -624,8 +644,8 @@ MainWindow::StoreSettings(BMessage& settings) const
void void
MainWindow::PackageChanged(const PackageInfoEvent& event) MainWindow::PackageChanged(const PackageInfoEvent& event)
{ {
uint32 whatchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE; uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
if ((event.Changes() & whatchedChanges) != 0) { if ((event.Changes() & watchedChanges) != 0) {
PackageInfoRef ref(event.Package()); PackageInfoRef ref(event.Package());
BMessage message(MSG_PACKAGE_CHANGED); BMessage message(MSG_PACKAGE_CHANGED);
message.AddPointer("package", ref.Get()); message.AddPointer("package", ref.Get());
@@ -850,12 +870,7 @@ MainWindow::_AdoptPackage(const PackageInfoRef& package)
fPackageListView->SelectPackage(package); fPackageListView->SelectPackage(package);
} }
// Trigger asynchronous package population from the web-app _PopulatePackageAsync(false);
{
AutoLocker<BLocker> lock(&fPackageToPopulateLock);
fPackageToPopulate = package;
}
release_sem_etc(fPackageToPopulateSem, 1, 0);
} }
@@ -1255,6 +1270,34 @@ MainWindow::_PackageActionWorker(void* arg)
} }
/*! This method will cause the package to have its data refreshed from
the server application. The refresh happens in the background; this method
is asynchronous.
*/
void
MainWindow::_PopulatePackageAsync(bool forcePopulate)
{
// Trigger asynchronous package population from the web-app
{
AutoLocker<BLocker> lock(&fPackageToPopulateLock);
fPackageToPopulate = fPackageInfoView->Package();
fForcePopulatePackage = forcePopulate;
}
release_sem_etc(fPackageToPopulateSem, 1, 0);
if (Logger::IsDebugEnabled()) {
printf("pkg [%s] will be updated from the server.\n",
fPackageToPopulate->Name().String());
}
}
/*! This method will run in the background. The thread will block until there
is a package to be updated. When the thread unblocks, it will update the
package with information from the server.
*/
status_t status_t
MainWindow::_PopulatePackageWorker(void* arg) MainWindow::_PopulatePackageWorker(void* arg)
{ {
@@ -1262,15 +1305,27 @@ MainWindow::_PopulatePackageWorker(void* arg)
while (acquire_sem(window->fPackageToPopulateSem) == B_OK) { while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
PackageInfoRef package; PackageInfoRef package;
bool force;
{ {
AutoLocker<BLocker> lock(&window->fPackageToPopulateLock); AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
package = window->fPackageToPopulate; package = window->fPackageToPopulate;
force = window->fForcePopulatePackage;
} }
if (package.Get() != NULL) { if (package.Get() != NULL) {
window->fModel.PopulatePackage(package, uint32 populateFlags = Model::POPULATE_USER_RATINGS
Model::POPULATE_USER_RATINGS | Model::POPULATE_SCREEN_SHOTS | Model::POPULATE_SCREEN_SHOTS
| Model::POPULATE_CHANGELOG); | Model::POPULATE_CHANGELOG;
if (force)
populateFlags |= Model::POPULATE_FORCE;
window->fModel.PopulatePackage(package, populateFlags);
if (Logger::IsDebugEnabled()) {
printf("populating package [%s]\n",
package->Name().String());
}
} }
} }
@@ -1435,9 +1490,35 @@ MainWindow::_UpdateAvailableRepositories()
} }
bool
MainWindow::_SelectedPackageHasWebAppRepositoryCode()
{
const PackageInfoRef& package = fPackageInfoView->Package();
const DepotInfo* depot = fModel.DepotForName(package->DepotName());
BString repositoryCode;
if (depot != NULL)
repositoryCode = depot->WebAppRepositoryCode();
return !repositoryCode.IsEmpty();
}
void void
MainWindow::_RatePackage() MainWindow::_RatePackage()
{ {
if (!_SelectedPackageHasWebAppRepositoryCode()) {
BAlert* alert = new(std::nothrow) BAlert(
B_TRANSLATE("rating_not_possible"),
B_TRANSLATE("Because there is no representation for this package "
"on the HaikuDepot server system, it is not possible to create "
"a new rating or edit an existing rating."),
B_TRANSLATE("OK"));
alert->Go();
return;
}
if (fModel.Username().IsEmpty()) { if (fModel.Username().IsEmpty()) {
BAlert* alert = new(std::nothrow) BAlert( BAlert* alert = new(std::nothrow) BAlert(
B_TRANSLATE("Not logged in"), B_TRANSLATE("Not logged in"),
+4
View File
@@ -57,6 +57,8 @@ private:
virtual Model* GetModel(); virtual Model* GetModel();
private: private:
bool _SelectedPackageHasWebAppRepositoryCode();
void _BuildMenu(BMenuBar* menuBar); void _BuildMenu(BMenuBar* menuBar);
void _BuildUserMenu(BMenuBar* menuBar); void _BuildUserMenu(BMenuBar* menuBar);
@@ -73,6 +75,7 @@ private:
void _RefreshRepositories(bool force); void _RefreshRepositories(bool force);
void _RefreshPackageList(bool force); void _RefreshPackageList(bool force);
void _PopulatePackageAsync(bool forcePopulate);
void _StartRefreshWorker(bool force = false); void _StartRefreshWorker(bool force = false);
static status_t _RefreshModelThreadWorker(void* arg); static status_t _RefreshModelThreadWorker(void* arg);
static status_t _PackageActionWorker(void* arg); static status_t _PackageActionWorker(void* arg);
@@ -127,6 +130,7 @@ private:
thread_id fPopulatePackageWorker; thread_id fPopulatePackageWorker;
PackageInfoRef fPackageToPopulate; PackageInfoRef fPackageToPopulate;
bool fForcePopulatePackage;
BLocker fPackageToPopulateLock; BLocker fPackageToPopulateLock;
sem_id fPackageToPopulateSem; sem_id fPackageToPopulateSem;
+60 -106
View File
@@ -1,5 +1,6 @@
/* /*
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>. * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2018, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
@@ -15,6 +16,7 @@
#include <CardLayout.h> #include <CardLayout.h>
#include <Catalog.h> #include <Catalog.h>
#include <ColumnListView.h> #include <ColumnListView.h>
#include <DateFormat.h>
#include <Font.h> #include <Font.h>
#include <GridView.h> #include <GridView.h>
#include <LayoutBuilder.h> #include <LayoutBuilder.h>
@@ -724,7 +726,6 @@ public:
fWebsiteLinkView->SetViewUIColor(ViewUIColor(), kContentTint); fWebsiteLinkView->SetViewUIColor(ViewUIColor(), kContentTint);
BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0.0f) BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0.0f)
// .Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f))
.AddGroup(leftGroup, 1.0f) .AddGroup(leftGroup, 1.0f)
.Add(fScreenshotView) .Add(fScreenshotView)
.AddGroup(B_HORIZONTAL) .AddGroup(B_HORIZONTAL)
@@ -867,98 +868,71 @@ private:
class RatingItemView : public BGroupView { class RatingItemView : public BGroupView {
public: public:
RatingItemView(const UserRating& rating, const BitmapRef& voteUpIcon, RatingItemView(const UserRating& rating)
const BitmapRef& voteDownIcon)
: :
BGroupView(B_HORIZONTAL, 0.0f) BGroupView(B_HORIZONTAL, 0.0f)
{ {
SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
fAvatarView = new BitmapView("avatar view"); BLayoutBuilder::Group<BLayoutBuilder::Group<void*> > verticalGroup =
if (rating.User().Avatar().Get() != NULL) { BLayoutBuilder::Group<>(this)
fAvatarView->SetBitmap(rating.User().Avatar(), .AddGroup(B_VERTICAL, 0.0f);
SharedBitmap::SIZE_16);
{
BStringView* userNicknameView = new BStringView("user-nickname",
rating.User().NickName());
userNicknameView->SetFont(be_bold_font);
verticalGroup.Add(userNicknameView);
} }
fAvatarView->SetExplicitMinSize(BSize(16.0f, 16.0f));
fNameView = new BStringView("user name", rating.User().NickName()); BLayoutBuilder::Group<BLayoutBuilder::Group<
BLayoutBuilder::Group<void *> > > ratingGroup =
verticalGroup.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING);
BFont nameFont(be_bold_font); if (rating.Rating() >= 0) {
nameFont.SetSize(std::max(9.0f, floorf(nameFont.Size() * 0.9f))); RatingView* ratingView = new RatingView("package rating view");
fNameView->SetFont(&nameFont); ratingView->SetRating(rating.Rating());
fNameView->SetExplicitMaxSize( ratingGroup.Add(ratingView);
BSize(nameFont.StringWidth("xxxxxxxxxxxxxxxxxxxxxx"), B_SIZE_UNSET)); }
fRatingView = new RatingView("package rating view"); {
fRatingView->SetRating(rating.Rating()); BDateFormat dateFormat;
BString createTimestampPresentation;
BString ratingLabel; dateFormat.Format(createTimestampPresentation,
if (rating.Rating() >= 0.0f) rating.CreateTimestamp().Date(), B_MEDIUM_DATE_FORMAT);
ratingLabel.SetToFormat("%.1f", rating.Rating());
fRatingLabelView = new BStringView("rating label", ratingLabel);
BString versionLabel(B_TRANSLATE("for %Version%")); BString ratingContextDescription(
versionLabel.ReplaceAll("%Version%", rating.PackageVersion()); B_TRANSLATE("%hd.timestamp% (version %hd.version%)"));
fPackageVersionView = new BStringView("package version", ratingContextDescription.ReplaceAll("%hd.timestamp%",
versionLabel); createTimestampPresentation);
BFont versionFont(be_plain_font); ratingContextDescription.ReplaceAll("%hd.version%",
versionFont.SetSize(std::max(9.0f, floorf(versionFont.Size() * 0.85f))); rating.PackageVersion());
fPackageVersionView->SetFont(&versionFont);
// TODO: User rating IDs to identify which rating to vote up or down BStringView* ratingContextView = new BStringView("rating-context",
// BMessage* voteUpMessage = new BMessage(MSG_VOTE_UP); ratingContextDescription);
// voteUpMessage->AddInt32("rating id", -1); BFont versionFont(be_plain_font);
// BMessage* voteDownMessage = new BMessage(MSG_VOTE_DOWN); ratingContextView->SetFont(&versionFont);
// voteDownMessage->AddInt32("rating id", -1); ratingGroup.Add(ratingContextView);
// }
// fVoteUpIconView = new BitmapButton("vote up icon", voteUpMessage);
// fUpVoteCountView = new BStringView("up vote count", "");
// fVoteDownIconView = new BitmapButton("vote down icon", voteDownMessage);
// fDownVoteCountView = new BStringView("up vote count", "");
//
// fVoteUpIconView->SetBitmap(voteUpIcon, SharedBitmap::SIZE_16);
// fVoteDownIconView->SetBitmap(voteDownIcon, SharedBitmap::SIZE_16);
//
// fUpVoteCountView->SetFont(&versionFont);
// fUpVoteCountView->SetHighColor(kLightBlack);
// fDownVoteCountView->SetFont(&versionFont);
// fDownVoteCountView->SetHighColor(kLightBlack);
//
// BString voteCountLabel;
// voteCountLabel.SetToFormat("%" B_PRId32, rating.UpVotes());
// fUpVoteCountView->SetText(voteCountLabel);
// voteCountLabel.SetToFormat("%" B_PRId32, rating.DownVotes());
// fDownVoteCountView->SetText(voteCountLabel);
fTextView = new TextView("rating text"); ratingGroup.AddGlue();
ParagraphStyle paragraphStyle(fTextView->ParagraphStyle()); ratingGroup.End();
paragraphStyle.SetJustify(true);
fTextView->SetParagraphStyle(paragraphStyle);
fTextView->SetText(rating.Comment()); if (rating.Comment() > 0) {
TextView* textView = new TextView("rating-text");
ParagraphStyle paragraphStyle(textView->ParagraphStyle());
paragraphStyle.SetJustify(true);
textView->SetParagraphStyle(paragraphStyle);
textView->SetText(rating.Comment());
verticalGroup.AddStrut(8.0f);
verticalGroup.Add(textView);
verticalGroup.AddStrut(8.0f);
}
BLayoutBuilder::Group<>(this) verticalGroup
.Add(fAvatarView, 0.2f)
.AddGroup(B_VERTICAL, 0.0f)
.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
.Add(fNameView)
.Add(fRatingView)
.Add(fRatingLabelView)
.AddGlue(0.1f)
.Add(fPackageVersionView)
.AddGlue(5.0f)
// .AddGroup(B_HORIZONTAL, 0.0f, 0.0f)
// .Add(fVoteUpIconView)
// .Add(fUpVoteCountView)
// .AddStrut(B_USE_HALF_ITEM_SPACING)
// .Add(fVoteDownIconView)
// .Add(fDownVoteCountView)
// .End()
.End()
.Add(fTextView)
.End() .End()
.SetInsets(B_USE_DEFAULT_SPACING) .SetInsets(B_USE_DEFAULT_SPACING);
;
SetFlags(Flags() | B_WILL_DRAW); SetFlags(Flags() | B_WILL_DRAW);
} }
@@ -976,19 +950,6 @@ public:
StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom()); StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom());
} }
private:
BitmapView* fAvatarView;
BStringView* fNameView;
RatingView* fRatingView;
BStringView* fRatingLabelView;
BStringView* fPackageVersionView;
// BitmapView* fVoteUpIconView;
// BStringView* fUpVoteCountView;
// BitmapView* fVoteDownIconView;
// BStringView* fDownVoteCountView;
TextView* fTextView;
}; };
@@ -1062,9 +1023,7 @@ class UserRatingsView : public BGroupView {
public: public:
UserRatingsView() UserRatingsView()
: :
BGroupView("package ratings view", B_HORIZONTAL), BGroupView("package ratings view", B_HORIZONTAL)
fThumbsUpIcon(BitmapRef(new SharedBitmap(502), true)),
fThumbsDownIcon(BitmapRef(new SharedBitmap(503), true))
{ {
SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
@@ -1077,6 +1036,8 @@ public:
BScrollView* scrollView = new RatingsScrollView( BScrollView* scrollView = new RatingsScrollView(
"ratings scroll view", ratingsContainerView); "ratings scroll view", ratingsContainerView);
scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
B_SIZE_UNLIMITED));
BLayoutBuilder::Group<>(this) BLayoutBuilder::Group<>(this)
.AddGroup(B_VERTICAL) .AddGroup(B_VERTICAL)
@@ -1084,6 +1045,7 @@ public:
.AddGlue() .AddGlue()
.SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 0.0f) .SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 0.0f)
.End() .End()
.AddStrut(64.0)
.Add(scrollView, 1.0f) .Add(scrollView, 1.0f)
.SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f) .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f)
; ;
@@ -1122,15 +1084,10 @@ public:
// TODO: Sort by age or usefullness rating // TODO: Sort by age or usefullness rating
for (int i = count - 1; i >= 0; i--) { for (int i = count - 1; i >= 0; i--) {
const UserRating& rating = userRatings.ItemAtFast(i); const UserRating& rating = userRatings.ItemAtFast(i);
// Prevent ratings from showing that have a comment which // was previously filtering comments just for the current
// is in another language // user's language, but as there are not so many comments at
if (!rating.Comment().IsEmpty() // the moment, just show all of them for now.
&& fPreferredLanguages.CountItems() > 0 RatingItemView* view = new RatingItemView(rating);
&& !fPreferredLanguages.Contains(rating.Language())) {
continue;
}
RatingItemView* view = new RatingItemView(rating, fThumbsUpIcon,
fThumbsDownIcon);
fRatingContainerLayout->AddView(0, view); fRatingContainerLayout->AddView(0, view);
} }
} }
@@ -1181,8 +1138,6 @@ private:
private: private:
BGroupLayout* fRatingContainerLayout; BGroupLayout* fRatingContainerLayout;
RatingSummaryView* fRatingSummaryView; RatingSummaryView* fRatingSummaryView;
BitmapRef fThumbsUpIcon;
BitmapRef fThumbsDownIcon;
StringList fPreferredLanguages; StringList fPreferredLanguages;
}; };
@@ -1516,5 +1471,4 @@ PackageInfoView::Clear()
fPackageListener->SetPackage(PackageInfoRef(NULL)); fPackageListener->SetPackage(PackageInfoRef(NULL));
fPackage.Unset(); fPackage.Unset();
} }
+224 -131
View File
@@ -1,6 +1,6 @@
/* /*
* Copyright 2014, Stephan Aßmus <superstippi@gmx.de>. * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2016, Andrew Lindesay <apl@lindesay.co.nz>. * Copyright 2016-2018, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
@@ -21,8 +21,10 @@
#include <ScrollView.h> #include <ScrollView.h>
#include <StringView.h> #include <StringView.h>
#include "HaikuDepotConstants.h"
#include "MarkupParser.h" #include "MarkupParser.h"
#include "RatingView.h" #include "RatingView.h"
#include "ServerHelper.h"
#include "TextDocumentView.h" #include "TextDocumentView.h"
#include "WebAppInterface.h" #include "WebAppInterface.h"
@@ -32,11 +34,12 @@
enum { enum {
MSG_SEND = 'send', MSG_SEND = 'send',
MSG_PACKAGE_RATED = 'rpkg', MSG_PACKAGE_RATED = 'rpkg',
MSG_STABILITY_SELECTED = 'stbl', MSG_STABILITY_SELECTED = 'stbl',
MSG_LANGUAGE_SELECTED = 'lngs', MSG_LANGUAGE_SELECTED = 'lngs',
MSG_RATING_ACTIVE_CHANGED = 'rtac' MSG_RATING_ACTIVE_CHANGED = 'rtac',
MSG_RATING_DETERMINATE_CHANGED = 'rdch'
}; };
//! Layouts the scrollbar so it looks nice with no border and the document //! Layouts the scrollbar so it looks nice with no border and the document
@@ -98,7 +101,8 @@ public:
SetRatingView() SetRatingView()
: :
RatingView("rate package view"), RatingView("rate package view"),
fPermanentRating(0.0f) fPermanentRating(0.0f),
fRatingDeterminate(true)
{ {
SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
SetRating(fPermanentRating); SetRating(fPermanentRating);
@@ -134,6 +138,24 @@ public:
SetRating(rating); SetRating(rating);
} }
/*! By setting this to false, this indicates that there is no rating for the
set; ie NULL. The indeterminate rating is indicated by a pale grey
colored star.
*/
void SetRatingDeterminate(bool value) {
fRatingDeterminate = value;
Invalidate();
}
protected:
virtual const BBitmap* StarBitmap()
{
if (fRatingDeterminate)
return fStarBlueBitmap.Bitmap(SharedBitmap::SIZE_16);
return fStarGrayBitmap.Bitmap(SharedBitmap::SIZE_16);
}
private: private:
float _RatingForMousePos(BPoint where) float _RatingForMousePos(BPoint where)
{ {
@@ -141,6 +163,7 @@ private:
} }
float fPermanentRating; float fPermanentRating;
bool fRatingDeterminate;
}; };
@@ -189,6 +212,10 @@ RatePackageWindow::RatePackageWindow(BWindow* parent, BRect frame,
B_TRANSLATE("Your rating:")); B_TRANSLATE("Your rating:"));
fSetRatingView = new SetRatingView(); fSetRatingView = new SetRatingView();
fSetRatingView->SetRatingDeterminate(false);
fRatingDeterminateCheckBox = new BCheckBox("has rating", NULL,
new BMessage(MSG_RATING_DETERMINATE_CHANGED));
fRatingDeterminateCheckBox->SetValue(B_CONTROL_OFF);
fTextView = new TextDocumentView(); fTextView = new TextDocumentView();
ScrollView* textScrollView = new ScrollView( ScrollView* textScrollView = new ScrollView(
@@ -258,7 +285,10 @@ RatePackageWindow::RatePackageWindow(BWindow* parent, BRect frame,
BLayoutBuilder::Group<>(this, B_VERTICAL) BLayoutBuilder::Group<>(this, B_VERTICAL)
.AddGrid() .AddGrid()
.Add(ratingLabel, 0, 0) .Add(ratingLabel, 0, 0)
.Add(fSetRatingView, 1, 0) .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 0)
.Add(fRatingDeterminateCheckBox)
.Add(fSetRatingView)
.End()
.AddMenuField(fStabilityField, 0, 1) .AddMenuField(fStabilityField, 0, 1)
.AddMenuField(fCommentLanguageField, 0, 2) .AddMenuField(fCommentLanguageField, 0, 2)
.End() .End()
@@ -284,12 +314,31 @@ RatePackageWindow::~RatePackageWindow()
} }
void
RatePackageWindow::DispatchMessage(BMessage* message, BHandler *handler)
{
if (message->what == B_KEY_DOWN) {
int8 key;
// if the user presses escape, close the window.
if ((message->FindInt8("byte", &key) == B_OK)
&& key == B_ESCAPE) {
Quit();
return;
}
}
BWindow::DispatchMessage(message, handler);
}
void void
RatePackageWindow::MessageReceived(BMessage* message) RatePackageWindow::MessageReceived(BMessage* message)
{ {
switch (message->what) { switch (message->what) {
case MSG_PACKAGE_RATED: case MSG_PACKAGE_RATED:
message->FindFloat("rating", &fRating); message->FindFloat("rating", &fRating);
fSetRatingView->SetRatingDeterminate(true);
fRatingDeterminateCheckBox->SetValue(B_CONTROL_ON);
break; break;
case MSG_STABILITY_SELECTED: case MSG_STABILITY_SELECTED:
@@ -300,6 +349,11 @@ RatePackageWindow::MessageReceived(BMessage* message)
message->FindString("code", &fCommentLanguage); message->FindString("code", &fCommentLanguage);
break; break;
case MSG_RATING_DETERMINATE_CHANGED:
fSetRatingView->SetRatingDeterminate(
fRatingDeterminateCheckBox->Value() == B_CONTROL_ON);
break;
case MSG_RATING_ACTIVE_CHANGED: case MSG_RATING_ACTIVE_CHANGED:
{ {
int32 value; int32 value;
@@ -308,6 +362,32 @@ RatePackageWindow::MessageReceived(BMessage* message)
break; break;
} }
case MSG_DID_ADD_USER_RATING:
{
BAlert* alert = new(std::nothrow) BAlert(
B_TRANSLATE("user_rating"),
B_TRANSLATE("Your rating was uploaded successfully. "
"You can update or remove it at any time by visiting the "
"server application on the web."),
B_TRANSLATE("Close"), NULL, NULL,
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->Go();
_RefreshPackageData();
break;
}
case MSG_DID_UPDATE_USER_RATING:
{
BAlert* alert = new(std::nothrow) BAlert(
B_TRANSLATE("user_rating"),
B_TRANSLATE("Your rating was updated."),
B_TRANSLATE("Close"), NULL, NULL,
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->Go();
_RefreshPackageData();
break;
}
case MSG_SEND: case MSG_SEND:
_SendRating(); _SendRating();
break; break;
@@ -318,6 +398,20 @@ RatePackageWindow::MessageReceived(BMessage* message)
} }
} }
/*! Refresh the data shown about the current page. This may be useful, for
example when somebody adds a rating and that changes the rating of the
package or they add a rating and want to see that immediately. The logic
should round-trip to the server so that actual data is shown.
*/
void
RatePackageWindow::_RefreshPackageData()
{
BMessage message(MSG_SERVER_DATA_CHANGED);
message.AddString("name", fPackage->Name());
be_app->PostMessage(&message);
}
void void
RatePackageWindow::SetPackage(const PackageInfoRef& package) RatePackageWindow::SetPackage(const PackageInfoRef& package)
@@ -360,8 +454,6 @@ RatePackageWindow::_SetWorkerThread(thread_id thread)
bool enabled = thread < 0; bool enabled = thread < 0;
// fTextEditor->SetEnabled(enabled);
// fSetRatingView->SetEnabled(enabled);
fStabilityField->SetEnabled(enabled); fStabilityField->SetEnabled(enabled);
fCommentLanguageField->SetEnabled(enabled); fCommentLanguageField->SetEnabled(enabled);
fSendButton->SetEnabled(enabled); fSendButton->SetEnabled(enabled);
@@ -386,6 +478,68 @@ RatePackageWindow::_QueryRatingThreadEntry(void* data)
} }
/*! A server request has been made to the server and the server has responded
with some data. The data is known not to be an error and now the data can
be extracted into the user interface elements.
*/
void
RatePackageWindow::_RelayServerDataToUI(BMessage& response)
{
if (Lock()) {
response.FindString("code", &fRatingID);
response.FindBool("active", &fRatingActive);
BString comment;
if (response.FindString("comment", &comment) == B_OK) {
MarkupParser parser;
fRatingText = parser.CreateDocumentFromMarkup(comment);
fTextView->SetTextDocument(fRatingText);
}
if (response.FindString("userRatingStabilityCode",
&fStability) == B_OK) {
int32 index = 0;
for (int32 i = fStabilityCodes.CountItems() - 1; i >= 0; i--) {
const StabilityRating& stability
= fStabilityCodes.ItemAtFast(i);
if (stability.Name() == fStability) {
index = i;
break;
}
}
BMenuItem* item = fStabilityField->Menu()->ItemAt(index);
if (item != NULL)
item->SetMarked(true);
}
if (response.FindString("naturalLanguageCode",
&fCommentLanguage) == B_OK) {
BMenuItem* item = fCommentLanguageField->Menu()->ItemAt(
fModel.SupportedLanguages().IndexOf(fCommentLanguage));
if (item != NULL)
item->SetMarked(true);
}
double rating;
if (response.FindDouble("rating", &rating) == B_OK) {
fRating = (float)rating;
fSetRatingView->SetPermanentRating(fRating);
fSetRatingView->SetRatingDeterminate(true);
fRatingDeterminateCheckBox->SetValue(B_CONTROL_ON);
} else {
fSetRatingView->SetRatingDeterminate(false);
fRatingDeterminateCheckBox->SetValue(B_CONTROL_OFF);
}
fRatingActiveCheckBox->SetValue(fRatingActive);
fRatingActiveCheckBox->Show();
fSendButton->SetLabel(B_TRANSLATE("Update"));
Unlock();
} else {
fprintf(stderr, "unable to acquire lock to update the ui\n");
}
}
void void
RatePackageWindow::_QueryRatingThread() RatePackageWindow::_QueryRatingThread()
{ {
@@ -416,67 +570,50 @@ RatePackageWindow::_QueryRatingThread()
if (depot != NULL) if (depot != NULL)
repositoryCode = depot->WebAppRepositoryCode(); repositoryCode = depot->WebAppRepositoryCode();
if (repositoryCode.Length() == 0) { if (repositoryCode.IsEmpty()) {
printf("unable to obtain the repository code for depot; %s\n", printf("unable to obtain the repository code for depot; %s\n",
package->DepotName().String()); package->DepotName().String());
BMessenger(this).SendMessage(B_QUIT_REQUESTED);
} else { } else {
status_t status = interface.RetrieveUserRating( status_t status = interface.RetrieveUserRating(
package->Name(), package->Version(), package->Architecture(), package->Name(), package->Version(), package->Architecture(),
repositoryCode, username, info); repositoryCode, username, info);
// info.PrintToStream(); if (status == B_OK) {
// could be an error or could be a valid response envelope
BMessage result; // containing data.
if (status == B_OK && info.FindMessage("result", &result) == B_OK switch (interface.ErrorCodeFromResponse(info)) {
&& Lock()) { case ERROR_CODE_NONE:
{
result.FindString("code", &fRatingID); //info.PrintToStream();
result.FindBool("active", &fRatingActive); BMessage result;
BString comment; if (info.FindMessage("result", &result) == B_OK) {
if (result.FindString("comment", &comment) == B_OK) { _RelayServerDataToUI(result);
MarkupParser parser; } else {
fRatingText = parser.CreateDocumentFromMarkup(comment); fprintf(stderr, "bad response envelope missing 'result'"
fTextView->SetTextDocument(fRatingText); "entry\n");
} ServerHelper::NotifyTransportError(B_BAD_VALUE);
if (result.FindString("userRatingStabilityCode", BMessenger(this).SendMessage(B_QUIT_REQUESTED);
&fStability) == B_OK) {
int32 index = 0;
for (int32 i = fStabilityCodes.CountItems() - 1; i >= 0; i--) {
const StabilityRating& stability
= fStabilityCodes.ItemAtFast(i);
if (stability.Name() == fStability) {
index = i;
break;
} }
break;
} }
BMenuItem* item = fStabilityField->Menu()->ItemAt(index); case ERROR_CODE_OBJECTNOTFOUND:
if (item != NULL) // an expected response
item->SetMarked(true); fprintf(stderr, "there was no previous rating for this"
" user on this version of this package so a new rating"
" will be added.\n");
break;
default:
ServerHelper::NotifyServerJsonRpcError(info);
BMessenger(this).SendMessage(B_QUIT_REQUESTED);
break;
} }
if (result.FindString("naturalLanguageCode",
&fCommentLanguage) == B_OK) {
BMenuItem* item = fCommentLanguageField->Menu()->ItemAt(
fModel.SupportedLanguages().IndexOf(fCommentLanguage));
if (item != NULL)
item->SetMarked(true);
}
double rating;
if (result.FindDouble("rating", &rating) == B_OK) {
fRating = (float)rating;
fSetRatingView->SetPermanentRating(fRating);
}
fRatingActiveCheckBox->SetValue(fRatingActive);
fRatingActiveCheckBox->Show();
fSendButton->SetLabel(B_TRANSLATE("Update"));
Unlock();
} else { } else {
fprintf(stderr, "rating query: Failed response: %s\n", fprintf(stderr, "an error has arisen communicating with the"
" server to obtain data for an existing rating [%s]\n",
strerror(status)); strerror(status));
if (!info.IsEmpty()) ServerHelper::NotifyTransportError(status);
info.PrintToStream(); BMessenger(this).SendMessage(B_QUIT_REQUESTED);
} }
} }
@@ -501,6 +638,7 @@ RatePackageWindow::_SendRatingThread()
return; return;
} }
BMessenger messenger = BMessenger(this);
BString package = fPackage->Name(); BString package = fPackage->Name();
BString architecture = fPackage->Architecture(); BString architecture = fPackage->Architecture();
BString repositoryCode; BString repositoryCode;
@@ -533,86 +671,41 @@ RatePackageWindow::_SendRatingThread()
status_t status; status_t status;
BMessage info; BMessage info;
if (ratingID.Length() > 0) { if (ratingID.Length() > 0) {
printf("will update the existing user rating [%s]\n",
ratingID.String());
status = interface.UpdateUserRating(ratingID, status = interface.UpdateUserRating(ratingID,
languageCode, comment, stability, rating, active, info); languageCode, comment, stability, rating, active, info);
} else { } else {
status = interface.CreateUserRating(package, architecture, printf("will create a new user rating for pkg [%s]\n",
repositoryCode, languageCode, comment, stability, rating, info); package.String());
status = interface.CreateUserRating(package, fPackage->Version(),
architecture, repositoryCode, languageCode, comment, stability,
rating, info);
} }
BString error = B_TRANSLATE(
"There was a puzzling response from the web service.");
BMessage result;
if (status == B_OK) { if (status == B_OK) {
if (info.FindMessage("result", &result) == B_OK) { // could be an error or could be a valid response envelope
error = ""; // containing data.
} else if (info.FindMessage("error", &result) == B_OK) { switch (interface.ErrorCodeFromResponse(info)) {
result.PrintToStream(); case ERROR_CODE_NONE:
BString message; {
if (result.FindString("message", &message) == B_OK) { if (ratingID.Length() > 0)
if (message == "objectnotfound") { messenger.SendMessage(MSG_DID_UPDATE_USER_RATING);
error = B_TRANSLATE("The package was not found by the " else
"web service. This probably means that it comes " messenger.SendMessage(MSG_DID_ADD_USER_RATING);
"from a depot which is not tracked there. Rating " break;
"such packages is unfortunately not supported.");
} else {
error << B_TRANSLATE(" It responded with: ");
error << message;
}
} }
default:
ServerHelper::NotifyServerJsonRpcError(info);
break;
} }
} else { } else {
error = B_TRANSLATE( fprintf(stderr, "an error has arisen communicating with the"
"It was not possible to contact the web service."); " server to obtain data for an existing rating [%s]\n",
strerror(status));
ServerHelper::NotifyTransportError(status);
} }
if (!error.IsEmpty()) { messenger.SendMessage(B_QUIT_REQUESTED);
BString failedTitle; _SetWorkerThread(-1);
if (ratingID.Length() > 0)
failedTitle = B_TRANSLATE("Failed to update rating");
else
failedTitle = B_TRANSLATE("Failed to rate package");
BAlert* alert = new(std::nothrow) BAlert(
failedTitle,
error,
B_TRANSLATE("Close"), NULL, NULL,
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
if (alert != NULL)
alert->Go();
fprintf(stderr,
B_TRANSLATE("Failed to create or update rating: %s\n"),
error.String());
if (!info.IsEmpty())
info.PrintToStream();
_SetWorkerThread(-1);
} else {
_SetWorkerThread(-1);
fModel.PopulatePackage(fPackage,
Model::POPULATE_FORCE | Model::POPULATE_USER_RATINGS);
BMessenger(this).SendMessage(B_QUIT_REQUESTED);
BString message;
if (ratingID.Length() > 0) {
message = B_TRANSLATE("Your rating was updated successfully.");
} else {
message = B_TRANSLATE("Your rating was uploaded successfully. "
"You can update or remove it at any time by rating the "
"package again.");
}
BAlert* alert = new(std::nothrow) BAlert(
B_TRANSLATE("Success"),
message,
B_TRANSLATE("Close"));
if (alert != NULL)
alert->Go();
}
} }
@@ -1,5 +1,6 @@
/* /*
* Copyright 2014, Stephan Aßmus <superstippi@gmx.de>. * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2018, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef RATE_PACKAGE_WINDOW_H #ifndef RATE_PACKAGE_WINDOW_H
@@ -26,11 +27,15 @@ public:
Model& model); Model& model);
virtual ~RatePackageWindow(); virtual ~RatePackageWindow();
virtual void DispatchMessage(BMessage* message,
BHandler *handler);
virtual void MessageReceived(BMessage* message); virtual void MessageReceived(BMessage* message);
void SetPackage(const PackageInfoRef& package); void SetPackage(const PackageInfoRef& package);
private: private:
void _RelayServerDataToUI(BMessage& result);
void _SendRating(); void _SendRating();
void _SetWorkerThread(thread_id thread); void _SetWorkerThread(thread_id thread);
@@ -41,6 +46,8 @@ private:
static int32 _SendRatingThreadEntry(void* data); static int32 _SendRatingThreadEntry(void* data);
void _SendRatingThread(); void _SendRatingThread();
void _RefreshPackageData();
private: private:
Model& fModel; Model& fModel;
TextDocumentRef fRatingText; TextDocumentRef fRatingText;
@@ -54,6 +61,7 @@ private:
PackageInfoRef fPackage; PackageInfoRef fPackage;
SetRatingView* fSetRatingView; SetRatingView* fSetRatingView;
BCheckBox* fRatingDeterminateCheckBox;
BMenuField* fStabilityField; BMenuField* fStabilityField;
BMenuField* fCommentLanguageField; BMenuField* fCommentLanguageField;
TextDocumentView* fTextView; TextDocumentView* fTextView;
+3 -2
View File
@@ -17,6 +17,7 @@
#include "BarberPole.h" #include "BarberPole.h"
#include "BitmapView.h" #include "BitmapView.h"
#include "HaikuDepotConstants.h"
#include "WebAppInterface.h" #include "WebAppInterface.h"
@@ -29,9 +30,9 @@ static const rgb_color kBackgroundColor = { 51, 102, 152, 255 };
// transparent regions // transparent regions
static BitmapRef sNextButtonIcon( static BitmapRef sNextButtonIcon(
new(std::nothrow) SharedBitmap(505), true); new(std::nothrow) SharedBitmap(RSRC_ARROW_LEFT), true);
static BitmapRef sPreviousButtonIcon( static BitmapRef sPreviousButtonIcon(
new(std::nothrow) SharedBitmap(506), true); new(std::nothrow) SharedBitmap(RSRC_ARROW_RIGHT), true);
ScreenshotWindow::ScreenshotWindow(BWindow* parent, BRect frame) ScreenshotWindow::ScreenshotWindow(BWindow* parent, BRect frame)
+31 -18
View File
@@ -1,5 +1,6 @@
/* /*
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>. * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2018, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
@@ -10,12 +11,15 @@
#include <LayoutUtils.h> #include <LayoutUtils.h>
#include "HaikuDepotConstants.h"
RatingView::RatingView(const char* name) RatingView::RatingView(const char* name)
: :
BView(name, B_WILL_DRAW), BView(name, B_WILL_DRAW),
fStarBitmap(501), fStarBlueBitmap(RSRC_STAR_BLUE),
fRating(-1.0f) fStarGrayBitmap(RSRC_STAR_GREY),
fRating(RATING_MISSING)
{ {
SetViewUIColor(B_PANEL_BACKGROUND_COLOR); SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
SetLowUIColor(ViewUIColor()); SetLowUIColor(ViewUIColor());
@@ -33,16 +37,26 @@ RatingView::AttachedToWindow()
AdoptParentColors(); AdoptParentColors();
} }
/*! This method will return a star image that can be used repeatedly in the
user interface in order to signify the rating given by a user. It could
be grey if no rating is assigned.
*/
const BBitmap*
RatingView::StarBitmap()
{
if (fRating < RATING_MIN)
return fStarGrayBitmap.Bitmap(SharedBitmap::SIZE_16);
return fStarBlueBitmap.Bitmap(SharedBitmap::SIZE_16);
}
void void
RatingView::Draw(BRect updateRect) RatingView::Draw(BRect updateRect)
{ {
FillRect(updateRect, B_SOLID_LOW); FillRect(updateRect, B_SOLID_LOW);
const BBitmap* star = StarBitmap();
if (fRating < 0.0f)
return;
const BBitmap* star = fStarBitmap.Bitmap(SharedBitmap::SIZE_16);
if (star == NULL) { if (star == NULL) {
fprintf(stderr, "No star icon found in application resources.\n"); fprintf(stderr, "No star icon found in application resources.\n");
return; return;
@@ -56,21 +70,20 @@ RatingView::Draw(BRect updateRect)
x += 16 + 2; x += 16 + 2;
} }
if (fRating >= 5.0f) if (fRating >= RATING_MIN && fRating < 5.0f) {
return; SetDrawingMode(B_OP_OVER);
SetDrawingMode(B_OP_OVER); BRect rect(Bounds());
rect.right = x - 2;
rect.left = ceilf(rect.left + (fRating / 5.0f) * rect.Width());
BRect rect(Bounds()); rgb_color color = LowColor();
rect.right = x - 2; color.alpha = 190;
rect.left = ceilf(rect.left + (fRating / 5.0f) * rect.Width()); SetHighColor(color);
rgb_color color = LowColor(); SetDrawingMode(B_OP_ALPHA);
color.alpha = 190; FillRect(rect, B_SOLID_HIGH);
SetHighColor(color); }
SetDrawingMode(B_OP_ALPHA);
FillRect(rect, B_SOLID_HIGH);
} }
+6 -1
View File
@@ -1,5 +1,6 @@
/* /*
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>. * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2018, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef RATING_VIEW_H #ifndef RATING_VIEW_H
@@ -26,8 +27,12 @@ public:
void SetRating(float rating); void SetRating(float rating);
float Rating() const; float Rating() const;
protected:
virtual const BBitmap* StarBitmap();
SharedBitmap fStarBlueBitmap;
SharedBitmap fStarGrayBitmap;
private: private:
SharedBitmap fStarBitmap;
float fRating; float fRating;
}; };