Revert "plugins: remove Crowdfunding and Bounties"

partial revert, keep CCS for now
This commit is contained in:
tobtoht
2025-11-12 15:25:16 +01:00
parent 7e8ba768b7
commit 4ad4709fb5
12 changed files with 533 additions and 2 deletions
+1
View File
@@ -33,6 +33,7 @@ option(TOR_INSTALLED "Is Tor installed on the filesystem?" OFF)
# Plugins
option(WITH_PLUGIN_HOME "Include Home tab plugin" ON)
option(WITH_PLUGIN_TICKERS "Include Tickers Home plugin" ON)
option(WITH_PLUGIN_CROWDFUNDING "Include Crowdfunding Home plugin" ON)
option(WITH_PLUGIN_REVUO "Include Revuo Home plugin" ON)
option(WITH_PLUGIN_CALC "Include Calc tab plugin" ON)
+26
View File
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#ifndef FEATHER_CCSENTRY_H
#define FEATHER_CCSENTRY_H
#include <QString>
struct CCSEntry {
CCSEntry()= default;;
QString title;
QString date;
QString address;
QString author;
QString state;
QString url;
QString organizer;
QString currency;
double target_amount = 0;
double raised_amount = 0;
double percentage_funded = 0;
int contributions = 0;
};
#endif //FEATHER_CCSENTRY_H
+100
View File
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#include "CCSModel.h"
CCSModel::CCSModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
void CCSModel::clear() {
beginResetModel();
m_entries.clear();
endResetModel();
}
void CCSModel::updateEntries(const QList<QSharedPointer<CCSEntry>>& entries) {
beginResetModel();
m_entries.clear();
for (const auto& entry : entries) {
m_entries.push_back(entry);
}
endResetModel();
}
int CCSModel::rowCount(const QModelIndex &parent) const{
if (parent.isValid()) {
return 0;
}
return m_entries.count();
}
int CCSModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return ModelColumn::COUNT;
}
QVariant CCSModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.count())
return QVariant();
QSharedPointer<CCSEntry> entry = m_entries.at(index.row());
if(role == Qt::DisplayRole) {
switch(index.column()) {
case Title:
return entry->title;
case Organizer:
return entry->organizer;
case Author:
return QString("%1 ").arg(entry->author);
case Progress:
return QString("%1/%2 %3").arg(QString::number(entry->raised_amount), QString::number(entry->target_amount), entry->currency);
default:
return QVariant();
}
}
return QVariant();
}
QVariant CCSModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole) {
return QVariant();
}
if (orientation == Qt::Horizontal)
{
switch(section) {
case Title:
return QString("Proposal");
case Organizer:
return QString("Organizer ");
case Author:
return QString("Author");
case Progress:
return QString("Progress");
default:
return QVariant();
}
}
return QVariant();
}
QSharedPointer<CCSEntry> CCSModel::entry(int row) {
if (row < 0 || row >= m_entries.size()) {
qCritical("%s: no reddit post for index %d", __FUNCTION__, row);
return QSharedPointer<CCSEntry>();
}
return m_entries.at(row);
}
+43
View File
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#ifndef FEATHER_CCSMODEL_H
#define FEATHER_CCSMODEL_H
#include <QAbstractTableModel>
#include <QSharedPointer>
#include "CCSEntry.h"
class CCSModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum ModelColumn
{
Title = 0,
Organizer,
Author,
Progress,
COUNT
};
explicit CCSModel(QObject *parent);
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
void clear();
void updateEntries(const QList<QSharedPointer<CCSEntry>>& entries);
QSharedPointer<CCSEntry> entry(int row);
private:
QList<QSharedPointer<CCSEntry>> m_entries;
};
#endif //FEATHER_CCSMODEL_H
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#include "CCSProgressDelegate.h"
#include <QApplication>
CCSProgressDelegate::CCSProgressDelegate(CCSModel *model, QWidget *parent)
: QStyledItemDelegate(parent)
, m_model(model)
{
}
void CCSProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const {
if (index.column() != CCSModel::Progress) {
QStyledItemDelegate::paint(painter, option, index);
return;
}
QStyleOptionProgressBar progressBarOption;
progressBarOption.state = QStyle::State_Enabled;
progressBarOption.direction = QApplication::layoutDirection();
progressBarOption.rect = option.rect;
progressBarOption.fontMetrics = QApplication::fontMetrics();
progressBarOption.minimum = 0;
progressBarOption.maximum = 100;
progressBarOption.textAlignment = Qt::AlignCenter;
progressBarOption.textVisible = true;
QSharedPointer<CCSEntry> entry = m_model->entry(index.row());
auto target = QString("%1/%2 XMR").arg(entry->raised_amount).arg(entry->target_amount);
auto progress = (int)entry->percentage_funded;
progressBarOption.progress = progress < 0 ? 0 : progress;
progressBarOption.text = target;
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter); // Draw the progress bar onto the view.
}
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#ifndef FEATHER_CSSPROGRESSDELEGATE_H
#define FEATHER_CSSPROGRESSDELEGATE_H
#include <QStyledItemDelegate>
#include "CCSModel.h"
class CCSProgressDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit CCSProgressDelegate(CCSModel *model, QWidget *parent = nullptr);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
private:
CCSModel *m_model;
};
#endif //FEATHER_CSSPROGRESSDELEGATE_H
+103
View File
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#include "CCSWidget.h"
#include "ui_CCSWidget.h"
#include <QTableWidget>
#include <QJsonArray>
#include "CCSProgressDelegate.h"
#include "utils/Utils.h"
#include "utils/WebsocketNotifier.h"
CCSWidget::CCSWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::CSSWidget)
, m_model(new CCSModel(this))
, m_contextMenu(new QMenu(this))
{
ui->setupUi(this);
ui->treeView->setModel(m_model);
m_contextMenu->addAction("View proposal", this, &CCSWidget::linkClicked);
m_contextMenu->addAction("Donate", this, &CCSWidget::donateClicked);
connect(ui->treeView, &QHeaderView::customContextMenuRequested, this, &CCSWidget::showContextMenu);
connect(ui->treeView, &QTreeView::doubleClicked, this, &CCSWidget::linkClicked);
ui->treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
ui->treeView->header()->setSectionResizeMode(CCSModel::Title, QHeaderView::Stretch);
connect(websocketNotifier(), &WebsocketNotifier::dataReceived, this, [this](const QString& type, const QJsonValue& json) {
if (type == "ccs") {
QJsonArray ccs_data = json.toArray();
QList<QSharedPointer<CCSEntry>> l;
for (const auto& entry: ccs_data) {
auto obj = entry.toObject();
auto c = QSharedPointer<CCSEntry>(new CCSEntry());
if (obj.value("state").toString() != "FUNDING-REQUIRED")
continue;
c->state = obj.value("state").toString();
c->address = obj.value("address").toString();
c->author = obj.value("author").toString();
c->date = obj.value("date").toString();
c->title = obj.value("title").toString();
c->target_amount = obj.value("target_amount").toDouble();
c->raised_amount = obj.value("raised_amount").toDouble();
c->percentage_funded = obj.value("percentage_funded").toDouble();
c->contributions = obj.value("contributions").toInt();
c->organizer = obj.value("organizer").toString();
c->currency = obj.value("currency").toString();
QString urlpath = obj.value("urlpath").toString();
if (c->organizer == "CCS") {
c->url = QString("https://ccs.getmonero.org/%1").arg(urlpath);
}
else if (c->organizer == "MAGIC") {
c->url = QString("https://donate.magicgrants.org/%1").arg(urlpath);
}
else {
continue;
}
l.append(c);
}
m_model->updateEntries(l);
}
});
}
void CCSWidget::linkClicked() {
QModelIndex index = ui->treeView->currentIndex();
auto entry = m_model->entry(index.row());
if (entry) {
Utils::externalLinkWarning(this, entry->url);
}
}
void CCSWidget::donateClicked() {
QModelIndex index = ui->treeView->currentIndex();
auto entry = m_model->entry(index.row());
if (entry) {
emit fillSendTab(entry->address, QString("Donation to %1: %2").arg(entry->organizer, entry->title));
}
}
void CCSWidget::showContextMenu(const QPoint &pos) {
QModelIndex index = ui->treeView->indexAt(pos);
if (!index.isValid()) {
return;
}
m_contextMenu->exec(ui->treeView->viewport()->mapToGlobal(pos));
}
CCSWidget::~CCSWidget() = default;
+45
View File
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#ifndef FEATHER_CSSWIDGET_H
#define FEATHER_CSSWIDGET_H
#include <QItemDelegate>
#include <QMenu>
#include <QObject>
#include <QProgressBar>
#include <QWidget>
#include "CCSModel.h"
#include "CCSEntry.h"
namespace Ui {
class CSSWidget;
}
class CCSWidget : public QWidget
{
Q_OBJECT
public:
explicit CCSWidget(QWidget *parent = nullptr);
~CCSWidget();
signals:
void fillSendTab(const QString &address, const QString &description);
public slots:
void donateClicked();
private slots:
void linkClicked();
private:
void showContextMenu(const QPoint &pos);
QScopedPointer<Ui::CSSWidget> ui;
CCSModel *m_model;
QMenu *m_contextMenu;
};
#endif // FEATHER_CSSWIDGET_H
+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CSSWidget</class>
<widget class="QWidget" name="CSSWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>893</width>
<height>396</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>4</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTreeView" name="treeView">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
<slots>
<slot>donateClicked()</slot>
<slot>linkClicked()</slot>
</slots>
</ui>
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#include "CrowdfundingPlugin.h"
#include "plugins/PluginRegistry.h"
#include "CCSWidget.h"
CrowdfundingPlugin::CrowdfundingPlugin()
{
}
void CrowdfundingPlugin::initialize(Wallet *wallet, QObject *parent) {
this->setParent(parent);
m_tab = new CCSWidget(nullptr);
connect(m_tab, &CCSWidget::fillSendTab, this, &Plugin::fillSendTab);
}
QString CrowdfundingPlugin::id() {
return "crowdfunding";
}
int CrowdfundingPlugin::idx() const {
return 10;
}
QString CrowdfundingPlugin::parent() {
return "home";
}
QString CrowdfundingPlugin::displayName() {
return "Crowdfunding";
}
QString CrowdfundingPlugin::description() {
return {};
}
QString CrowdfundingPlugin::icon() {
return {};
}
QStringList CrowdfundingPlugin::socketData() {
return {"ccs"};
}
Plugin::PluginType CrowdfundingPlugin::type() {
return Plugin::PluginType::TAB;
}
QWidget* CrowdfundingPlugin::tab() {
return m_tab;
}
const bool CrowdfundingPlugin::registered = [] {
PluginRegistry::registerPlugin(CrowdfundingPlugin::create());
PluginRegistry::getInstance().registerPluginCreator(&CrowdfundingPlugin::create);
return true;
}();
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#ifndef CROWDFUNDINGPLUGIN_H
#define CROWDFUNDINGPLUGIN_H
#include "plugins/Plugin.h"
#include "CCSWidget.h"
class CrowdfundingPlugin : public Plugin {
Q_OBJECT
public:
explicit CrowdfundingPlugin();
QString id() override;
int idx() const override;
QString parent() override;
QString displayName() override;
QString description() override;
QString icon() override;
QStringList socketData() override;
PluginType type() override;
QWidget* tab() override;
void initialize(Wallet *wallet, QObject *parent) override;
static CrowdfundingPlugin* create() { return new CrowdfundingPlugin(); }
private:
CCSWidget* m_tab = nullptr;
static const bool registered;
};
#endif //CROWDFUNDINGPLUGIN_H
+2 -2
View File
@@ -27,7 +27,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::warnOnKiImport,{QS("warnOnKiImport"), true}},
{Config::logLevel,{QS("logLevel"), 0}},
{Config::homeWidget,{QS("homeWidget"), "revuo"}},
{Config::homeWidget,{QS("homeWidget"), "ccs"}},
{Config::donateBeg,{QS("donateBeg"), 1}},
{Config::showHistorySyncNotice, {QS("showHistorySyncNotice"), true}},
@@ -123,7 +123,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::useLocalTor, {QS("useLocalTor"), false}},
{Config::initSyncThreshold, {QS("initSyncThreshold"), 360}},
{Config::enabledPlugins, {QS("enabledPlugins"), QStringList{"tickers", "revuo", "calc"}}},
{Config::enabledPlugins, {QS("enabledPlugins"), QStringList{"tickers", "crowdfunding", "revuo", "calc"}}},
{Config::restartRequired, {QS("restartRequired"), false}},
{Config::tickers, {QS("tickers"), QStringList{"XMR", "BTC", "XMR/BTC"}}},