Allow editing business location in Settings.

This commit is contained in:
John Preston 2024-07-17 10:37:14 +02:00
parent 2c3ef13b01
commit 8c55364afa
11 changed files with 266 additions and 33 deletions

View File

@ -2387,6 +2387,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_location_title" = "Location"; "lng_location_title" = "Location";
"lng_location_about" = "Display the location of your business on your account."; "lng_location_about" = "Display the location of your business on your account.";
"lng_location_address" = "Enter Address"; "lng_location_address" = "Enter Address";
"lng_location_set_map" = "Set Location on Map";
"lng_location_fallback" = "You can set your location on the map from your mobile device."; "lng_location_fallback" = "You can set your location on the map from your mobile device.";
"lng_hours_title" = "Business Hours"; "lng_hours_title" = "Business Hours";
@ -3195,6 +3196,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_maps_point" = "Location"; "lng_maps_point" = "Location";
"lng_maps_point_send" = "Send This Location"; "lng_maps_point_send" = "Send This Location";
"lng_maps_point_set" = "Set This Location";
"lng_maps_or_choose" = "Or choose a venue"; "lng_maps_or_choose" = "Or choose a venue";
"lng_maps_places_in_area" = "Places in this area"; "lng_maps_places_in_area" = "Places in this area";
"lng_maps_no_places" = "No places found"; "lng_maps_no_places" = "No places found";

View File

@ -39,7 +39,6 @@ namespace tgcalls {
class InstanceImpl; class InstanceImpl;
class InstanceV2Impl; class InstanceV2Impl;
class InstanceV2ReferenceImpl; class InstanceV2ReferenceImpl;
class InstanceV2_4_0_0Impl;
class InstanceImplLegacy; class InstanceImplLegacy;
void SetLegacyGlobalServerConfig(const std::string &serverConfig); void SetLegacyGlobalServerConfig(const std::string &serverConfig);
} // namespace tgcalls } // namespace tgcalls
@ -56,7 +55,6 @@ const auto kDefaultVersion = "2.4.4"_q;
const auto Register = tgcalls::Register<tgcalls::InstanceImpl>(); const auto Register = tgcalls::Register<tgcalls::InstanceImpl>();
const auto RegisterV2 = tgcalls::Register<tgcalls::InstanceV2Impl>(); const auto RegisterV2 = tgcalls::Register<tgcalls::InstanceV2Impl>();
const auto RegV2Ref = tgcalls::Register<tgcalls::InstanceV2ReferenceImpl>(); const auto RegV2Ref = tgcalls::Register<tgcalls::InstanceV2ReferenceImpl>();
const auto RegisterV240 = tgcalls::Register<tgcalls::InstanceV2_4_0_0Impl>();
const auto RegisterLegacy = tgcalls::Register<tgcalls::InstanceImplLegacy>(); const auto RegisterLegacy = tgcalls::Register<tgcalls::InstanceImplLegacy>();
[[nodiscard]] base::flat_set<int64> CollectEndpointIds( [[nodiscard]] base::flat_set<int64> CollectEndpointIds(

View File

@ -130,6 +130,42 @@ void BusinessInfo::saveChatIntro(ChatIntro data, Fn<void(QString)> fail) {
session->user()->setBusinessDetails(std::move(details)); session->user()->setBusinessDetails(std::move(details));
} }
void BusinessInfo::saveLocation(
BusinessLocation data,
Fn<void(QString)> fail) {
const auto session = &_owner->session();
auto details = session->user()->businessDetails();
const auto &was = details.location;
if (was == data) {
return;
} else {
const auto session = &_owner->session();
using Flag = MTPaccount_UpdateBusinessLocation::Flag;
session->api().request(MTPaccount_UpdateBusinessLocation(
MTP_flags((data.point ? Flag::f_geo_point : Flag())
| (data.address.isEmpty() ? Flag() : Flag::f_address)),
(data.point
? MTP_inputGeoPoint(
MTP_flags(0),
MTP_double(data.point->lat()),
MTP_double(data.point->lon()),
MTPint()) // accuracy_radius
: MTP_inputGeoPointEmpty()),
MTP_string(data.address)
)).fail([=](const MTP::Error &error) {
auto details = session->user()->businessDetails();
details.location = was;
session->user()->setBusinessDetails(std::move(details));
if (fail) {
fail(error.type());
}
}).send();
}
details.location = std::move(data);
session->user()->setBusinessDetails(std::move(details));
}
void BusinessInfo::applyAwaySettings(AwaySettings data) { void BusinessInfo::applyAwaySettings(AwaySettings data) {
if (_awaySettings == data) { if (_awaySettings == data) {
return; return;

View File

@ -22,6 +22,7 @@ public:
void saveWorkingHours(WorkingHours data, Fn<void(QString)> fail); void saveWorkingHours(WorkingHours data, Fn<void(QString)> fail);
void saveChatIntro(ChatIntro data, Fn<void(QString)> fail); void saveChatIntro(ChatIntro data, Fn<void(QString)> fail);
void saveLocation(BusinessLocation data, Fn<void(QString)> fail);
void saveAwaySettings(AwaySettings data, Fn<void(QString)> fail); void saveAwaySettings(AwaySettings data, Fn<void(QString)> fail);
void applyAwaySettings(AwaySettings data); void applyAwaySettings(AwaySettings data);

View File

@ -26,6 +26,11 @@ LocationPoint::LocationPoint(const MTPDgeoPoint &point)
, _access(point.vaccess_hash().v) { , _access(point.vaccess_hash().v) {
} }
LocationPoint::LocationPoint(float64 lat, float64 lon, IgnoreAccessHash)
: _lat(lat)
, _lon(lon) {
}
QString LocationPoint::latAsString() const { QString LocationPoint::latAsString() const {
return AsString(_lat); return AsString(_lat);
} }

View File

@ -16,6 +16,11 @@ public:
LocationPoint() = default; LocationPoint() = default;
explicit LocationPoint(const MTPDgeoPoint &point); explicit LocationPoint(const MTPDgeoPoint &point);
enum IgnoreAccessHash {
NoAccessHash,
};
LocationPoint(float64 lat, float64 lon, IgnoreAccessHash);
[[nodiscard]] QString latAsString() const; [[nodiscard]] QString latAsString() const;
[[nodiscard]] QString lonAsString() const; [[nodiscard]] QString lonAsString() const;
[[nodiscard]] MTPGeoPoint toMTP() const; [[nodiscard]] MTPGeoPoint toMTP() const;
@ -55,7 +60,7 @@ struct InputVenue {
QString venueType; QString venueType;
[[nodiscard]] bool justLocation() const { [[nodiscard]] bool justLocation() const {
return id.isEmpty() && title.isEmpty() && address.isEmpty(); return id.isEmpty();
} }
friend inline bool operator==( friend inline bool operator==(

View File

@ -1822,6 +1822,7 @@ void ChooseAndSendLocation(
Ui::LocationPicker::Show({ Ui::LocationPicker::Show({
.parent = controller->widget(), .parent = controller->widget(),
.config = config, .config = config,
.chooseLabel = tr::lng_maps_point_send(),
.recipient = action.history->peer, .recipient = action.history->peer,
.session = &controller->session(), .session = &controller->session(),
.callback = crl::guard(controller, callback), .callback = crl::guard(controller, callback),

View File

@ -632,7 +632,6 @@ void ChatIntro::setupContent(
} }
void ChatIntro::save() { void ChatIntro::save() {
const auto show = controller()->uiShow();
const auto fail = [=](QString error) { const auto fail = [=](QString error) {
}; };
controller()->session().data().businessInfo().saveChatIntro( controller()->session().data().businessInfo().saveChatIntro(

View File

@ -8,16 +8,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/business/settings_location.h" #include "settings/business/settings_location.h"
#include "core/application.h" #include "core/application.h"
#include "core/shortcuts.h"
#include "data/business/data_business_info.h"
#include "data/data_file_origin.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_app_config.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "settings/business/settings_recipients_helper.h" #include "settings/business/settings_recipients_helper.h"
#include "settings/settings_common.h"
#include "storage/storage_account.h"
#include "ui/controls/location_picker.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/vertical_list.h" #include "ui/vertical_list.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h" #include "styles/style_settings.h"
namespace Settings { namespace Settings {
@ -40,16 +54,37 @@ private:
void setupContent(not_null<Window::SessionController*> controller); void setupContent(not_null<Window::SessionController*> controller);
void save(); void save();
[[nodiscard]] bool mapSupported() const; void setupPicker(not_null<Ui::VerticalLayout*> content);
void setupUnsupported(not_null<Ui::VerticalLayout*> content);
[[nodiscard]] bool mapSupported() const;
void chooseOnMap();
const Ui::LocationPickerConfig _config;
rpl::variable<Data::BusinessLocation> _data;
rpl::variable<Data::CloudImage*> _map = nullptr;
std::shared_ptr<QImage> _view;
Ui::RoundRect _bottomSkipRounding; Ui::RoundRect _bottomSkipRounding;
}; };
[[nodiscard]] Ui::LocationPickerConfig ResolveBusinessMapsConfig(
not_null<Main::Session*> session) {
const auto &appConfig = session->appConfig();
auto map = appConfig.get<base::flat_map<QString, QString>>(
u"tdesktop_config_map"_q,
base::flat_map<QString, QString>());
return {
.mapsToken = map[u"bmaps"_q],
.geoToken = map[u"bgeo"_q],
};
}
Location::Location( Location::Location(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller) not_null<Window::SessionController*> controller)
: BusinessSection(parent, controller) : BusinessSection(parent, controller)
, _config(ResolveBusinessMapsConfig(&controller->session()))
, _bottomSkipRounding(st::boxRadius, st::boxDividerBg) { , _bottomSkipRounding(st::boxRadius, st::boxDividerBg) {
setupContent(controller); setupContent(controller);
} }
@ -65,12 +100,23 @@ rpl::producer<QString> Location::title() {
} }
void Location::setupContent( void Location::setupContent(
not_null<Window::SessionController*> controller) { not_null<Window::SessionController*> controller) {
using namespace rpl::mappers; using namespace rpl::mappers;
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this); const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
#if 0 // #TODO location choosing if (mapSupported()) {
setupPicker(content);
} else {
setupUnsupported(content);
}
Ui::ResizeFitChild(this, content);
}
void Location::setupPicker(not_null<Ui::VerticalLayout*> content) {
_data = controller()->session().user()->businessDetails().location;
AddDividerTextWithLottie(content, { AddDividerTextWithLottie(content, {
.lottie = u"location"_q, .lottie = u"location"_q,
.lottieSize = st::settingsCloudPasswordIconSize, .lottieSize = st::settingsCloudPasswordIconSize,
@ -86,36 +132,158 @@ void Location::setupContent(
st::settingsLocationAddress, st::settingsLocationAddress,
Ui::InputField::Mode::MultiLine, Ui::InputField::Mode::MultiLine,
tr::lng_location_address(), tr::lng_location_address(),
QString()), _data.current().address),
st::settingsChatbotsUsernameMargins); st::settingsChatbotsUsernameMargins);
_data.value(
) | rpl::start_with_next([=](const Data::BusinessLocation &location) {
address->setText(location.address);
}, address->lifetime());
address->changes() | rpl::start_with_next([=] {
auto copy = _data.current();
copy.address = address->getLastText();
_data = std::move(copy);
}, address->lifetime());
AddDivider(content);
AddSkip(content);
const auto maptoggle = AddButtonWithIcon(
content,
tr::lng_location_set_map(),
st::settingsButton,
{ &st::menuIconAddress }
)->toggleOn(_data.value(
) | rpl::map([](const Data::BusinessLocation &location) {
return location.point.has_value();
}));
maptoggle->toggledValue() | rpl::start_with_next([=](bool toggled) {
if (!toggled) {
auto copy = _data.current();
if (copy.point.has_value()) {
copy.point = std::nullopt;
_data = std::move(copy);
}
} else if (!_data.current().point.has_value()) {
_data.force_assign(_data.current());
chooseOnMap();
}
}, maptoggle->lifetime());
const auto mapSkip = st::defaultVerticalListSkip;
const auto mapWrap = content->add(
object_ptr<Ui::SlideWrap<Ui::AbstractButton>>(
content,
object_ptr<Ui::AbstractButton>(content),
st::boxRowPadding + QMargins(0, mapSkip, 0, mapSkip)));
mapWrap->toggle(_data.current().point.has_value(), anim::type::instant);
const auto map = mapWrap->entity();
map->resize(map->width(), st::locationSize.height());
_data.value(
) | rpl::start_with_next([=](const Data::BusinessLocation &location) {
const auto image = location.point.has_value()
? controller()->session().data().location(*location.point).get()
: nullptr;
if (image) {
image->load(&controller()->session(), {});
_view = image->createView();
}
mapWrap->toggle(image != nullptr, anim::type::normal);
_map = image;
}, mapWrap->lifetime());
map->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(map);
const auto left = (map->width() - st::locationSize.width()) / 2;
const auto rect = QRect(QPoint(left, 0), st::locationSize);
const auto &image = _view ? *_view : QImage();
if (!image.isNull()) {
p.drawImage(rect, image);
}
const auto paintMarker = [&](const style::icon &icon) {
icon.paint(
p,
rect.x() + ((rect.width() - icon.width()) / 2),
rect.y() + (rect.height() / 2) - icon.height(),
width());
};
paintMarker(st::historyMapPoint);
paintMarker(st::historyMapPointInner);
}, map->lifetime());
controller()->session().downloaderTaskFinished(
) | rpl::start_with_next([=] {
map->update();
}, map->lifetime());
map->setClickedCallback([=] {
chooseOnMap();
});
showFinishes() | rpl::start_with_next([=] { showFinishes() | rpl::start_with_next([=] {
address->setFocus(); address->setFocus();
}, address->lifetime()); }, address->lifetime());
#endif }
if (!mapSupported()) { void Location::chooseOnMap() {
AddDividerTextWithLottie(content, { const auto callback = [=](Data::InputVenue venue) {
.lottie = u"phone"_q, auto copy = _data.current();
.lottieSize = st::settingsCloudPasswordIconSize, copy.point = Data::LocationPoint(
.lottieMargins = st::peerAppearanceIconPadding, venue.lat,
.showFinished = showFinishes(), venue.lon,
.about = tr::lng_location_fallback(Ui::Text::WithEntities), Data::LocationPoint::NoAccessHash);
.aboutMargins = st::peerAppearanceCoverLabelMargin, copy.address = venue.address;
.parts = RectPart::Top, _data = std::move(copy);
}); };
} else { const auto session = &controller()->session();
const auto current = _data.current().point;
const auto initial = current
? Core::GeoLocation{
.point = { current->lat(), current->lon() },
.accuracy = Core::GeoLocationAccuracy::Exact,
}
: Core::GeoLocation();
Ui::LocationPicker::Show({
.parent = controller()->widget(),
.config = _config,
.chooseLabel = tr::lng_maps_point_set(),
.session = session,
.initial = initial,
.callback = crl::guard(this, callback),
.quit = [] { Shortcuts::Launch(Shortcuts::Command::Quit); },
.storageId = session->local().resolveStorageIdBots(),
.closeRequests = controller()->content()->death(),
});
}
} void Location::setupUnsupported(not_null<Ui::VerticalLayout*> content) {
AddDividerTextWithLottie(content, {
Ui::ResizeFitChild(this, content); .lottie = u"phone"_q,
.lottieSize = st::settingsCloudPasswordIconSize,
.lottieMargins = st::peerAppearanceIconPadding,
.showFinished = showFinishes(),
.about = tr::lng_location_fallback(Ui::Text::WithEntities),
.aboutMargins = st::peerAppearanceCoverLabelMargin,
.parts = RectPart::Top,
});
} }
void Location::save() { void Location::save() {
const auto fail = [=](QString error) {
};
auto value = _data.current();
value.address = value.address.trimmed();
controller()->session().data().businessInfo().saveLocation(value, fail);
} }
bool Location::mapSupported() const { bool Location::mapSupported() const {
return false; return Ui::LocationPicker::Available(_config);
} }
} // namespace } // namespace

View File

@ -427,8 +427,9 @@ void VenuesController::rowPaintIcon(
)"_q; )"_q;
} }
[[nodiscard]] object_ptr<AbstractButton> MakeSendLocationButton( [[nodiscard]] object_ptr<AbstractButton> MakeChooseLocationButton(
QWidget *parent, QWidget *parent,
rpl::producer<QString> label,
rpl::producer<QString> address) { rpl::producer<QString> address) {
auto result = object_ptr<FlatButton>( auto result = object_ptr<FlatButton>(
parent, parent,
@ -465,7 +466,7 @@ void VenuesController::rowPaintIcon(
}); });
const auto name = CreateChild<FlatLabel>( const auto name = CreateChild<FlatLabel>(
raw, raw,
tr::lng_maps_point_send(tr::now), std::move(label),
st::pickLocationButtonText); st::pickLocationButtonText);
name->show(); name->show();
const auto status = CreateChild<FlatLabel>( const auto status = CreateChild<FlatLabel>(
@ -679,10 +680,15 @@ bool LocationPicker::Available(const LocationPickerConfig &config) {
void LocationPicker::setup(const Descriptor &descriptor) { void LocationPicker::setup(const Descriptor &descriptor) {
setupWindow(descriptor); setupWindow(descriptor);
setupWebview(descriptor); setupWebview(descriptor);
if (LastExactLocation) {
venuesRequest(LastExactLocation); _initialProvided = descriptor.initial.exact();
resolveAddress(LastExactLocation); const auto initial = _initialProvided
venuesSearchEnableAt(LastExactLocation); ? descriptor.initial
: LastExactLocation;
if (initial) {
venuesRequest(initial);
resolveAddress(initial);
venuesSearchEnableAt(initial);
} }
} }
@ -717,7 +723,10 @@ void LocationPicker::setupWindow(const Descriptor &descriptor) {
const auto toppad = mapControls->add(object_ptr<RpWidget>(controls)); const auto toppad = mapControls->add(object_ptr<RpWidget>(controls));
const auto button = mapControls->add( const auto button = mapControls->add(
MakeSendLocationButton(mapControls, _geocoderAddress.value()), MakeChooseLocationButton(
mapControls,
std::move(descriptor.chooseLabel),
_geocoderAddress.value()),
{ 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip }); { 0, st::pickLocationButtonSkip, 0, st::pickLocationButtonSkip });
button->setClickedCallback([=] { button->setClickedCallback([=] {
_webview->eval("LocationPicker.send();"); _webview->eval("LocationPicker.send();");
@ -809,7 +818,9 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) {
const auto event = object.value("event").toString(); const auto event = object.value("event").toString();
if (event == u"ready"_q) { if (event == u"ready"_q) {
mapReady(); mapReady();
resolveCurrentLocation(); if (!_initialProvided) {
resolveCurrentLocation();
}
if (_webview) { if (_webview) {
_webview->focus(); _webview->focus();
} }
@ -820,7 +831,11 @@ void LocationPicker::setupWebview(const Descriptor &descriptor) {
} else if (event == u"send"_q) { } else if (event == u"send"_q) {
const auto lat = object.value("latitude").toDouble(); const auto lat = object.value("latitude").toDouble();
const auto lon = object.value("longitude").toDouble(); const auto lon = object.value("longitude").toDouble();
_callback({ lat, lon }); _callback({
.lat = lat,
.lon = lon,
.address = _geocoderAddress.current(),
});
close(); close();
} else if (event == u"move_start"_q) { } else if (event == u"move_start"_q) {
if (const auto now = _geocoderAddress.current() if (const auto now = _geocoderAddress.current()

View File

@ -80,8 +80,10 @@ public:
struct Descriptor { struct Descriptor {
RpWidget *parent = nullptr; RpWidget *parent = nullptr;
LocationPickerConfig config; LocationPickerConfig config;
rpl::producer<QString> chooseLabel;
PeerData *recipient = nullptr; PeerData *recipient = nullptr;
not_null<Main::Session*> session; not_null<Main::Session*> session;
Core::GeoLocation initial;
Fn<void(Data::InputVenue)> callback; Fn<void(Data::InputVenue)> callback;
Fn<void()> quit; Fn<void()> quit;
Webview::StorageId storageId; Webview::StorageId storageId;
@ -132,6 +134,7 @@ private:
std::unique_ptr<Webview::Window> _webview; std::unique_ptr<Webview::Window> _webview;
SingleQueuedInvokation _updateStyles; SingleQueuedInvokation _updateStyles;
bool _subscribedToColors = false; bool _subscribedToColors = false;
bool _initialProvided = false;
base::Timer _geocoderResolveTimer; base::Timer _geocoderResolveTimer;
Core::GeoLocation _geocoderResolvePostponed; Core::GeoLocation _geocoderResolvePostponed;