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_about" = "Display the location of your business on your account.";
"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_hours_title" = "Business Hours";
@ -3195,6 +3196,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_maps_point" = "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_places_in_area" = "Places in this area";
"lng_maps_no_places" = "No places found";

View File

@ -39,7 +39,6 @@ namespace tgcalls {
class InstanceImpl;
class InstanceV2Impl;
class InstanceV2ReferenceImpl;
class InstanceV2_4_0_0Impl;
class InstanceImplLegacy;
void SetLegacyGlobalServerConfig(const std::string &serverConfig);
} // namespace tgcalls
@ -56,7 +55,6 @@ const auto kDefaultVersion = "2.4.4"_q;
const auto Register = tgcalls::Register<tgcalls::InstanceImpl>();
const auto RegisterV2 = tgcalls::Register<tgcalls::InstanceV2Impl>();
const auto RegV2Ref = tgcalls::Register<tgcalls::InstanceV2ReferenceImpl>();
const auto RegisterV240 = tgcalls::Register<tgcalls::InstanceV2_4_0_0Impl>();
const auto RegisterLegacy = tgcalls::Register<tgcalls::InstanceImplLegacy>();
[[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));
}
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) {
if (_awaySettings == data) {
return;

View File

@ -22,6 +22,7 @@ public:
void saveWorkingHours(WorkingHours 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 applyAwaySettings(AwaySettings data);

View File

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

View File

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

View File

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

View File

@ -632,7 +632,6 @@ void ChatIntro::setupContent(
}
void ChatIntro::save() {
const auto show = controller()->uiShow();
const auto fail = [=](QString error) {
};
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 "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_user.h"
#include "lang/lang_keys.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "mainwindow.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/widgets/fields/input_field.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/vertical_list.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
namespace Settings {
@ -40,16 +54,37 @@ private:
void setupContent(not_null<Window::SessionController*> controller);
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;
};
[[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(
QWidget *parent,
not_null<Window::SessionController*> controller)
: BusinessSection(parent, controller)
, _config(ResolveBusinessMapsConfig(&controller->session()))
, _bottomSkipRounding(st::boxRadius, st::boxDividerBg) {
setupContent(controller);
}
@ -65,12 +100,23 @@ rpl::producer<QString> Location::title() {
}
void Location::setupContent(
not_null<Window::SessionController*> controller) {
not_null<Window::SessionController*> controller) {
using namespace rpl::mappers;
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, {
.lottie = u"location"_q,
.lottieSize = st::settingsCloudPasswordIconSize,
@ -86,36 +132,158 @@ void Location::setupContent(
st::settingsLocationAddress,
Ui::InputField::Mode::MultiLine,
tr::lng_location_address(),
QString()),
_data.current().address),
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([=] {
address->setFocus();
}, address->lifetime());
#endif
}
if (!mapSupported()) {
AddDividerTextWithLottie(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,
});
} else {
void Location::chooseOnMap() {
const auto callback = [=](Data::InputVenue venue) {
auto copy = _data.current();
copy.point = Data::LocationPoint(
venue.lat,
venue.lon,
Data::LocationPoint::NoAccessHash);
copy.address = venue.address;
_data = std::move(copy);
};
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(),
});
}
}
Ui::ResizeFitChild(this, content);
void Location::setupUnsupported(not_null<Ui::VerticalLayout*> content) {
AddDividerTextWithLottie(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() {
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 {
return false;
return Ui::LocationPicker::Available(_config);
}
} // namespace

View File

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

View File

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