UnigramDev/Unigram/develop • 255025a • 2 files, +84/-31
Carry the gesture inside the page's scrolling host
On settings pages the chip only appeared in the title strip and nowhere in the
page itself. A vertical ScrollViewer takes the touchpad pan before any ancestor
sees it, and the source lives on DetailRoot, which is an ancestor of the page -
so the header, sitting outside the scroller, was the only place left.
The chat history was never evidence to the contrary: what works there is
MessageSelector's source, which is a descendant of the scroller and so wins the
contact. This does the same thing deliberately, putting a second source on the
scrolling host's content element and adding it to the same tracker. Rails and an
X-only source mode leave vertical panning to the ScrollViewer, as they already
do in the chat history.
Only ScrollViewer hosts. A ListViewBase one would need its ItemsPanelRoot, which
does not exist until the list realises, and the list that matters is the chat
history.
The old source is dropped on the way in rather than on the way out, because
OnNavigating is never subscribed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • b8b17c5 • 2 files, +68/-5
Reach the gesture to the foot of a short page, and slide back from the left
Moving the source inside the scrolling host fixed only the long pages. Privacy
and Security worked; Power Saving and Advanced did not, and those are the two
shortest settings pages in the app. A ScrollViewer arranges content shorter than
the viewport at its desired height, so the source reached the rows and stopped -
everything below the last one is bare scroller, which takes the pan.
The source has to sit inside the scrolled content, since that is the only thing
that beats the scroller to the contact. So the content is what has to cover the
viewport: its MinHeight now comes from the host's ViewportHeight, kept current on
SizeChanged. Nothing else moves, because the extent still matches the viewport
and a short page stays unscrollable. ViewportHeight is 0 at OnNavigated, so
SizeChanged is where the height first lands rather than only where it is kept.
Committing the gesture now navigates with a FromLeft slide, mirroring the
FromRight that TLNavigationService uses going forward - the default entrance
transition reads as unrelated to the finger that asked for it. Gated on the power
saving policy, which FrameFacade applies to Navigate but not to GoBack, so
nothing else would have stopped it animating.
Drops a comment that claimed this route lets an INavigablePage answer for
itself. It does not: NavigationService.GoBack goes straight to the frame and
never reaches OnBackRequested. That matches the visible back button, which takes
the same route.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Carry the gesture inside the page's scrolling host
On settings pages the chip only appeared in the title strip and nowhere in the
page itself. A vertical ScrollViewer takes the touchpad pan before any ancestor
sees it, and the source lives on DetailRoot, which is an ancestor of the page -
so the header, sitting outside the scroller, was the only place left.
The chat history was never evidence to the contrary: what works there is
MessageSelector's source, which is a descendant of the scroller and so wins the
contact. This does the same thing deliberately, putting a second source on the
scrolling host's content element and adding it to the same tracker. Rails and an
X-only source mode leave vertical panning to the ScrollViewer, as they already
do in the chat history.
Only ScrollViewer hosts. A ListViewBase one would need its ItemsPanelRoot, which
does not exist until the list realises, and the list that matters is the chat
history.
The old source is dropped on the way in rather than on the way out, because
OnNavigating is never subscribed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • b8b17c5 • 2 files, +68/-5
Reach the gesture to the foot of a short page, and slide back from the left
Moving the source inside the scrolling host fixed only the long pages. Privacy
and Security worked; Power Saving and Advanced did not, and those are the two
shortest settings pages in the app. A ScrollViewer arranges content shorter than
the viewport at its desired height, so the source reached the rows and stopped -
everything below the last one is bare scroller, which takes the pan.
The source has to sit inside the scrolled content, since that is the only thing
that beats the scroller to the contact. So the content is what has to cover the
viewport: its MinHeight now comes from the host's ViewportHeight, kept current on
SizeChanged. Nothing else moves, because the extent still matches the viewport
and a short page stays unscrollable. ViewportHeight is 0 at OnNavigated, so
SizeChanged is where the height first lands rather than only where it is kept.
Committing the gesture now navigates with a FromLeft slide, mirroring the
FromRight that TLNavigationService uses going forward - the default entrance
transition reads as unrelated to the finger that asked for it. Gated on the power
saving policy, which FrameFacade applies to Navigate but not to GoBack, so
nothing else would have stopped it animating.
Drops a comment that claimed this route lets an INavigablePage answer for
itself. It does not: NavigationService.GoBack goes straight to the frame and
never reaches OnBackRequested. That matches the visible back button, which takes
the same route.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • caedfcc • 2 files, +54/-17
Give a short settings page an area that hit-tests
SettingsStickersPage works, and its ScrollingHost is a TableListView, so none of
the scrolling-host code runs for it at all. An ancestor source does get the pan
there, which means the scroller was never the whole story.
The discriminator is hit-testing. SettingsPanel is a bare Panel, and a Panel with
a null Background does not hit-test its own area. Its rows hit-test themselves,
which is why the gesture always worked on a page long enough to be all rows and
nowhere on the empty part of a short one - and why Stickers works from the other
direction, its ListViewItems being real content. With no hit there is no element
to walk up from and no source is ever found.
So the height added in the last commit bought nothing on its own. It needs a
Background to go with it, and Transparent paints the same as null.
Also flips the transition to FromRight. The frame inverts the effect on a back
navigation, so FromRight is what slides the uncovered page in from the left, and
it is the same value TLNavigationService passes going forward.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 54eac9b • 2 files, +46/-96
Take back the two fixes for a cause that was not the cause
Advanced and Power Saving fail over text and buttons, not only over empty space,
so neither the content falling short of the viewport nor a bare Panel declining
to hit-test its own area can be what is stopping them. Both were guesses, both
mutated pages this control does not own, and neither moved the symptom.
The scroller theory that started the sequence is doubtful too: SettingsStickers
works, and its host is a TableListView that ConfigureBackGestureContent skips
altogether, so an ancestor source is evidently enough somewhere. The source it
adds is left in place but is not to be treated as load-bearing.
What is left is a page whose XAML matches, element for element, pages that work.
The notes record what has been ruled out so the next attempt starts from
evidence rather than from another reading of the markup.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 3da7ee8 • 2 files, +47/-16
Keep the scroller manipulable so the pan stays a manipulation
Advanced and Power Saving take the gesture once the window is small enough for
their content to overflow, and refuse it at any size where it fits. That is the
whole of it.
ScrollMode.Auto turns the manipulation off when there is nothing to scroll, and a
touchpad pan over an element that cannot be manipulated is never classified as
one - so no interaction source is consulted anywhere up the tree and the gesture
is absent rather than misrouted, which is why no chip ever appeared.
Every settings page declares VerticalScrollMode="Auto". The difference between
the ones that worked and the ones that did not was never the markup, which is
identical, but whether the content happened to overflow at the size the window
was. Enabled holds the manipulation open regardless of extent, and costs nothing
here: there is still nothing to scroll, so nothing becomes scrollable that was
not already.
The declared value is restored when the page is left.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Give a short settings page an area that hit-tests
SettingsStickersPage works, and its ScrollingHost is a TableListView, so none of
the scrolling-host code runs for it at all. An ancestor source does get the pan
there, which means the scroller was never the whole story.
The discriminator is hit-testing. SettingsPanel is a bare Panel, and a Panel with
a null Background does not hit-test its own area. Its rows hit-test themselves,
which is why the gesture always worked on a page long enough to be all rows and
nowhere on the empty part of a short one - and why Stickers works from the other
direction, its ListViewItems being real content. With no hit there is no element
to walk up from and no source is ever found.
So the height added in the last commit bought nothing on its own. It needs a
Background to go with it, and Transparent paints the same as null.
Also flips the transition to FromRight. The frame inverts the effect on a back
navigation, so FromRight is what slides the uncovered page in from the left, and
it is the same value TLNavigationService passes going forward.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 54eac9b • 2 files, +46/-96
Take back the two fixes for a cause that was not the cause
Advanced and Power Saving fail over text and buttons, not only over empty space,
so neither the content falling short of the viewport nor a bare Panel declining
to hit-test its own area can be what is stopping them. Both were guesses, both
mutated pages this control does not own, and neither moved the symptom.
The scroller theory that started the sequence is doubtful too: SettingsStickers
works, and its host is a TableListView that ConfigureBackGestureContent skips
altogether, so an ancestor source is evidently enough somewhere. The source it
adds is left in place but is not to be treated as load-bearing.
What is left is a page whose XAML matches, element for element, pages that work.
The notes record what has been ruled out so the next attempt starts from
evidence rather than from another reading of the markup.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 3da7ee8 • 2 files, +47/-16
Keep the scroller manipulable so the pan stays a manipulation
Advanced and Power Saving take the gesture once the window is small enough for
their content to overflow, and refuse it at any size where it fits. That is the
whole of it.
ScrollMode.Auto turns the manipulation off when there is nothing to scroll, and a
touchpad pan over an element that cannot be manipulated is never classified as
one - so no interaction source is consulted anywhere up the tree and the gesture
is absent rather than misrouted, which is why no chip ever appeared.
Every settings page declares VerticalScrollMode="Auto". The difference between
the ones that worked and the ones that did not was never the markup, which is
identical, but whether the content happened to overflow at the size the window
was. Enabled holds the manipulation open regardless of extent, and costs nothing
here: there is still nothing to scroll, so nothing becomes scrollable that was
not already.
The declared value is restored when the page is left.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • 99b01ee • 1 files, +6/-2
Record that the gesture is working on a build
Confirmed across the chat history, profile and settings, including the pages that
refused it while their content fitted the window.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 44ceef8 • 2 files, +100/-45
Draw the back indicator out of the edge instead of sliding it in
A circle arriving over the page was the chat list's vocabulary borrowed wholesale,
and it read as stock even after it was made larger and given an armed state.
It is a pill now, and the edge is where it comes from rather than somewhere it
passes. It starts as an 18px sliver whose right edge sits exactly on x=0, so
there is nothing to see until the finger moves, and it lengthens as it is pulled.
Height grows with it, 40 to 48, so the shape reads as elastic rather than as
something arriving at a fixed size. The corner radius stays at half the height,
so it is a true capsule the whole way and lands on a circle exactly when width
and height meet at the threshold - which is also the moment its left edge clears
the boundary and it detaches from what it grew out of. That detach is the armed
state, with a scale pop and full opacity behind it.
The arrow waits until the pill is long enough to hold it, then fades in and
swings upright.
All of it is one expression per property, the geometry's Size and CornerRadius
included, so nothing here costs a frame on the UI thread.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 9029ae8 • 5 files, +122/-136
Put the chat list's indicator back, and frost all three
The pill was worse than what it replaced. This is ChatListListView's indicator on
its own numbers - in from the edge over 55px, growing 0.8 to 1, fading the whole
way - mirrored on X, since ArrowLeft.png is drawn for the right-hand edge and the
chat list flips it the same way for the one it brings in from the left.
The fill is the one thing that is new. The circle is a SpriteVisual painted with
SolidGaussianBrush, so it is frosted over whatever sits behind it rather than a
flat 40%-alpha grey, and the same goes for the ChatListListView and
MessageSelector indicators, which are the same 30px circle.
A shape fill cannot carry an effect brush - CompositionSpriteShape takes colour
and gradient brushes only - so the circle becomes a mask over the effect instead,
drawn once through a CompositionVisualSurface. That keeps the antialiasing a
geometric clip would have thrown away. The mask is applied either way, so where
materials are switched off the fallback is still a circle and not a square.
SolidGaussianBrush keeps its XAML behaviour and now builds its graph through a
static factory the three call sites share.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Record that the gesture is working on a build
Confirmed across the chat history, profile and settings, including the pages that
refused it while their content fitted the window.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 44ceef8 • 2 files, +100/-45
Draw the back indicator out of the edge instead of sliding it in
A circle arriving over the page was the chat list's vocabulary borrowed wholesale,
and it read as stock even after it was made larger and given an armed state.
It is a pill now, and the edge is where it comes from rather than somewhere it
passes. It starts as an 18px sliver whose right edge sits exactly on x=0, so
there is nothing to see until the finger moves, and it lengthens as it is pulled.
Height grows with it, 40 to 48, so the shape reads as elastic rather than as
something arriving at a fixed size. The corner radius stays at half the height,
so it is a true capsule the whole way and lands on a circle exactly when width
and height meet at the threshold - which is also the moment its left edge clears
the boundary and it detaches from what it grew out of. That detach is the armed
state, with a scale pop and full opacity behind it.
The arrow waits until the pill is long enough to hold it, then fades in and
swings upright.
All of it is one expression per property, the geometry's Size and CornerRadius
included, so nothing here costs a frame on the UI thread.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 9029ae8 • 5 files, +122/-136
Put the chat list's indicator back, and frost all three
The pill was worse than what it replaced. This is ChatListListView's indicator on
its own numbers - in from the edge over 55px, growing 0.8 to 1, fading the whole
way - mirrored on X, since ArrowLeft.png is drawn for the right-hand edge and the
chat list flips it the same way for the one it brings in from the left.
The fill is the one thing that is new. The circle is a SpriteVisual painted with
SolidGaussianBrush, so it is frosted over whatever sits behind it rather than a
flat 40%-alpha grey, and the same goes for the ChatListListView and
MessageSelector indicators, which are the same 30px circle.
A shape fill cannot carry an effect brush - CompositionSpriteShape takes colour
and gradient brushes only - so the circle becomes a mask over the effect instead,
drawn once through a CompositionVisualSurface. That keeps the antialiasing a
geometric clip would have thrown away. The mask is applied either way, so where
materials are switched off the fallback is still a circle and not a square.
SolidGaussianBrush keeps its XAML behaviour and now builds its graph through a
static factory the three call sites share.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • d4a7992 • 2 files, +38/-53
Drop the second interaction source the scroll mode made unnecessary
It was placed inside the scrolling host's content back when a scroller taking the
pan looked like the cause. It is not: once the manipulation is held open, an
ancestor source is reached over the content, which is what SettingsStickersPage
has been demonstrating all along - it works on nothing but the DetailRoot source,
its TableListView host never having been given one.
So the method keeps only the VerticalScrollMode it holds open, and no longer has
to reach the host's content element at all.
Two dead visual properties go with it. The sprite's CenterPoint was left from
when it was scaled on its own, and the container's Size does nothing on a
ContainerVisual, which draws nothing and carries no clip. The container's
CenterPoint stays and is now explained: mirroring runs through the X scale, so
the centre has to be the circle's.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 6e51c6f • 3 files, +31/-5
Ask for the back gesture rather than let it be brushed into
72 was borrowed from the reply and folder swipes, and at that distance it fired by
accident. Those two are cheap to trigger and cheap to undo. Leaving the page is
neither, so it now wants 120.
The threshold is public, because MessageSelector has to clamp its own tracker to
the same figure whenever back is what the left-to-right direction does. Left at
72 there, the pull would commit while the indicator was still a little over half
drawn - the tracker would reach its bound long before progress reached 1.
Share keeps 72, since it shares that direction only when back is not using it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 412f875 • 1 files, +8/-3
Guard the location access request in CheckDeviceAccessAsync
Geolocator.RequestAccessAsync is a call into the Windows Geolocation
Service and throws ERROR_SERVICE_DISABLED (0x80070422) when that service
is disabled, as the identical call in SendLocationPopup.FindLocation did
in the field. Treat a throw as access not being available, the way
StartTrackingAsync already does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 4290dae • 1 files, +13/-4
Refuse a drag whose data view cannot be inspected
IDataPackageView::Contains is a cross-process call into the process that
started the drag, and it can be denied, or fail because that process went
away mid-drag. Neither is something the app can act on, so the drop is
declined and the failure logged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Drop the second interaction source the scroll mode made unnecessary
It was placed inside the scrolling host's content back when a scroller taking the
pan looked like the cause. It is not: once the manipulation is held open, an
ancestor source is reached over the content, which is what SettingsStickersPage
has been demonstrating all along - it works on nothing but the DetailRoot source,
its TableListView host never having been given one.
So the method keeps only the VerticalScrollMode it holds open, and no longer has
to reach the host's content element at all.
Two dead visual properties go with it. The sprite's CenterPoint was left from
when it was scaled on its own, and the container's Size does nothing on a
ContainerVisual, which draws nothing and carries no clip. The container's
CenterPoint stays and is now explained: mirroring runs through the X scale, so
the centre has to be the circle's.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 6e51c6f • 3 files, +31/-5
Ask for the back gesture rather than let it be brushed into
72 was borrowed from the reply and folder swipes, and at that distance it fired by
accident. Those two are cheap to trigger and cheap to undo. Leaving the page is
neither, so it now wants 120.
The threshold is public, because MessageSelector has to clamp its own tracker to
the same figure whenever back is what the left-to-right direction does. Left at
72 there, the pull would commit while the indicator was still a little over half
drawn - the tracker would reach its bound long before progress reached 1.
Share keeps 72, since it shares that direction only when back is not using it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 412f875 • 1 files, +8/-3
Guard the location access request in CheckDeviceAccessAsync
Geolocator.RequestAccessAsync is a call into the Windows Geolocation
Service and throws ERROR_SERVICE_DISABLED (0x80070422) when that service
is disabled, as the identical call in SendLocationPopup.FindLocation did
in the field. Treat a throw as access not being available, the way
StartTrackingAsync already does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 4290dae • 1 files, +13/-4
Refuse a drag whose data view cannot be inspected
IDataPackageView::Contains is a cross-process call into the process that
started the drag, and it can be denied, or fail because that process went
away mid-drag. Neither is something the app can act on, so the drop is
declined and the failure logged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • fc20910 • 2 files, +18/-3
Stop the gallery ad taking clicks while it fades out
ShowHide(false) sets _collapsed immediately but only applies Visibility from the
scoped batch's Completed, so the button stays visible and hit-testable for the
whole fade - after the state its click handler needs is gone. Swiping to another
gallery item runs Dispose(), which reaches UpdateAdvertisement(null) and clears
_advertisement, and closing additionally drops the data context, so a click in
that window crashed on _advertisement.UniqueId (NullReferenceException, reported
by crash telemetry on 12.9.1).
The button now stops taking input when hiding starts rather than when the fade
ends, and the handler is guarded as well, because a focused button can still be
invoked from the keyboard or by automation, neither of which hit tests.
Also fix Unload() dropping the delegate before calling PlaybackStopped(), which
left UpdateAdvertisement(null) with nothing to call and the control holding a
stale advertisement.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Stop the gallery ad taking clicks while it fades out
ShowHide(false) sets _collapsed immediately but only applies Visibility from the
scoped batch's Completed, so the button stays visible and hit-testable for the
whole fade - after the state its click handler needs is gone. Swiping to another
gallery item runs Dispose(), which reaches UpdateAdvertisement(null) and clears
_advertisement, and closing additionally drops the data context, so a click in
that window crashed on _advertisement.UniqueId (NullReferenceException, reported
by crash telemetry on 12.9.1).
The button now stops taking input when hiding starts rather than when the fade
ends, and the handler is guarded as well, because a focused button can still be
invoked from the keyboard or by automation, neither of which hit tests.
Also fix Unload() dropping the delegate before calling PlaybackStopped(), which
left UpdateAdvertisement(null) with nothing to call and the control holding a
stale advertisement.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
TGX-Android/Telegram-X/main • 7735236 • 127 files, +1070/-742
Fix lint issues
TGX-Android/Telegram-X/main • 9782a2a • 1 files, +1/-1
Version bump to `1804`
#tgxandroid
Fix lint issues
TGX-Android/Telegram-X/main • 9782a2a • 1 files, +1/-1
Version bump to `1804`
#tgxandroid
🫡3
TGX-Android/Telegram-X/main • f5d2ec8 • 1 files, +12/-10
Add alert when trying to turn contacts sync toggle on
TGX-Android/Telegram-X/main • c73251d • 1 files, +1/-1
Version bump to `1804`
#tgxandroid
Add alert when trying to turn contacts sync toggle on
TGX-Android/Telegram-X/main • c73251d • 1 files, +1/-1
Version bump to `1804`
#tgxandroid
🫡3
UnigramDev/Unigram/record-session • 01bea13 • 9 files, +1363/-1157
Give a recording an owner
The record button held four things: the gesture, the state of the recording, the
capture engine, and the decision of what to do with the result. The engine was a
thread-static singleton every loaded button subscribed to, so a recording started
in one chat drove the state of every other chat and story composer on the thread,
and QuantumProcessed was a single field that whichever button loaded last took
over and the first to unload set back to null.
ChatRecordSession now owns the state and the engine, one per button, which is one
per chat. What is left in the button is the press-and-hold gesture and its own
visuals.
Seven booleans and an integer interface state become three named states. The
fourth one - locked, then stopped - was only reachable through StopRecording(false),
which nothing ever called, so it went out along with the tri-state stop that
neither sent nor discarded what it recorded. Cancel and Complete say which they
are.
Elapsed comes off a monotonic clock rather than DateTime.Now, freezes while
paused, and starts when the capture device is actually open rather than when the
button was pressed. The pause state is reset when a recording ends instead of
leaking into the next one, and the bar detaches from the button on unload.
Permissions go through MediaDevicePermissions, which is what calls already use,
so granting access no longer costs the first press: the old check asked
DeviceAccessInformation and then returned false regardless, leaving the prompt as
the only thing that press did.
MediaCapture.Failed ends the recording instead of only logging it, and a press
too short to record now says so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Give a recording an owner
The record button held four things: the gesture, the state of the recording, the
capture engine, and the decision of what to do with the result. The engine was a
thread-static singleton every loaded button subscribed to, so a recording started
in one chat drove the state of every other chat and story composer on the thread,
and QuantumProcessed was a single field that whichever button loaded last took
over and the first to unload set back to null.
ChatRecordSession now owns the state and the engine, one per button, which is one
per chat. What is left in the button is the press-and-hold gesture and its own
visuals.
Seven booleans and an integer interface state become three named states. The
fourth one - locked, then stopped - was only reachable through StopRecording(false),
which nothing ever called, so it went out along with the tri-state stop that
neither sent nor discarded what it recorded. Cancel and Complete say which they
are.
Elapsed comes off a monotonic clock rather than DateTime.Now, freezes while
paused, and starts when the capture device is actually open rather than when the
button was pressed. The pause state is reset when a recording ends instead of
leaking into the next one, and the bar detaches from the button on unload.
Permissions go through MediaDevicePermissions, which is what calls already use,
so granting access no longer costs the first press: the old check asked
DeviceAccessInformation and then returned false regardless, leaving the prompt as
the only thing that press did.
MediaCapture.Failed ends the recording instead of only logging it, and a press
too short to record now says so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/record-session • 83a4cd0 • 9 files, +1364/-1157
Give a recording an owner
The record button held four things: the gesture, the state of the recording, the
capture engine, and the decision of what to do with the result. The engine was a
thread-static singleton every loaded button subscribed to, so a recording started
in one chat drove the state of every other chat and story composer on the thread,
and QuantumProcessed was a single field that whichever button loaded last took
over and the first to unload set back to null.
ChatRecordSession now owns the state and the engine, one per button, which is one
per chat. What is left in the button is the press-and-hold gesture and its own
visuals.
Seven booleans and an integer interface state become three named states. The
fourth one - locked, then stopped - was only reachable through StopRecording(false),
which nothing ever called, so it went out along with the tri-state stop that
neither sent nor discarded what it recorded. Cancel and Complete say which they
are.
Elapsed comes off a monotonic clock rather than DateTime.Now, freezes while
paused, and starts when the capture device is actually open rather than when the
button was pressed. The pause state is reset when a recording ends instead of
leaking into the next one, and the bar detaches from the button on unload.
Permissions go through MediaDevicePermissions, which is what calls already use,
so granting access no longer costs the first press: the old check asked
DeviceAccessInformation and then returned false regardless, leaving the prompt as
the only thing that press did.
MediaCapture.Failed ends the recording instead of only logging it, and a press
too short to record now says so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Give a recording an owner
The record button held four things: the gesture, the state of the recording, the
capture engine, and the decision of what to do with the result. The engine was a
thread-static singleton every loaded button subscribed to, so a recording started
in one chat drove the state of every other chat and story composer on the thread,
and QuantumProcessed was a single field that whichever button loaded last took
over and the first to unload set back to null.
ChatRecordSession now owns the state and the engine, one per button, which is one
per chat. What is left in the button is the press-and-hold gesture and its own
visuals.
Seven booleans and an integer interface state become three named states. The
fourth one - locked, then stopped - was only reachable through StopRecording(false),
which nothing ever called, so it went out along with the tri-state stop that
neither sent nor discarded what it recorded. Cancel and Complete say which they
are.
Elapsed comes off a monotonic clock rather than DateTime.Now, freezes while
paused, and starts when the capture device is actually open rather than when the
button was pressed. The pause state is reset when a recording ends instead of
leaking into the next one, and the bar detaches from the button on unload.
Permissions go through MediaDevicePermissions, which is what calls already use,
so granting access no longer costs the first press: the old check asked
DeviceAccessInformation and then returned false regardless, leaving the prompt as
the only thing that press did.
MediaCapture.Failed ends the recording instead of only logging it, and a press
too short to record now says so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/record-session • 951c903 • 9 files, +1363/-1157
Give a recording an owner
ChatRecordSession owns the state and the engine, one per button rather than a
thread-static singleton shared by every chat and story composer on the thread.
Seven booleans and an interface state become three named states, and the tri-state
stop that neither sent nor discarded becomes Cancel and Complete. Permissions go
through MediaDevicePermissions, so granting access no longer costs the first press.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Give a recording an owner
ChatRecordSession owns the state and the engine, one per button rather than a
thread-static singleton shared by every chat and story composer on the thread.
Seven booleans and an interface state become three named states, and the tri-state
stop that neither sent nor discarded becomes Cancel and Complete. Permissions go
through MediaDevicePermissions, so granting access no longer costs the first press.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
TGX-Android/Telegram-X/main • 70c21ad • 4 files, +56/-3
Add flavor-specific `registerReceiver`
TGX-Android/Telegram-X/main • 8bbfcaa • 1 files, +1/-1
Version bump to `1804`
#tgxandroid
Add flavor-specific `registerReceiver`
TGX-Android/Telegram-X/main • 8bbfcaa • 1 files, +1/-1
Version bump to `1804`
#tgxandroid
🫡3
UnigramDev/Unigram/develop • 14060e3 • 9 files, +1363/-1157
Give a recording an owner
ChatRecordSession owns the state and the engine, one per button rather than a
thread-static singleton shared by every chat and story composer on the thread.
Seven booleans and an interface state become three named states, and the tri-state
stop that neither sent nor discarded becomes Cancel and Complete. Permissions go
through MediaDevicePermissions, so granting access no longer costs the first press.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Give a recording an owner
ChatRecordSession owns the state and the engine, one per button rather than a
thread-static singleton shared by every chat and story composer on the thread.
Seven booleans and an interface state become three named states, and the tri-state
stop that neither sent nor discarded becomes Cancel and Complete. Permissions go
through MediaDevicePermissions, so granting access no longer costs the first press.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/record-video-size • 32f72e4 • 4 files, +80/-9
Stop a video message at sixty seconds
The bar drew a sixty second ring and nothing ended the recording when it filled.
The limit is one constant now, read by both the ring and the check, and measured
off Elapsed so pausing pauses it.
UnigramDev/Unigram/record-video-size • 7d3c29a • 4 files, +81/-21
Record a video message at the size it is sent at
A sixty second video message was written at whatever the camera gives, 1080p on
most, and then re-encoded to 384 square when sent. The encoding profile now takes
the camera's own aspect scaled so the short side is the video note length, which
leaves the send-time pass a crop.
The camera's format is untouched, so the sharing mode still allows it.
#unigram
Stop a video message at sixty seconds
The bar drew a sixty second ring and nothing ended the recording when it filled.
The limit is one constant now, read by both the ring and the check, and measured
off Elapsed so pausing pauses it.
UnigramDev/Unigram/record-video-size • 7d3c29a • 4 files, +81/-21
Record a video message at the size it is sent at
A sixty second video message was written at whatever the camera gives, 1080p on
most, and then re-encoded to 384 square when sent. The encoding profile now takes
the camera's own aspect scaled so the short side is the video note length, which
leaves the send-time pass a crop.
The camera's format is untouched, so the sharing mode still allows it.
#unigram
UnigramDev/Unigram/develop • 2a261bf • 1 files, +14/-26
Simplify text block pointer logic
UnigramDev/Unigram/develop • 29db484 • 1 files, +19/-4
Improve format reset
UnigramDev/Unigram/develop • 064b3a1 • 3 files, +39/-16
Fix modern debug build
UnigramDev/Unigram/develop • b230601 • 1 files, +1/-1
Fix wrong field reference
UnigramDev/Unigram/develop • 67a5090 • 3 files, +271/-26
Hook fail fast methods for better error reporting
#unigram
Simplify text block pointer logic
UnigramDev/Unigram/develop • 29db484 • 1 files, +19/-4
Improve format reset
UnigramDev/Unigram/develop • 064b3a1 • 3 files, +39/-16
Fix modern debug build
UnigramDev/Unigram/develop • b230601 • 1 files, +1/-1
Fix wrong field reference
UnigramDev/Unigram/develop • 67a5090 • 3 files, +271/-26
Hook fail fast methods for better error reporting
#unigram
UnigramDev/Unigram/develop • 98ccd91 • 4 files, +80/-9
Stop a video message at sixty seconds
The bar drew a sixty second ring and nothing ended the recording when it filled.
The limit is one constant now, read by both the ring and the check, and measured
off Elapsed so pausing pauses it.
UnigramDev/Unigram/develop • f2ed087 • 4 files, +81/-21
Record a video message at the size it is sent at
A sixty second video message was written at whatever the camera gives, 1080p on
most, and then re-encoded to 384 square when sent. The encoding profile now takes
the camera's own aspect scaled so the short side is the video note length, which
leaves the send-time pass a crop.
The camera's format is untouched, so the sharing mode still allows it.
#unigram
Stop a video message at sixty seconds
The bar drew a sixty second ring and nothing ended the recording when it filled.
The limit is one constant now, read by both the ring and the check, and measured
off Elapsed so pausing pauses it.
UnigramDev/Unigram/develop • f2ed087 • 4 files, +81/-21
Record a video message at the size it is sent at
A sixty second video message was written at whatever the camera gives, 1080p on
most, and then re-encoded to 384 square when sent. The encoding profile now takes
the camera's own aspect scaled so the short side is the video note length, which
leaves the send-time pass a crop.
The camera's format is untouched, so the sharing mode still allows it.
#unigram
UnigramDev/Unigram/record-start-on-press • 131d28b • 1 files, +19/-0
Note the sixty-second pause and message drafts as todo
The official apps stop at sixty seconds and show the pause UI rather than
sending, which 4.3 does not, and they keep an unsent recording as a draft,
which Unigram has no notion of.
UnigramDev/Unigram/record-start-on-press • b160874 • 4 files, +185/-38
Open the recording device on the press, not after it
The 300 ms timer that tells a hold from a tap to switch mode used to run
before device init rather than during it. A session now starts unseen and is
committed when the timer ticks, or thrown away when it doesn't, so nothing
the user can see or hear happens before that. A press only warms up where
access has already been granted, and so can never raise a consent prompt.
#unigram
Note the sixty-second pause and message drafts as todo
The official apps stop at sixty seconds and show the pause UI rather than
sending, which 4.3 does not, and they keep an unsent recording as a draft,
which Unigram has no notion of.
UnigramDev/Unigram/record-start-on-press • b160874 • 4 files, +185/-38
Open the recording device on the press, not after it
The 300 ms timer that tells a hold from a tap to switch mode used to run
before device init rather than during it. A session now starts unseen and is
committed when the timer ticks, or thrown away when it doesn't, so nothing
the user can see or hear happens before that. A press only warms up where
access has already been granted, and so can never raise a consent prompt.
#unigram
UnigramDev/Unigram/record-start-on-press • 45da018 • 1 files, +4/-0
Note that the sixty-second stop cannot be resumed from
The limit reuses the pause UI, but a video message gets no more time, so the
pause button has to go rather than offer its resume glyph.
#unigram
Note that the sixty-second stop cannot be resumed from
The limit reuses the pause UI, but a video message gets no more time, so the
pause button has to go rather than offer its resume glyph.
#unigram
Ajaxy/telegram-tt/master • 0e76735 • 3 files, +4/-4
12.0.39
Ajaxy/telegram-tt/master • 15d3e95 • 10 files, +726/-62
Layer 228 (#7109)
Ajaxy/telegram-tt/master • 8c997fe • 66 files, +1698/-303
Support Ephemeral Messages (#7110)
Co-authored-by: Nikolay Karavaev <47010047+maxdestor@users.noreply.github.com>
Co-authored-by: Dmitry Kabanov <153344039+dmitrykabanovdev@users.noreply.github.com>
Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
Ajaxy/telegram-tt/master • f5032b3 • 47 files, +861/-40
Support global Auto Delete (#7118)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Ajaxy/telegram-tt/master • 575bf0a • 14 files, +272/-196
Story Composer: Redesign (#7126)
Ajaxy/telegram-tt/master • 96a9411 • 10 files, +1095/-1069
Profile Settings: Add new icons (#7136)
Ajaxy/telegram-tt/master • 280e006 • 37 files, +574/-771
Media: Use responsive styling (#7132)
Ajaxy/telegram-tt/master • f222d66 • 44 files, +1751/-110
Support Communities (#7101)
Ajaxy/telegram-tt/master • d1e1618 • 41 files, +1437/-510
Rich Content: Support copy (#7127)
Ajaxy/telegram-tt/master • 91ad760 • 1 files, +9/-10
CI: Skip build on failed version check (#7211)
Ajaxy/telegram-tt/master • 5bb07f5 • 1 files, +1/-2
Chat Report Pane: Fix history deletion in private chats (#7212)
Ajaxy/telegram-tt/master • cbd8f1f • 1 files, +1/-1
Lovely Chart: Fix shifted hover dot (#7214)
Ajaxy/telegram-tt/master • 8c63d46 • 1 files, +7/-2
Stories: Fix long names in stories header (#7215)
Ajaxy/telegram-tt/master • d616b2b • 2 files, +8/-4
Chat: Fix infinite loading on new user conversation(#7213)
#webz
12.0.39
Ajaxy/telegram-tt/master • 15d3e95 • 10 files, +726/-62
Layer 228 (#7109)
Ajaxy/telegram-tt/master • 8c997fe • 66 files, +1698/-303
Support Ephemeral Messages (#7110)
Co-authored-by: Nikolay Karavaev <47010047+maxdestor@users.noreply.github.com>
Co-authored-by: Dmitry Kabanov <153344039+dmitrykabanovdev@users.noreply.github.com>
Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
Ajaxy/telegram-tt/master • f5032b3 • 47 files, +861/-40
Support global Auto Delete (#7118)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Ajaxy/telegram-tt/master • 575bf0a • 14 files, +272/-196
Story Composer: Redesign (#7126)
Ajaxy/telegram-tt/master • 96a9411 • 10 files, +1095/-1069
Profile Settings: Add new icons (#7136)
Ajaxy/telegram-tt/master • 280e006 • 37 files, +574/-771
Media: Use responsive styling (#7132)
Ajaxy/telegram-tt/master • f222d66 • 44 files, +1751/-110
Support Communities (#7101)
Ajaxy/telegram-tt/master • d1e1618 • 41 files, +1437/-510
Rich Content: Support copy (#7127)
Ajaxy/telegram-tt/master • 91ad760 • 1 files, +9/-10
CI: Skip build on failed version check (#7211)
Ajaxy/telegram-tt/master • 5bb07f5 • 1 files, +1/-2
Chat Report Pane: Fix history deletion in private chats (#7212)
Ajaxy/telegram-tt/master • cbd8f1f • 1 files, +1/-1
Lovely Chart: Fix shifted hover dot (#7214)
Ajaxy/telegram-tt/master • 8c63d46 • 1 files, +7/-2
Stories: Fix long names in stories header (#7215)
Ajaxy/telegram-tt/master • d616b2b • 2 files, +8/-4
Chat: Fix infinite loading on new user conversation(#7213)
#webz
Ajaxy/telegram-tt/master • 03fb5b8 • 13 files, +98/-30
Message: Add special copy options (#7219)
Ajaxy/telegram-tt/master • 665d928 • 5 files, +38/-27
Localization: Regular updates (#7221)
Ajaxy/telegram-tt/master • f564a63 • 3 files, +127/-90
Community: Follow-up (#7220)
Ajaxy/telegram-tt/master • ba0a4a5 • 8 files, +30/-4
Premium Modal: Add missing premium features to the list (#7217)
Ajaxy/telegram-tt/master • e474e0c • 1 files, +2/-0
Settings: Fix star style (#7222)
Ajaxy/telegram-tt/master • d7d43d8 • 2 files, +28/-10
Settings: Fix blinking in Auto-Delete settings (#7224)
Ajaxy/telegram-tt/master • 1858d63 • 24 files, +1612/-1290
Audio Player, Message Context Menu: Support "Add to Profile" for audio (#7225)
Ajaxy/telegram-tt/master • da86d68 • 167 files, +2808/-2694
[Build]
#webz
Message: Add special copy options (#7219)
Ajaxy/telegram-tt/master • 665d928 • 5 files, +38/-27
Localization: Regular updates (#7221)
Ajaxy/telegram-tt/master • f564a63 • 3 files, +127/-90
Community: Follow-up (#7220)
Ajaxy/telegram-tt/master • ba0a4a5 • 8 files, +30/-4
Premium Modal: Add missing premium features to the list (#7217)
Ajaxy/telegram-tt/master • e474e0c • 1 files, +2/-0
Settings: Fix star style (#7222)
Ajaxy/telegram-tt/master • d7d43d8 • 2 files, +28/-10
Settings: Fix blinking in Auto-Delete settings (#7224)
Ajaxy/telegram-tt/master • 1858d63 • 24 files, +1612/-1290
Audio Player, Message Context Menu: Support "Add to Profile" for audio (#7225)
Ajaxy/telegram-tt/master • da86d68 • 167 files, +2808/-2694
[Build]
#webz
UnigramDev/Unigram/playback-rework • 1e3ece3 • 1 files, +176/-27
Show album covers in the system media transport controls
The cover is the album cover thumbnail TDLib provides, or the external
variant closest to the size the transport renders when the audio file
carries none. Voice and video notes have no cover and show nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/playback-rework • 920cc42 • 2 files, +103/-49
Fix threading around the transport controls and the player
The transport belongs to the view it was acquired for, so every write to
it now goes back to that view's thread; it can otherwise be driven from a
second window's thread once a Clear has recreated the player there.
Player creation takes the same lock the rest of the player access does,
and repeat mode and shuffle are persisted and mirrored to the transport.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/playback-rework • af08bff • 3 files, +487/-57
Grow the playback playlist as it is played
The playlist was one search of up to a hundred messages around the one
that started it, and the profile audio one was the first hundred audios
with the played one moved to the front. Both now page in as the current
track nears either end, through a source that owns its cursors.
A caller holding a profile source and the audios it loaded can hand both
over, so the playlist keeps the profile order instead of starting again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/playback-rework • d09471c • 2 files, +19/-4
Start a handed-over profile playlist past the items it came with
Paging profile audio is by position, so a caller that hands over items
without the source that loaded them left the cursor at zero and the first
page repeated them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Show album covers in the system media transport controls
The cover is the album cover thumbnail TDLib provides, or the external
variant closest to the size the transport renders when the audio file
carries none. Voice and video notes have no cover and show nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/playback-rework • 920cc42 • 2 files, +103/-49
Fix threading around the transport controls and the player
The transport belongs to the view it was acquired for, so every write to
it now goes back to that view's thread; it can otherwise be driven from a
second window's thread once a Clear has recreated the player there.
Player creation takes the same lock the rest of the player access does,
and repeat mode and shuffle are persisted and mirrored to the transport.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/playback-rework • af08bff • 3 files, +487/-57
Grow the playback playlist as it is played
The playlist was one search of up to a hundred messages around the one
that started it, and the profile audio one was the first hundred audios
with the played one moved to the front. Both now page in as the current
track nears either end, through a source that owns its cursors.
A caller holding a profile source and the audios it loaded can hand both
over, so the playlist keeps the profile order instead of starting again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/playback-rework • d09471c • 2 files, +19/-4
Start a handed-over profile playlist past the items it came with
Paging profile audio is by position, so a caller that hands over items
without the source that loaded them left the cursor at zero and the first
page repeated them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
morethanwords/tweb/master • 79d6a8f • 4 files, +276/-24
Stop replaying dice in a chat you have already read
A group/channel message loaded without its dialog carries no `unread`
flag, so renderMessage falls back to the peer's read cursor. It asked
getReadMaxIdIfUnread for it — but that one answers 0 once the chat has
nothing unread, and `0 < mid` holds for every bubble. Every message of a
fully read chat therefore looked unread: dice and the other interactive
emoji rolled again on every chat open, sticker and message effects
re-played, and every bubble got a pointless read observer.
Extract the cursor itself as getInboxReadMaxId and read that from the
renderer. Own messages are excluded — the inbox cursor tracks incoming
ones and stays below them, so a dice we sent looked unread too. An
unknown cursor (no dialog loaded yet) keeps the old conservative answer:
the read observer is the only thing that marks a history as read, so it
must never be skipped on a guess.
getReadMaxIdIfUnread keeps its contract for the unread delimiter, the
jump target and the sponsored cursor; a 648-case matrix pins it to the
previous implementation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
morethanwords/tweb/master • a2662b6 • 3 files, +249/-4
Validate the URL an Instant View link decodes to
wrapUrl's `iv` branch pulled the target out of `tg://iv?url=...` and handed it
straight to the anchor href, skipping the protocol filter every other branch in
the same function applies. The branch is gated on `safe`, which
wrapTelegramRichText sets from `textUrl.webpage_id != 0` — so the href was only
ever as trustworthy as that one wire field, with no client-side backstop.
Run the decoded URL through matchUrlProtocol the same way the incoming url is
handled a few lines above. Real IV targets are absolute http/https and come out
unchanged; schemeless ones stop resolving relative to our own origin.
Also stop reading a stringified '0' webpage_id as a real webpage: that only
worked because fetchLong keeps longs numeric while they fit in the safe-integer
range, which is not something this call site should depend on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#webk
Stop replaying dice in a chat you have already read
A group/channel message loaded without its dialog carries no `unread`
flag, so renderMessage falls back to the peer's read cursor. It asked
getReadMaxIdIfUnread for it — but that one answers 0 once the chat has
nothing unread, and `0 < mid` holds for every bubble. Every message of a
fully read chat therefore looked unread: dice and the other interactive
emoji rolled again on every chat open, sticker and message effects
re-played, and every bubble got a pointless read observer.
Extract the cursor itself as getInboxReadMaxId and read that from the
renderer. Own messages are excluded — the inbox cursor tracks incoming
ones and stays below them, so a dice we sent looked unread too. An
unknown cursor (no dialog loaded yet) keeps the old conservative answer:
the read observer is the only thing that marks a history as read, so it
must never be skipped on a guess.
getReadMaxIdIfUnread keeps its contract for the unread delimiter, the
jump target and the sponsored cursor; a 648-case matrix pins it to the
previous implementation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
morethanwords/tweb/master • a2662b6 • 3 files, +249/-4
Validate the URL an Instant View link decodes to
wrapUrl's `iv` branch pulled the target out of `tg://iv?url=...` and handed it
straight to the anchor href, skipping the protocol filter every other branch in
the same function applies. The branch is gated on `safe`, which
wrapTelegramRichText sets from `textUrl.webpage_id != 0` — so the href was only
ever as trustworthy as that one wire field, with no client-side backstop.
Run the decoded URL through matchUrlProtocol the same way the incoming url is
handled a few lines above. Real IV targets are absolute http/https and come out
unchanged; schemeless ones stop resolving relative to our own origin.
Also stop reading a stringified '0' webpage_id as a real webpage: that only
worked because fetchLong keeps longs numeric while they fit in the safe-integer
range, which is not something this call site should depend on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#webk
🫡2