Skip to content
This repository has been archived by the owner on Apr 15, 2021. It is now read-only.

Commit

Permalink
Preserve position in explorer when autoloading messages
Browse files Browse the repository at this point in the history
  • Loading branch information
x2bool committed Jan 8, 2019
1 parent 12f3768 commit 6b76364
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 51 deletions.
131 changes: 84 additions & 47 deletions src/Tel.Egram.Gui.Views/Messenger/Explorer/ExplorerControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using ReactiveUI;
using Tel.Egram.Model.Messenger.Explorer;
using Tel.Egram.Services.Utils;
Expand All @@ -20,80 +21,116 @@ public class ExplorerControl : BaseControl<ExplorerModel>
o => o.VisibleRange,
(o, v) => o.VisibleRange = v);

public static readonly DirectProperty<ExplorerControl, object> TargetItemProperty =
AvaloniaProperty.RegisterDirect<ExplorerControl, object>(
nameof(TargetItem),
o => o.TargetItem,
(o, v) => o.TargetItem = v);

private readonly ListBox _listBox;

public ExplorerControl()
{
AvaloniaXamlLoader.Load(this);

var listBox = this.FindControl<ListBox>("ItemList");
_listBox = this.FindControl<ListBox>("ItemList");

this.WhenActivated(disposables =>
{
var offsetChanges = listBox.WhenAnyValue(lb => lb.Scroll.Offset)
var offsetChanges = _listBox.WhenAnyValue(lb => lb.Scroll.Offset)
.Select(_ => Unit.Default);
var extentChanges = listBox.WhenAnyValue(lb => lb.Scroll.Extent)
var extentChanges = _listBox.WhenAnyValue(lb => lb.Scroll.Extent)
.Select(_ => Unit.Default);
var viewportChanges = listBox.WhenAnyValue(lb => lb.Scroll.Viewport)
var viewportChanges = _listBox.WhenAnyValue(lb => lb.Scroll.Viewport)
.Select(_ => Unit.Default);
offsetChanges
.Merge(extentChanges)
.Merge(viewportChanges)
.Accept(_ =>
{
var offset = listBox.Scroll.Offset;
var viewport = listBox.Scroll.Viewport;
.Accept(_ => HandleOffsetChange())
.DisposeWith(disposables);
var topTreshold = offset.Y;
var bottomTreshold = offset.Y + viewport.Height;
int i = 0;
var items = listBox?.Items;
this.WhenAnyValue(x => x.TargetItem)
.Accept(item => HandleTargetItem(item))
.DisposeWith(disposables);
});
}

int from = Int32.MaxValue;
int to = Int32.MinValue;
if (items != null)
{
foreach (var item in items)
{
var listBoxItem = (ListBoxItem)listBox.ItemContainerGenerator.ContainerFromIndex(i);
var top = listBoxItem.Bounds.TopLeft.Y;
var bottom = listBoxItem.Bounds.BottomLeft.Y;
if (bottom >= topTreshold && top <= bottomTreshold)
{
if (i < from)
{
from = i;
}
private void HandleTargetItem(object item)
{
if (item != null)
{
RxApp.MainThreadScheduler.Schedule(
"",
TimeSpan.FromSeconds(0.1),
(scheduler, s) =>
{
_listBox.ScrollIntoView(item);
return Disposable.Empty;
});
}
}

if (i > to)
{
to = i;
}
}
i++;
}
}
private void HandleOffsetChange()
{
var offset = _listBox.Scroll.Offset;
var viewport = _listBox.Scroll.Viewport;

if (from != Int32.MaxValue && to != Int32.MinValue)
var topTreshold = offset.Y;
var bottomTreshold = offset.Y + viewport.Height;

int i = 0;
var items = _listBox?.Items;

int from = Int32.MaxValue;
int to = Int32.MinValue;

if (items != null)
{
foreach (var item in items)
{
var listBoxItem = (ListBoxItem)_listBox.ItemContainerGenerator.ContainerFromIndex(i);

var top = listBoxItem.Bounds.TopLeft.Y;
var bottom = listBoxItem.Bounds.BottomLeft.Y;
if (bottom >= topTreshold && top <= bottomTreshold)
{
if (i < from)
{
VisibleRange = new Range(from, to - from + 1);
from = i;
}
else

if (i > to)
{
VisibleRange = default(Range);
to = i;
}
})
.DisposeWith(disposables);
});
}
i++;
}
}

if (from != Int32.MaxValue && to != Int32.MinValue)
{
VisibleRange = new Range(from, to - from + 1);
}
else
{
VisibleRange = default(Range);
}
}

private Range _visibleRange;
public Range VisibleRange
{
get { return _visibleRange; }
set { SetAndRaise(VisibleRangeProperty, ref _visibleRange, value); }
get => _visibleRange;
set => SetAndRaise(VisibleRangeProperty, ref _visibleRange, value);
}

private object _targetItem;
public object TargetItem
{
get => _targetItem;
set => SetAndRaise(TargetItemProperty, ref _targetItem, value);
}
}
}
1 change: 1 addition & 0 deletions src/Tel.Egram.Gui.Views/Messenger/MessengerControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
DataContext="{Binding ExplorerModel}"
IsVisible="{Binding IsVisible}"
VisibleRange="{Binding VisibleRange, Mode=TwoWay}"
TargetItem="{Binding TargetItem, Mode=TwoWay}"
Grid.Column="1"
Grid.Row="1" />

Expand Down
4 changes: 2 additions & 2 deletions src/Tel.Egram.Model/Messenger/Explorer/ExplorerModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public class ExplorerModel : ISupportsActivation

public Range VisibleRange { get; set; }

public ItemModel TargetItem { get; set; }

public ObservableCollectionExtended<ItemModel> Items { get; set; }
= new ObservableCollectionExtended<ItemModel>();

Expand Down Expand Up @@ -53,8 +55,6 @@ public ExplorerModel(Chat chat)
private IDisposable BindSource()
{
return SourceItems.Connect()
.SubscribeOn(RxApp.TaskpoolScheduler)
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(Items)
.Accept();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using ReactiveUI;
using Splat;
using Tel.Egram.Model.Messenger.Explorer.Factories;
using Tel.Egram.Model.Messenger.Explorer.Items;
using Tel.Egram.Model.Messenger.Explorer.Messages;
using Tel.Egram.Services.Messaging.Chats;
using Tel.Egram.Services.Messaging.Messages;
Expand Down Expand Up @@ -52,7 +53,7 @@ public IDisposable Bind(
.Synchronize(_conductor.Locker)
.SelectSeq(r => StartLoading(chat))
.ObserveOn(RxApp.MainThreadScheduler)
.Accept(list => HandleLoading(model, list));
.Accept(list => HandleLoading(model, chat, list));
}

private IObservable<IList<MessageModel>> StartLoading(
Expand All @@ -72,10 +73,22 @@ private IObservable<IList<MessageModel>> StartLoading(

private void HandleLoading(
ExplorerModel model,
Chat chat,
IList<MessageModel> messageModels)
{
//Console.WriteLine("End init");

// find last read message to scroll to it later
var targetItem = messageModels
.FirstOrDefault(m => m.Message.MessageData.Id == chat.ChatData.LastReadInboxMessageId);

if (targetItem == null && messageModels.Count > 0)
{
targetItem = messageModels[messageModels.Count / 2];
}

model.SourceItems.AddRange(messageModels);
model.TargetItem = targetItem;
}

private IObservable<IList<MessageModel>> LoadInitMessages(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public IDisposable Bind(
Chat chat)
{
return model.WhenAnyValue(m => m.VisibleRange)
.Throttle(TimeSpan.FromSeconds(1))
.Select(r => r.LastIndex)
.DistinctUntilChanged()
.Where(index => model.SourceItems.Count != 0) // skip initial
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using ReactiveUI;
using Splat;
using Tel.Egram.Model.Messenger.Explorer.Factories;
using Tel.Egram.Model.Messenger.Explorer.Items;
using Tel.Egram.Model.Messenger.Explorer.Messages;
using Tel.Egram.Services.Messaging.Chats;
using Tel.Egram.Services.Messaging.Messages;
Expand Down Expand Up @@ -47,6 +48,7 @@ public IDisposable Bind(
Chat chat)
{
return model.WhenAnyValue(m => m.VisibleRange)
.Throttle(TimeSpan.FromSeconds(1))
.Select(r => r.Index)
.DistinctUntilChanged()
.Where(index => model.SourceItems.Count != 0) // skip initial
Expand All @@ -55,7 +57,7 @@ public IDisposable Bind(
.Synchronize(_conductor.Locker)
.SelectSeq(r => StartLoading(model, chat))
.ObserveOn(RxApp.MainThreadScheduler)
.Accept(list => HandleLoading(model, list));
.Accept(list => HandleLoading(model, chat, list));
}

private IObservable<IList<MessageModel>> StartLoading(
Expand All @@ -81,9 +83,29 @@ private IObservable<IList<MessageModel>> StartLoading(

private void HandleLoading(
ExplorerModel model,
Chat chat,
IList<MessageModel> messageModels)
{
//Console.WriteLine("End prev");

ItemModel targetItem = null;

// find item which is currently visible to scroll to it later
if (model.SourceItems.Count > 0)
{
targetItem = model.SourceItems.Items
.Skip(model.VisibleRange.Index)
.OfType<MessageModel>()
.FirstOrDefault();
}

// or take last added item
if (targetItem == null && messageModels.Count > 0)
{
targetItem = messageModels[messageModels.Count - 1];
}

model.TargetItem = targetItem;
model.SourceItems.InsertRange(messageModels, 0);
}

Expand Down

0 comments on commit 6b76364

Please sign in to comment.