~gioverse/chat

ff42a2f8b59287707042461aa1ca1347fb8250eb — Chris Waldon 4 months ago 7e2050c
list: update Loader to return if more elements

This commit changes the signature of the Loader hook so that it
also returns whether there are more elements in a given direction.
This greatly simplifies some of the state management logic, and
actually eliminates several spurious invocations of the Loader
hook.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
5 files changed, 31 insertions(+), 21 deletions(-)

M list/async.go
M list/async_test.go
M list/element.go
M list/manager.go
M list/manager_test.go
M list/async.go => list/async.go +3 -2
@@ 125,10 125,11 @@ func asyncProcess(maxSize int, hooks Hooks) (chan<- interface{}, chan viewport, 
						loadSerial = synthesis.SerialAt(len(synthesis.Source) - 1)
					}
					// Load new elements.
					newElems = append(newElems, hooks.Loader(req.Direction, loadSerial)...)
					var more bool
					newElems, more = hooks.Loader(req.Direction, loadSerial)
					// Track whether all new elements in a given direction have been
					// exhausted.
					if len(newElems) == 0 {
					if len(newElems) == 0 || !more {
						ignore.Add(req.Direction)
					} else {
						ignore = NoDirection

M list/async_test.go => list/async_test.go +15 -6
@@ 20,14 20,15 @@ var testElements = func() []Element {

func TestAsyncProcess(t *testing.T) {
	var nextLoad []Element
	var more bool
	var loadInvoked bool
	hooks := Hooks{
		Invalidator: func() {},
		Comparator:  testComparator,
		Synthesizer: testSynthesizer,
		Loader: func(dir Direction, rt Serial) []Element {
		Loader: func(dir Direction, rt Serial) ([]Element, bool) {
			loadInvoked = true
			return nextLoad
			return nextLoad, more
		},
	}
	size := 6


@@ 40,6 41,9 @@ func TestAsyncProcess(t *testing.T) {
		input loadRequest
		// the data that will be returned by the data request (if the loader is executed)
		load []Element
		// whether the async logic should expect additional content in the direction of
		// the load.
		loadMore bool
		// should the testcase block waiting for an update on the update channel
		skipUpdate bool
		// the update to expect on the update channel


@@ 60,7 64,8 @@ func TestAsyncProcess(t *testing.T) {
				},
				Direction: Before,
			},
			load: testElements[7:],
			load:     testElements[7:],
			loadMore: true,
			expected: stateUpdate{
				Synthesis: Synthesis{
					Elements: testElements[7:],


@@ 119,7 124,8 @@ func TestAsyncProcess(t *testing.T) {
				},
				Direction: Before,
			},
			load: testElements[4:7],
			load:     testElements[4:7],
			loadMore: true,
			expected: stateUpdate{
				Synthesis: Synthesis{
					Elements: testElements[4:],


@@ 172,7 178,8 @@ func TestAsyncProcess(t *testing.T) {
				},
				Direction: Before,
			},
			load: testElements[1:4],
			load:     testElements[1:4],
			loadMore: true,
			expected: stateUpdate{
				Synthesis: Synthesis{
					Elements: testElements[3:9],


@@ 201,7 208,8 @@ func TestAsyncProcess(t *testing.T) {
				},
				Direction: Before,
			},
			load: testElements[:3],
			load:     testElements[:3],
			loadMore: true,
			expected: stateUpdate{
				Synthesis: Synthesis{
					Elements: testElements[2:8],


@@ 267,6 275,7 @@ func TestAsyncProcess(t *testing.T) {
			// ensure that the next invocation of the loader will load this
			// testcase's data payload.
			nextLoad = tc.load
			more = tc.loadMore

			// request a load
			reqs <- tc.input

M list/element.go => list/element.go +6 -5
@@ 58,14 58,15 @@ type Synthesizer func(previous, current, next Element) []Element
type Comparator func(a, b Element) bool

// Loader is a function that can fulfill load requests. If it returns
// a response with no elements in a given direction, the manager will not
// a response with no elements in a given direction or false as its
// second return value, the manager will not
// invoke the loader in that direction again until the manager loads
// data from the other end of the list or another manger state update
// occurs.
//
// Loader implements pull modifications. When the manager wants more data it
// will invoke the Loader hook to get more.
type Loader func(direction Direction, relativeTo Serial) []Element
type Loader func(direction Direction, relativeTo Serial) (elems []Element, more bool)

// Presenter is a function that can transform the data for an Element
// into a widget to be laid out in the user interface. It must not return


@@ 121,11 122,11 @@ func DefaultHooks(w *app.Window, th *material.Theme) Hooks {
		Comparator: func(a, b Element) bool {
			return string(a.Serial()) < string(b.Serial())
		},
		Loader: func(dir Direction, relativeTo Serial) []Element {
		Loader: func(dir Direction, relativeTo Serial) ([]Element, bool) {
			if relativeTo == NoSerial {
				return newDefaultElements()
				return newDefaultElements(), false
			}
			return nil
			return nil, false
		},
		Presenter: func(elem Element, state interface{}) layout.Widget {
			return material.H4(th, "Implement list.Hooks to change me.").Layout

M list/manager.go => list/manager.go +2 -2
@@ 333,8 333,8 @@ func (m *Manager) UpdatedLen(list *layout.List) int {
			// the list's position.
			firstElementVisible := list.Position.First == 0
			lastElementVisible := list.Position.First+list.Position.Count == len(m.elements.Elements)
			stickToEnd := lastElementVisible && m.Stickiness.Contains(After) && m.ignoring.Contains(After)
			stickToBeginning := firstElementVisible && m.Stickiness.Contains(Before) && m.ignoring.Contains(Before)
			stickToEnd := lastElementVisible && m.Stickiness.Contains(After) && (m.ignoring.Contains(After) || su.Type == push)
			stickToBeginning := firstElementVisible && m.Stickiness.Contains(Before) && (m.ignoring.Contains(Before) || su.Type == push)

			if !stickToBeginning {
				// Update the list position to match the new set of elements.

M list/manager_test.go => list/manager_test.go +5 -6
@@ 67,8 67,8 @@ func TestManager(t *testing.T) {
				Height: unit.Dp(5),
			}.Layout
		},
		Loader: func(dir Direction, relativeTo Serial) []Element {
			return nil
		Loader: func(dir Direction, relativeTo Serial) ([]Element, bool) {
			return nil, false
		},
		Invalidator: func() {},
		Comparator:  func(a, b Element) bool { return true },


@@ 381,7 381,6 @@ func TestManagerPrefetch(t *testing.T) {
				}
			default:
			}

		})
	}
}


@@ 420,7 419,7 @@ func TestManagerViewportOnRemoval(t *testing.T) {
				Height: unit.Dp(5),
			}.Layout
		},
		Loader:      func(dir Direction, relativeTo Serial) []Element { return nil },
		Loader:      func(dir Direction, relativeTo Serial) ([]Element, bool) { return nil, false },
		Invalidator: func() {},
		Comparator:  func(a, b Element) bool { return true },
		Synthesizer: synth,


@@ 559,8 558,8 @@ var testHooks = Hooks{
			Height: unit.Dp(5),
		}.Layout
	},
	Loader: func(dir Direction, relativeTo Serial) []Element {
		return nil
	Loader: func(dir Direction, relativeTo Serial) ([]Element, bool) {
		return nil, false
	},
	Invalidator: func() {},
	Comparator:  func(a, b Element) bool { return true },