TGX-Android/Telegram-X/main • cb3c194 • 1 files, +2/-3
Increase heap size + Enable caching
TGX-Android/Telegram-X/main • 920a83a • 1 files, +1/-1
Version bump to `1802`
#tgxandroid
Increase heap size + Enable caching
TGX-Android/Telegram-X/main • 920a83a • 1 files, +1/-1
Version bump to `1802`
#tgxandroid
🫡3
TGX-Android/Telegram-X/main • 48ebd44 • 3 files, +5/-5
Exclude `:baseline-profile` from default project configuration
TGX-Android/Telegram-X/main • ad5025c • 1 files, +1/-1
Version bump to `1802`
#tgxandroid
Exclude `:baseline-profile` from default project configuration
TGX-Android/Telegram-X/main • ad5025c • 1 files, +1/-1
Version bump to `1802`
#tgxandroid
🫡3
TGX-Android/Telegram-X/main • 4608042 • 2 files, +28/-32
Improve build speed
TGX-Android/Telegram-X/main • 8177ad8 • 1 files, +1/-1
Version bump to `1802`
#tgxandroid
Improve build speed
TGX-Android/Telegram-X/main • 8177ad8 • 1 files, +1/-1
Version bump to `1802`
#tgxandroid
🫡3
telegramdesktop/tdesktop/nightly • b0d2935 • 2 files, +17/-5
[poll-view] Fixed cut emoji icon in add-option field at some scales.
telegramdesktop/tdesktop/nightly • 82e66c8 • 1 files, +49/-36
[poll-view] Fixed vertical alignment of answer media and text.
telegramdesktop/tdesktop/nightly • d910e4e • 4 files, +53/-15
[poll-view] Fixed answer media dropped for pending webpage.
telegramdesktop/tdesktop/nightly • 0ef0421 • 1 files, +44/-27
[poll-view] Slightly improved answer highlight.
#tdesktop
[poll-view] Fixed cut emoji icon in add-option field at some scales.
telegramdesktop/tdesktop/nightly • 82e66c8 • 1 files, +49/-36
[poll-view] Fixed vertical alignment of answer media and text.
telegramdesktop/tdesktop/nightly • d910e4e • 4 files, +53/-15
[poll-view] Fixed answer media dropped for pending webpage.
telegramdesktop/tdesktop/nightly • 0ef0421 • 1 files, +44/-27
[poll-view] Slightly improved answer highlight.
#tdesktop
🫡3
UnigramDev/Unigram/tdlib-list-t • 828d784 • 2 files, +72/-30
Expose every TDLib vector to WinRT from the schema
SettingsStoragePage binds Statistics.ByChat to ItemsSource and threw
E_INVALIDARG. TG1001 cannot see it: a binding assigns through the declared
IList<T>, so the concrete type is only known at runtime. The schema does know
it, and both parsers materialise a vector as List<T>, so the generator emits
the registration for every one of them - 188, nested vectors included.
That also settles the arrays that were listed by hand. A vector is never an
array, so none of them could ever be reached, and removing them leaves TG1001
reporting exactly one: PremiumGiftPaymentOption[], which the app builds
itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/tdlib-list-t • 636c91d • 2 files, +53/-7
Flag the collection expressions no registration can fix
Targeting a read-only interface, a collection expression compiles to a type
the compiler makes up, and nothing can generate a vtable for that. TG1001
never reaches it: the target is typed, so its reasoning says the generator has
the conversion covered - but the marshaller it emits still needs a CCW for
whatever concrete type turns up.
There are two of them and the element count picks which, so a call site that
works with two elements can break when one is removed. Measured rather than
assumed, along with the targets that are safe: List, IList and ICollection all
produce a real List, a mutable interface needing a mutable instance. That also
retires the reason the generated API used IList - new[] { x } is no longer the
only short spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/tdlib-list-t • 2c74f62 • 4 files, +285/-20
Expose the TDLib vector instantiations from real source
CsWinRT reads these attributes in its own generator, and a generator cannot
see what another generator wrote, so emitting them was a no-op: the storage
page still threw. TDAPI003 now compares the schema against the attributes
actually present, for the next time TDLib adds a vector.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/tdlib-list-t • 224de7f • 25 files, +106/-67
Expose TDLib object vectors as List<T>
Objects are read - bound to ItemsSource, iterated per render - so List makes
the concrete type visible to the analyzer, stops foreach boxing an enumerator
and lets the indexer inline. Functions are only ever written and serialised,
and keep IList. Constructors take IList either way, so arrays and collection
expressions still bind, and a parsed response is cast rather than copied.
AnimatedImage draws no shimmer when Outline is null, hence the shared empty.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#unigram
Expose every TDLib vector to WinRT from the schema
SettingsStoragePage binds Statistics.ByChat to ItemsSource and threw
E_INVALIDARG. TG1001 cannot see it: a binding assigns through the declared
IList<T>, so the concrete type is only known at runtime. The schema does know
it, and both parsers materialise a vector as List<T>, so the generator emits
the registration for every one of them - 188, nested vectors included.
That also settles the arrays that were listed by hand. A vector is never an
array, so none of them could ever be reached, and removing them leaves TG1001
reporting exactly one: PremiumGiftPaymentOption[], which the app builds
itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/tdlib-list-t • 636c91d • 2 files, +53/-7
Flag the collection expressions no registration can fix
Targeting a read-only interface, a collection expression compiles to a type
the compiler makes up, and nothing can generate a vtable for that. TG1001
never reaches it: the target is typed, so its reasoning says the generator has
the conversion covered - but the marshaller it emits still needs a CCW for
whatever concrete type turns up.
There are two of them and the element count picks which, so a call site that
works with two elements can break when one is removed. Measured rather than
assumed, along with the targets that are safe: List, IList and ICollection all
produce a real List, a mutable interface needing a mutable instance. That also
retires the reason the generated API used IList - new[] { x } is no longer the
only short spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/tdlib-list-t • 2c74f62 • 4 files, +285/-20
Expose the TDLib vector instantiations from real source
CsWinRT reads these attributes in its own generator, and a generator cannot
see what another generator wrote, so emitting them was a no-op: the storage
page still threw. TDAPI003 now compares the schema against the attributes
actually present, for the next time TDLib adds a vector.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/tdlib-list-t • 224de7f • 25 files, +106/-67
Expose TDLib object vectors as List<T>
Objects are read - bound to ItemsSource, iterated per render - so List makes
the concrete type visible to the analyzer, stops foreach boxing an enumerator
and lets the indexer inline. Functions are only ever written and serialised,
and keep IList. Constructors take IList either way, so arrays and collection
expressions still bind, and a parsed response is cast rather than copied.
AnimatedImage draws no shimmer when Outline is null, hence the shared empty.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/tdlib-list-t • 4cbb362 • 1 files, +23/-1
Follow x:Bind into the setters the XAML compiler emits
A binding does not assign the property: it calls a Set_ on XamlBindingSetters,
which takes the value as object and belongs to this assembly, so nothing
flagged it. Following that one call reports 83 collections bound from XAML
with no CCW vtable - each one a page that throws when opened.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/tdlib-list-t • 099bab6 • 82 files, +337/-313
Take List<T> in the generated constructors too
The parameter and the field are the same type now, so TdCollection.AsList is
gone. Call sites became [x] and [], which allocates less than it did: the
converter copied every array into a list.
Four needed more than a rewrite. TryGetColors and the two chat-list readers
fill by Add rather than by index, because a List's capacity is not its count.
TextStyleRun shares one empty list, since Array.Empty was free and these sit
on the render path - a shared mutable list is a footgun, and notes has the
plan for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#unigram
Follow x:Bind into the setters the XAML compiler emits
A binding does not assign the property: it calls a Set_ on XamlBindingSetters,
which takes the value as object and belongs to this assembly, so nothing
flagged it. Following that one call reports 83 collections bound from XAML
with no CCW vtable - each one a page that throws when opened.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/tdlib-list-t • 099bab6 • 82 files, +337/-313
Take List<T> in the generated constructors too
The parameter and the field are the same type now, so TdCollection.AsList is
gone. Call sites became [x] and [], which allocates less than it did: the
converter copied every array into a list.
Four needed more than a rewrite. TryGetColors and the two chat-list readers
fill by Add rather than by index, because a List's capacity is not its count.
TextStyleRun shares one empty list, since Array.Empty was free and these sit
on the render path - a shared mutable list is a footgun, and notes has the
plan for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/dragover-access-denied • 7a6aacc • 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
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/location-access-check • 7a24205 • 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>
#unigram
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>
#unigram
UnigramDev/Unigram/media-collection-loading • e9d2435 • 1 files, +21/-4
Handle errors and cancellation when loading media
An Error response fell through silently, and the cancellation token was
never checked, so an abandoned load still appended to the collection.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Handle errors and cancellation when loading media
An Error response fell through silently, and the cancellation token was
never checked, so an abandoned load still appended to the collection.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/composer-readonly-enter • 447e1cc • 1 files, +7/-0
Don't run the editing shortcuts in a read-only composer
Enter without Shift wrote a vertical tab into the document, which
RichEdit rejects with E_ACCESSDENIED whenever the composer has been
made read-only because the user cannot post in the chat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Don't run the editing shortcuts in a read-only composer
Enter without Shift wrote a vertical tab into the document, which
RichEdit rejects with E_ACCESSDENIED whenever the composer has been
made read-only because the user cannot post in the chat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/currency-formatter-fallback • cc19c38 • 2 files, +5/-1
Fall back to a plain currency format when globalization is unavailable
GetCurrencyFormatter treated GlobalizationPreferences as infallible, so a
denied system query escaped as an UnauthorizedAccessException through the
synchronous ContainerContentChanging callback that reaches it. FormatCurrency
already handled a null formatter, but nothing could ever return one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Fall back to a plain currency format when globalization is unavailable
GetCurrencyFormatter treated GlobalizationPreferences as infallible, so a
denied system query escaped as an UnauthorizedAccessException through the
synchronous ContainerContentChanging callback that reaches it. FormatCurrency
already handled a null formatter, but nothing could ever return one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
💅1
UnigramDev/Unigram/delayed-file-source-null • 84c7b53 • 2 files, +8/-2
Tolerate a null file in DelayedFileSource
A sticker with no StickerValue leaves _file null, which every other member
of the hierarchy already supports, so hashing the source crashed on Id.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Tolerate a null file in DelayedFileSource
A sticker with no StickerValue leaves _file null, which every other member
of the hierarchy already supports, so hashing the source crashed on Id.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/gallery-ad-click-race • bf2539b • 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
UnigramDev/Unigram/tdlib-fatal-message • 1373931 • 1 files, +62/-0
Normalize TDLib fatal log lines before hashing
A LOG(FATAL) line becomes the crash report message, and its thread number,
scheduler tag, session index and printed values differ on every crash, so a
single assertion is hashed into a group per report. Keep the source location,
the actor name and the stringified condition, drop the rest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Normalize TDLib fatal log lines before hashing
A LOG(FATAL) line becomes the crash report message, and its thread number,
scheduler tag, session index and printed values differ on every crash, so a
single assertion is hashed into a group per report. Keep the source location,
the actor name and the stringified condition, drop the rest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
TGX-Android/Telegram-X/main • ed3cad0 • 1 files, +2/-0
Disable `lintError`
TGX-Android/Telegram-X/main • 5309275 • 1 files, +1/-1
Version bump to `1803`
#tgxandroid
Disable `lintError`
TGX-Android/Telegram-X/main • 5309275 • 1 files, +1/-1
Version bump to `1803`
#tgxandroid
🫡3
UnigramDev/Unigram/develop • 09f6aca • 1 files, +86/-4
Report the marshalling failures nothing else can see
A managed exception thrown inside a CCW callback reaches neither unhandled
handler: CsWinRT converts it to an HRESULT at the ABI boundary, so the runtime
counts it as handled, and the fail-fast XAML raises on that HRESULT bypasses
the native filter as well. First chance is the only point it is still an
exception, and reports are written synchronously, so the record survives.
Narrow on purpose: the two types CsWinRT throws when it cannot marshal
something, one report per distinct message, eight a session. They draw on the
same token bucket as real crashes, and crash.id stays unwritten so a handled
exception does not mark the session as crashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/develop • d2042c4 • 1 files, +5/-2
Spell out the kernel32 entry points
LibraryImport always generates ExactSpelling, so the bare name is looked up
verbatim and kernel32 only exports the W and A forms. DllImport found them by
probing, which it does because a CharSet leaves ExactSpelling false.
IsPasskeySupported went through GetModuleHandle behind a Lazy, so it threw
once and then threw forever.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/develop • 51c712d • 1 files, +6/-1
Recognise the HRESULT suffix CsWinRT writes
.NET Native appends "(Exception from HRESULT: 0x80004005)", CsWinRT appends
"(0x80004005)". Matching only the first left the suffix attached to every
message on the AOT build, so no sentence matched the translation table and one
fault split into a group per language.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/develop • f94f44c • 1 files, +5/-3
Produce a symbol package for the modern bundle
A sideload build does emit an .appxsym - Telegram.Msix has one beside every
bundle - and without it a tester's crash cannot be symbolicated at all, which
is what happened to the last one handed out.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/develop • c3135cf • 1 files, +1/-1
Invalidate the load in flight when recycling
Recycling reset the generation to 0, so the next load took that same number
and an outstanding continuation still matched - painting its image over the
one now bound to the brush. Advance the counter instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#unigram
Report the marshalling failures nothing else can see
A managed exception thrown inside a CCW callback reaches neither unhandled
handler: CsWinRT converts it to an HRESULT at the ABI boundary, so the runtime
counts it as handled, and the fail-fast XAML raises on that HRESULT bypasses
the native filter as well. First chance is the only point it is still an
exception, and reports are written synchronously, so the record survives.
Narrow on purpose: the two types CsWinRT throws when it cannot marshal
something, one report per distinct message, eight a session. They draw on the
same token bucket as real crashes, and crash.id stays unwritten so a handled
exception does not mark the session as crashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/develop • d2042c4 • 1 files, +5/-2
Spell out the kernel32 entry points
LibraryImport always generates ExactSpelling, so the bare name is looked up
verbatim and kernel32 only exports the W and A forms. DllImport found them by
probing, which it does because a CharSet leaves ExactSpelling false.
IsPasskeySupported went through GetModuleHandle behind a Lazy, so it threw
once and then threw forever.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/develop • 51c712d • 1 files, +6/-1
Recognise the HRESULT suffix CsWinRT writes
.NET Native appends "(Exception from HRESULT: 0x80004005)", CsWinRT appends
"(0x80004005)". Matching only the first left the suffix attached to every
message on the AOT build, so no sentence matched the translation table and one
fault split into a group per language.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/develop • f94f44c • 1 files, +5/-3
Produce a symbol package for the modern bundle
A sideload build does emit an .appxsym - Telegram.Msix has one beside every
bundle - and without it a tester's crash cannot be symbolicated at all, which
is what happened to the last one handed out.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UnigramDev/Unigram/develop • c3135cf • 1 files, +1/-1
Invalidate the load in flight when recycling
Recycling reset the generation to 0, so the next load took that same number
and an outstanding continuation still matched - painting its image over the
one now bound to the brush. Advance the counter instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#unigram
🫡1
UnigramDev/Unigram/develop • 9729f60 • 12 files, +461/-34
Fix the bindings that resolve a property by name
All three read a property off a managed type by name, which NativeAOT resolves
only where the XAML compiler generated an accessor or the type carries
[GeneratedBindableCustomProperty]. The emoji drawer's group headers and the
revenue tabs move to x:Bind, the drawer's three group types gaining a shared
IDrawerGroup so the template has an x:DataType; the Instant View tab header
stays a binding, since the title arrives after navigation, so InstantViewModel
is registered for it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 614b57c • 2 files, +4/-4
Swap night/day icons on Windows 10
UnigramDev/Unigram/develop • 14e59d4 • 1 files, +1/-1
Start selection only WITHIN the text
#unigram
Fix the bindings that resolve a property by name
All three read a property off a managed type by name, which NativeAOT resolves
only where the XAML compiler generated an accessor or the type carries
[GeneratedBindableCustomProperty]. The emoji drawer's group headers and the
revenue tabs move to x:Bind, the drawer's three group types gaining a shared
IDrawerGroup so the template has an x:DataType; the Instant View tab header
stays a binding, since the title arrives after navigation, so InstantViewModel
is registered for it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 614b57c • 2 files, +4/-4
Swap night/day icons on Windows 10
UnigramDev/Unigram/develop • 14e59d4 • 1 files, +1/-1
Start selection only WITHIN the text
#unigram
UnigramDev/Unigram/develop • 4a14080 • 1 files, +4/-0
Name the chart cards for the screen reader
A ListViewItem takes its name from the content ToString, which for a
ChartViewData is the type name, so every chart in the channel statistics
announced Telegram.ViewModels.Chats.ChartViewData. Give the container the
chart title, beside the header it already sets.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 75177e5 • 4 files, +218/-3
Reach the statistics charts from the keyboard
The chart is drawn, so its values exist as text only in the legend, which
followed the pointer and nothing else. Arrows step a point, page keys a tenth
of the visible range, Home and End the edges, Escape drops the selection, and
every move is announced through a notification; focus arriving from the
keyboard selects the most recent point, and leaving clears it.
Taking focus means being a Control, so the view moves off the Grid it was
ported onto, the canvas and the legend now sitting in its content, and gets a
peer that reports it focusable and hands out the selected point as its value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 47adb00 • 1 files, +24/-1
Select chart points on hover
A mouse reports moves with nothing pressed, which the touch original had no
notion of: the legend only followed a held button, and the branch that took
over tested the press position rather than the pointer, so after any click
inside the chart a hover kept dragging the selection along.
Hovering the chart area now selects and moving off it clears, a move with
nothing in contact ends a drag whose release was lost to a scroll or a flyout,
and clearing on pointer exit redraws, which it never did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 00bb9e8 • 10 files, +357/-214
Don't search chats once per keystroke
The local half of the search ran on the keystroke itself, so typing a word sent
three TDLib requests and re-rendered the list per character; it now has its own
short debounce beside the online one. Its requests also go out together instead
of one after another, the message group is replaced in place rather than emptied
and refilled, and a row standing for the same chat is reused instead of rebuilt.
The four copies of ReplaceDiff became one extension, which is where the missing
UpdateItem call lands: a surviving row used to keep the query it was first found
by. Highlighters were also accumulating on recycled rows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • f2d33ae • 1 files, +8/-3
Add a page of public posts once, not once per message
LoadMoreItemsAsync called AddRange inside a foreach over the same page, so
loading fifty posts appended two and a half thousand rows. Its staleness check
also read the _cancellation field, which a newer query replaces with a source
that is not cancelled.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Name the chart cards for the screen reader
A ListViewItem takes its name from the content ToString, which for a
ChartViewData is the type name, so every chart in the channel statistics
announced Telegram.ViewModels.Chats.ChartViewData. Give the container the
chart title, beside the header it already sets.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 75177e5 • 4 files, +218/-3
Reach the statistics charts from the keyboard
The chart is drawn, so its values exist as text only in the legend, which
followed the pointer and nothing else. Arrows step a point, page keys a tenth
of the visible range, Home and End the edges, Escape drops the selection, and
every move is announced through a notification; focus arriving from the
keyboard selects the most recent point, and leaving clears it.
Taking focus means being a Control, so the view moves off the Grid it was
ported onto, the canvas and the legend now sitting in its content, and gets a
peer that reports it focusable and hands out the selected point as its value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 47adb00 • 1 files, +24/-1
Select chart points on hover
A mouse reports moves with nothing pressed, which the touch original had no
notion of: the legend only followed a held button, and the branch that took
over tested the press position rather than the pointer, so after any click
inside the chart a hover kept dragging the selection along.
Hovering the chart area now selects and moving off it clears, a move with
nothing in contact ends a drag whose release was lost to a scroll or a flyout,
and clearing on pointer exit redraws, which it never did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 00bb9e8 • 10 files, +357/-214
Don't search chats once per keystroke
The local half of the search ran on the keystroke itself, so typing a word sent
three TDLib requests and re-rendered the list per character; it now has its own
short debounce beside the online one. Its requests also go out together instead
of one after another, the message group is replaced in place rather than emptied
and refilled, and a row standing for the same chat is reused instead of rebuilt.
The four copies of ReplaceDiff became one extension, which is where the missing
UpdateItem call lands: a surviving row used to keep the query it was first found
by. Highlighters were also accumulating on recycled rows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • f2d33ae • 1 files, +8/-3
Add a page of public posts once, not once per message
LoadMoreItemsAsync called AddRange inside a foreach over the same page, so
loading fifty posts appended two and a half thousand rows. Its staleness check
also read the _cancellation field, which a newer query replaces with a source
that is not cancelled.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • b5bf963 • 1 files, +3/-0
Assign the flag SearchResult is constructed with
Every constructor took canSendMessageToUser and dropped it, so the field was
always false and the restricts-new-chats indicator could never appear.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 004ff43 • 14 files, +3/-20
Code cleanup
UnigramDev/Unigram/develop • 3060ffb • 1 files, +2/-2
Fix live location style registration
UnigramDev/Unigram/develop • 2a4fbd3 • 1 files, +7/-3
Access violation guard
UnigramDev/Unigram/develop • bbab91b • 7 files, +408/-9
Pull a back chip in from the edge of the detail
A left-to-right swipe over the detail pane brings in a circular back chip and,
past 72px, navigates back. Only the chip moves: revealing the master would mean
measuring the whole chat list at the moment the finger starts moving, since it is
collapsed while a chat is open, and a Frame navigation cannot be scrubbed anyway.
MessageSelector already owns a horizontal tracker over every bubble, and it wins
the manipulation: its source claims the contact first and chaining is off. So the
MasterDetailView does not compete for it. It owns the chip and binds it, by
expression, to whichever tracker is driving - MessageSelector's over a bubble, its
own everywhere else. Expressions may reference a tracker from anywhere in the
compositor, so nothing runs per frame on the UI thread.
The bubble already stays still under such a swipe: its offset expression reads
CanShare, which is false whenever back has the direction. Only MinPosition was
holding it at zero.
Back takes the direction Share is not using, so a user who wants it everywhere
turns Share off, and no setting is ever on while doing nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • ff6dd5a • 4 files, +85/-17
Make the back chip harder to miss
Fela asked for something catchier. It is 40px rather than 30 and travels the
full width of its own diameter, it swings upright from -30 as it comes in, and
past the threshold it latches into an armed state - scale pops to 1.15 and
opacity goes to full - so releasing feels committed rather than guessed at.
The tracker is clamped at the threshold, so progress rests at exactly 1 for as
long as the gesture is past it. Testing that rather than a distance means the
armed state latches on and stays on, instead of flickering at the boundary.
Still one expression per property, so none of this costs a frame.
The fill colour is now refreshed per gesture. The chip is built once and lives
for the window, so it kept whatever accent it was born under across a theme
change.
Carries the generated Resources.cs and .resw for the SwipeGoBack string added
in the previous commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Assign the flag SearchResult is constructed with
Every constructor took canSendMessageToUser and dropped it, so the field was
always false and the restricts-new-chats indicator could never appear.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 004ff43 • 14 files, +3/-20
Code cleanup
UnigramDev/Unigram/develop • 3060ffb • 1 files, +2/-2
Fix live location style registration
UnigramDev/Unigram/develop • 2a4fbd3 • 1 files, +7/-3
Access violation guard
UnigramDev/Unigram/develop • bbab91b • 7 files, +408/-9
Pull a back chip in from the edge of the detail
A left-to-right swipe over the detail pane brings in a circular back chip and,
past 72px, navigates back. Only the chip moves: revealing the master would mean
measuring the whole chat list at the moment the finger starts moving, since it is
collapsed while a chat is open, and a Frame navigation cannot be scrubbed anyway.
MessageSelector already owns a horizontal tracker over every bubble, and it wins
the manipulation: its source claims the contact first and chaining is off. So the
MasterDetailView does not compete for it. It owns the chip and binds it, by
expression, to whichever tracker is driving - MessageSelector's over a bubble, its
own everywhere else. Expressions may reference a tracker from anywhere in the
compositor, so nothing runs per frame on the UI thread.
The bubble already stays still under such a swipe: its offset expression reads
CanShare, which is false whenever back has the direction. Only MinPosition was
holding it at zero.
Back takes the direction Share is not using, so a user who wants it everywhere
turns Share off, and no setting is ever on while doing nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • ff6dd5a • 4 files, +85/-17
Make the back chip harder to miss
Fela asked for something catchier. It is 40px rather than 30 and travels the
full width of its own diameter, it swings upright from -30 as it comes in, and
past the threshold it latches into an armed state - scale pops to 1.15 and
opacity goes to full - so releasing feels committed rather than guessed at.
The tracker is clamped at the threshold, so progress rests at exactly 1 for as
long as the gesture is past it. Testing that rather than a distance means the
armed state latches on and stays on, instead of flickering at the boundary.
Still one expression per property, so none of this costs a frame.
The fill colour is now refreshed per gesture. The chip is built once and lives
for the window, so it kept whatever accent it was born under across a theme
change.
Carries the generated Resources.cs and .resw for the SwipeGoBack string added
in the previous commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
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