Added ability to set profile photo from camera.

This commit is contained in:
23rd 2022-03-14 00:52:33 +03:00
parent 3b9ac19482
commit 3cb595c3c9
8 changed files with 297 additions and 189 deletions

View file

@ -1308,6 +1308,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_migrate_learn_more" = "Learn more »"; "lng_profile_migrate_learn_more" = "Learn more »";
"lng_profile_migrate_button" = "Upgrade to supergroup"; "lng_profile_migrate_button" = "Upgrade to supergroup";
"lng_profile_add_more_after_create" = "You will be able to add more members after you create the group."; "lng_profile_add_more_after_create" = "You will be able to add more members after you create the group.";
"lng_profile_camera_title" = "Capture yourself";
"lng_channel_not_accessible" = "Sorry, this channel is not accessible."; "lng_channel_not_accessible" = "Sorry, this channel is not accessible.";
"lng_group_not_accessible" = "Sorry, this group is not accessible."; "lng_group_not_accessible" = "Sorry, this group is not accessible.";
@ -1459,6 +1460,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_attach_failed" = "Failed"; "lng_attach_failed" = "Failed";
"lng_attach_file" = "File"; "lng_attach_file" = "File";
"lng_attach_photo" = "Photo"; "lng_attach_photo" = "Photo";
"lng_attach_camera" = "Camera";
"lng_media_open_with" = "Open With"; "lng_media_open_with" = "Open With";
"lng_media_download" = "Download"; "lng_media_download" = "Download";

View file

@ -67,7 +67,8 @@ void OpenWithPreparedFile(
void PrepareProfilePhoto( void PrepareProfilePhoto(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
not_null<Window::Controller*> controller, not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&doneCallback) { Fn<void(QImage &&image)> &&doneCallback,
QImage &&image) {
const auto resizeToMinSize = [=]( const auto resizeToMinSize = [=](
QImage &&image, QImage &&image,
Qt::AspectRatioMode mode) { Qt::AspectRatioMode mode) {
@ -82,17 +83,6 @@ void PrepareProfilePhoto(
return std::move(image); return std::move(image);
}; };
const auto callback = [=, done = std::move(doneCallback)](
const FileDialog::OpenResult &result) {
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
return;
}
auto image = Images::Read({
.path = result.paths.isEmpty() ? QString() : result.paths.front(),
.content = result.remoteContent,
.forceOpaque = true,
}).image;
if (image.isNull() if (image.isNull()
|| (image.width() > (10 * image.height())) || (image.width() > (10 * image.height()))
|| (image.height() > (10 * image.width()))) { || (image.height() > (10 * image.width()))) {
@ -104,7 +94,7 @@ void PrepareProfilePhoto(
Qt::KeepAspectRatioByExpanding); Qt::KeepAspectRatioByExpanding);
const auto fileImage = std::make_shared<Image>(std::move(image)); const auto fileImage = std::make_shared<Image>(std::move(image));
auto applyModifications = [=, done = std::move(done)]( auto applyModifications = [=, done = std::move(doneCallback)](
const PhotoModifications &mods) { const PhotoModifications &mods) {
done(resizeToMinSize( done(resizeToMinSize(
ImageModified(fileImage->original(), mods), ImageModified(fileImage->original(), mods),
@ -132,6 +122,28 @@ void PrepareProfilePhoto(
.cropType = EditorData::CropType::Ellipse, .cropType = EditorData::CropType::Ellipse,
.keepAspectRatio = true, }), .keepAspectRatio = true, }),
Ui::LayerOption::KeepOther); Ui::LayerOption::KeepOther);
}
void PrepareProfilePhotoFromFile(
not_null<Ui::RpWidget*> parent,
not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&doneCallback) {
const auto callback = [=, done = std::move(doneCallback)](
const FileDialog::OpenResult &result) mutable {
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
return;
}
auto image = Images::Read({
.path = result.paths.isEmpty() ? QString() : result.paths.front(),
.content = result.remoteContent,
.forceOpaque = true,
}).image;
PrepareProfilePhoto(
parent,
controller,
std::move(done),
std::move(image));
}; };
FileDialog::GetOpenPath( FileDialog::GetOpenPath(
parent.get(), parent.get(),

View file

@ -32,6 +32,12 @@ void OpenWithPreparedFile(
Fn<void()> &&doneCallback); Fn<void()> &&doneCallback);
void PrepareProfilePhoto( void PrepareProfilePhoto(
not_null<Ui::RpWidget*> parent,
not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&doneCallback,
QImage &&image);
void PrepareProfilePhotoFromFile(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
not_null<Window::Controller*> controller, not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&doneCallback); Fn<void(QImage &&image)> &&doneCallback);

View file

@ -56,49 +56,45 @@ Calls::Calls(
Calls::~Calls() = default; Calls::~Calls() = default;
void Calls::sectionSaveChanges(FnMut<void()> done) { Webrtc::VideoTrack *Calls::AddCameraSubsection(
if (_micTester) { std::shared_ptr<Ui::Show> show,
_micTester.reset(); not_null<Ui::VerticalLayout*> content,
} bool saveToSettings) {
done(); auto &lifetime = content->lifetime();
}
void Calls::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto &settings = Core::App().settings();
const auto cameras = GetVideoInputList();
if (!cameras.empty()) {
const auto hasCall = (Core::App().calls().currentCall() != nullptr); const auto hasCall = (Core::App().calls().currentCall() != nullptr);
auto capturerOwner = content->lifetime().make_state< const auto cameraNameStream = lifetime.make_state<
rpl::event_stream<QString>
>();
auto capturerOwner = lifetime.make_state<
std::shared_ptr<tgcalls::VideoCaptureInterface> std::shared_ptr<tgcalls::VideoCaptureInterface>
>(); >();
const auto track = content->lifetime().make_state<VideoTrack>( const auto track = lifetime.make_state<VideoTrack>(
(hasCall (hasCall
? VideoState::Inactive ? VideoState::Inactive
: VideoState::Active)); : VideoState::Active));
const auto currentCameraName = [&] { const auto currentCameraName = [&] {
const auto cameras = GetVideoInputList();
const auto i = ranges::find( const auto i = ranges::find(
cameras, cameras,
settings.callVideoInputDeviceId(), Core::App().settings().callVideoInputDeviceId(),
&VideoInput::id); &VideoInput::id);
return (i != end(cameras)) return (i != end(cameras))
? i->name ? i->name
: tr::lng_settings_call_device_default(tr::now); : tr::lng_settings_call_device_default(tr::now);
}(); }();
AddSkip(content);
AddSubsectionTitle(content, tr::lng_settings_call_camera());
AddButtonWithLabel( AddButtonWithLabel(
content, content,
tr::lng_settings_call_input_device(), tr::lng_settings_call_input_device(),
rpl::single( rpl::single(
currentCameraName currentCameraName
) | rpl::then( ) | rpl::then(
_cameraNameStream.events() cameraNameStream->events()
), ),
st::settingsButtonNoIcon st::settingsButtonNoIcon
)->addClickHandler([=] { )->addClickHandler([=] {
@ -115,13 +111,15 @@ void Calls::setupContent() {
const auto currentOption = (i != end(devices)) const auto currentOption = (i != end(devices))
? int(i - begin(devices) + 1) ? int(i - begin(devices) + 1)
: 0; : 0;
const auto save = crl::guard(this, [=](int option) { const auto save = crl::guard(content, [=](int option) {
_cameraNameStream.fire_copy(options[option]); cameraNameStream->fire_copy(options[option]);
const auto deviceId = option const auto deviceId = option
? devices[option - 1].id ? devices[option - 1].id
: "default"; : "default";
if (saveToSettings) {
Core::App().settings().setCallVideoInputDeviceId(deviceId); Core::App().settings().setCallVideoInputDeviceId(deviceId);
Core::App().saveSettingsDelayed(); Core::App().saveSettingsDelayed();
}
if (const auto call = Core::App().calls().currentCall()) { if (const auto call = Core::App().calls().currentCall()) {
call->setCurrentCameraDevice(deviceId); call->setCurrentCameraDevice(deviceId);
} }
@ -131,7 +129,7 @@ void Calls::setupContent() {
false); false);
} }
}); });
_controller->show(Box([=](not_null<Ui::GenericBox*> box) { show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
SingleChoiceBox(box, { SingleChoiceBox(box, {
.title = tr::lng_settings_call_camera(), .title = tr::lng_settings_call_camera(),
.options = options, .options = options,
@ -141,7 +139,7 @@ void Calls::setupContent() {
})); }));
}); });
const auto bubbleWrap = content->add(object_ptr<Ui::RpWidget>(content)); const auto bubbleWrap = content->add(object_ptr<Ui::RpWidget>(content));
const auto bubble = content->lifetime().make_state<::Calls::VideoBubble>( const auto bubble = lifetime.make_state<::Calls::VideoBubble>(
bubbleWrap, bubbleWrap,
track); track);
const auto padding = st::settingsButtonNoIcon.padding.left(); const auto padding = st::settingsButtonNoIcon.padding.left();
@ -202,8 +200,28 @@ void Calls::setupContent() {
} else { } else {
crl::on_main(content, checkCapturer); crl::on_main(content, checkCapturer);
} }
}, content->lifetime()); }, lifetime);
return track;
}
void Calls::sectionSaveChanges(FnMut<void()> done) {
if (_micTester) {
_micTester.reset();
}
done();
}
void Calls::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
if (!GetVideoInputList().empty()) {
AddSkip(content);
AddSubsectionTitle(content, tr::lng_settings_call_camera());
AddCameraSubsection(
std::make_shared<Window::Show>(_controller),
content,
true);
AddSkip(content); AddSkip(content);
AddDivider(content); AddDivider(content);
} }

View file

@ -23,10 +23,12 @@ class Call;
namespace Ui { namespace Ui {
class LevelMeter; class LevelMeter;
class GenericBox; class GenericBox;
class Show;
} // namespace Ui } // namespace Ui
namespace Webrtc { namespace Webrtc {
class AudioInputTester; class AudioInputTester;
class VideoTrack;
} // namespace Webrtc } // namespace Webrtc
namespace Settings { namespace Settings {
@ -38,6 +40,11 @@ public:
void sectionSaveChanges(FnMut<void()> done) override; void sectionSaveChanges(FnMut<void()> done) override;
static Webrtc::VideoTrack *AddCameraSubsection(
std::shared_ptr<Ui::Show> show,
not_null<Ui::VerticalLayout*> content,
bool saveToSettings);
private: private:
void setupContent(); void setupContent();
void requestPermissionAndStartTestingMicrophone(); void requestPermissionAndStartTestingMicrophone();

View file

@ -66,7 +66,7 @@ void SetupPhoto(
auto callback = [=](QImage &&image) { auto callback = [=](QImage &&image) {
self->session().api().peerPhoto().upload(self, std::move(image)); self->session().api().peerPhoto().upload(self, std::move(image));
}; };
Editor::PrepareProfilePhoto( Editor::PrepareProfilePhotoFromFile(
upload, upload,
&controller->window(), &controller->window(),
std::move(callback)); std::move(callback));

View file

@ -7,8 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "ui/special_buttons.h" #include "ui/special_buttons.h"
#include "styles/style_boxes.h" #include "base/call_delayed.h"
#include "styles/style_chat.h"
#include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_layout.h"
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "ui/effects/radial_animation.h" #include "ui/effects/radial_animation.h"
@ -32,19 +31,66 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_instance.h" #include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h" #include "media/streaming/media_streaming_player.h"
#include "media/streaming/media_streaming_document.h" #include "media/streaming/media_streaming_document.h"
#include "settings/settings_calls.h" // Calls::AddCameraSubsection.
#include "calls/calls_instance.h"
#include "webrtc/webrtc_media_devices.h" // Webrtc::GetVideoInputList.
#include "webrtc/webrtc_video_track.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "mainwidget.h" #include "styles/style_boxes.h"
#include "facades.h" #include "styles/style_chat.h"
namespace Ui { namespace Ui {
namespace { namespace {
constexpr auto kAnimationDuration = crl::time(120); constexpr auto kAnimationDuration = crl::time(120);
bool IsCameraAvailable() {
return (Core::App().calls().currentCall() == nullptr)
&& !Webrtc::GetVideoInputList().empty();
}
void CameraBox(
not_null<Ui::GenericBox*> box,
not_null<Window::Controller*> controller,
Fn<void(QImage &&image)> &&doneCallback) {
using namespace Webrtc;
const auto track = Settings::Calls::AddCameraSubsection(
std::make_shared<Ui::BoxShow>(box),
box->verticalLayout(),
false);
if (!track) {
box->closeBox();
return;
}
track->stateValue(
) | rpl::start_with_next([=](const VideoState &state) {
if (state == VideoState::Inactive) {
box->closeBox();
}
}, box->lifetime());
auto done = [=, done = std::move(doneCallback)](QImage &&image) {
box->closeBox();
done(std::move(image));
};
box->setTitle(tr::lng_profile_camera_title());
box->addButton(tr::lng_continue(), [=, done = std::move(done)]() mutable {
Editor::PrepareProfilePhoto(
box,
controller,
std::move(done),
track->frame(FrameRequest()).mirrored(true, false));
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
QString CropTitle(not_null<PeerData*> peer) { QString CropTitle(not_null<PeerData*> peer) {
if (peer->isChat() || peer->isMegagroup()) { if (peer->isChat() || peer->isMegagroup()) {
return tr::lng_create_group_crop(tr::now); return tr::lng_create_group_crop(tr::now);
@ -198,10 +244,7 @@ void UserpicButton::prepare() {
void UserpicButton::setClickHandlerByRole() { void UserpicButton::setClickHandlerByRole() {
switch (_role) { switch (_role) {
case Role::ChangePhoto: case Role::ChangePhoto:
addClickHandler(App::LambdaDelayed( addClickHandler([=] { changePhotoLocally(); });
_st.changeButton.ripple.hideDuration,
this,
[=] { changePhotoLocally(); }));
break; break;
case Role::OpenPhoto: case Role::OpenPhoto:
@ -230,10 +273,26 @@ void UserpicButton::changePhotoLocally(bool requestToUpload) {
_uploadPhotoRequests.fire({}); _uploadPhotoRequests.fire({});
} }
}; };
Editor::PrepareProfilePhoto( const auto chooseFile = [=] {
base::call_delayed(
_st.changeButton.ripple.hideDuration,
crl::guard(this, [=] {
Editor::PrepareProfilePhotoFromFile(
this, this,
_window, _window,
std::move(callback)); callback);
}));
};
if (!IsCameraAvailable()) {
chooseFile();
} else {
_menu = base::make_unique_q<Ui::PopupMenu>(this);
_menu->addAction(tr::lng_attach_file(tr::now), chooseFile);
_menu->addAction(tr::lng_attach_camera(tr::now), [=] {
_window->show(Box(CameraBox, _window, callback));
});
_menu->popup(QCursor::pos());
}
} }
void UserpicButton::openPeerPhoto() { void UserpicButton::openPeerPhoto() {

View file

@ -36,6 +36,8 @@ struct Information;
namespace Ui { namespace Ui {
class PopupMenu;
class HistoryDownButton : public RippleButton { class HistoryDownButton : public RippleButton {
public: public:
HistoryDownButton(QWidget *parent, const style::TwoIconButton &st); HistoryDownButton(QWidget *parent, const style::TwoIconButton &st);
@ -158,6 +160,8 @@ private:
std::unique_ptr<Media::Streaming::Instance> _streamed; std::unique_ptr<Media::Streaming::Instance> _streamed;
PhotoData *_streamedPhoto = nullptr; PhotoData *_streamedPhoto = nullptr;
base::unique_qptr<Ui::PopupMenu> _menu;
bool _showSavedMessagesOnSelf = false; bool _showSavedMessagesOnSelf = false;
bool _canOpenPhoto = false; bool _canOpenPhoto = false;
bool _cursorInChangeOverlay = false; bool _cursorInChangeOverlay = false;