@nativescript/core 9.0.0-alpha.23 → 9.0.0-alpha.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/application/application.android.d.ts +9 -2
- package/application/application.android.js +83 -6
- package/application/application.android.js.map +1 -1
- package/application/application.d.ts +8 -1
- package/application/application.ios.d.ts +13 -1
- package/application/application.ios.js +30 -3
- package/application/application.ios.js.map +1 -1
- package/application/helpers.android.d.ts +0 -9
- package/application/helpers.android.js +0 -54
- package/application/helpers.android.js.map +1 -1
- package/application/helpers.d.ts +0 -10
- package/application/helpers.ios.d.ts +0 -19
- package/application/helpers.ios.js +0 -38
- package/application/helpers.ios.js.map +1 -1
- package/connectivity/index.android.js +3 -3
- package/connectivity/index.android.js.map +1 -1
- package/core-types/index.d.ts +68 -63
- package/core-types/index.js.map +1 -1
- package/image-source/index.d.ts +2 -2
- package/index.js +0 -1
- package/index.js.map +1 -1
- package/inspector_modules.js +72 -5
- package/inspector_modules.js.map +1 -1
- package/package.json +1 -1
- package/platforms/android/widgets-release.aar +0 -0
- package/references.d.ts +1 -1
- package/ui/button/index.ios.js.map +1 -1
- package/ui/core/view/index.android.d.ts +4 -6
- package/ui/core/view/index.android.js +126 -271
- package/ui/core/view/index.android.js.map +1 -1
- package/ui/core/view/index.d.ts +1 -1
- package/ui/core/view/index.ios.d.ts +7 -0
- package/ui/core/view/index.ios.js +74 -35
- package/ui/core/view/index.ios.js.map +1 -1
- package/ui/core/view/view-common.d.ts +37 -5
- package/ui/core/view/view-common.js +21 -0
- package/ui/core/view/view-common.js.map +1 -1
- package/ui/core/view/view-interfaces.d.ts +3 -0
- package/ui/frame/index.android.js +55 -2
- package/ui/frame/index.android.js.map +1 -1
- package/ui/index.d.ts +1 -1
- package/ui/layouts/index.d.ts +2 -0
- package/ui/layouts/index.js +2 -0
- package/ui/layouts/index.js.map +1 -1
- package/ui/layouts/liquid-glass/index.android.d.ts +5 -0
- package/ui/layouts/liquid-glass/index.android.js +8 -0
- package/ui/layouts/liquid-glass/index.android.js.map +1 -0
- package/ui/layouts/liquid-glass/index.d.ts +10 -0
- package/ui/layouts/liquid-glass/index.ios.d.ts +10 -0
- package/ui/layouts/liquid-glass/index.ios.js +59 -0
- package/ui/layouts/liquid-glass/index.ios.js.map +1 -0
- package/ui/layouts/liquid-glass/liquid-glass-common.d.ts +3 -0
- package/ui/layouts/liquid-glass/liquid-glass-common.js +4 -0
- package/ui/layouts/liquid-glass/liquid-glass-common.js.map +1 -0
- package/ui/layouts/liquid-glass-container/index.android.d.ts +3 -0
- package/ui/layouts/liquid-glass-container/index.android.js +4 -0
- package/ui/layouts/liquid-glass-container/index.android.js.map +1 -0
- package/ui/layouts/liquid-glass-container/index.d.ts +3 -0
- package/ui/layouts/liquid-glass-container/index.ios.d.ts +14 -0
- package/ui/layouts/liquid-glass-container/index.ios.js +121 -0
- package/ui/layouts/liquid-glass-container/index.ios.js.map +1 -0
- package/ui/layouts/liquid-glass-container/liquid-glass-container-common.d.ts +4 -0
- package/ui/layouts/liquid-glass-container/liquid-glass-container-common.js +5 -0
- package/ui/layouts/liquid-glass-container/liquid-glass-container-common.js.map +1 -0
- package/ui/list-view/index.android.d.ts +26 -1
- package/ui/list-view/index.android.js +701 -23
- package/ui/list-view/index.android.js.map +1 -1
- package/ui/list-view/index.d.ts +122 -0
- package/ui/list-view/index.ios.d.ts +34 -2
- package/ui/list-view/index.ios.js +560 -9
- package/ui/list-view/index.ios.js.map +1 -1
- package/ui/list-view/list-view-common.d.ts +22 -1
- package/ui/list-view/list-view-common.js +85 -0
- package/ui/list-view/list-view-common.js.map +1 -1
- package/ui/styling/background-common.d.ts +4 -4
- package/ui/styling/background-common.js +8 -8
- package/ui/styling/background-common.js.map +1 -1
- package/ui/styling/background.d.ts +0 -3
- package/ui/styling/background.ios.d.ts +2 -1
- package/ui/styling/background.ios.js +47 -38
- package/ui/styling/background.ios.js.map +1 -1
- package/ui/styling/css-utils.d.ts +1 -0
- package/ui/styling/css-utils.js +15 -4
- package/ui/styling/css-utils.js.map +1 -1
- package/ui/styling/style/index.d.ts +2 -1
- package/ui/styling/style/index.js.map +1 -1
- package/ui/styling/style-properties.js +23 -11
- package/ui/styling/style-properties.js.map +1 -1
- package/ui/tab-view/index.android.js +22 -8
- package/ui/tab-view/index.android.js.map +1 -1
- package/ui/tab-view/index.ios.d.ts +1 -1
- package/ui/tab-view/index.ios.js +28 -9
- package/ui/tab-view/index.ios.js.map +1 -1
- package/ui/tab-view/tab-view-common.d.ts +2 -0
- package/ui/tab-view/tab-view-common.js +5 -0
- package/ui/tab-view/tab-view-common.js.map +1 -1
- package/utils/common.d.ts +3 -1
- package/utils/common.js +9 -3
- package/utils/common.js.map +1 -1
- package/utils/index.d.ts +6 -0
- package/utils/native-helper-for-android.d.ts +14 -3
- package/utils/native-helper-for-android.js +57 -54
- package/utils/native-helper-for-android.js.map +1 -1
- package/utils/native-helper.android.d.ts +3 -3
- package/utils/native-helper.android.js +2 -2
- package/utils/native-helper.android.js.map +1 -1
- package/utils/native-helper.d.ts +22 -5
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { ListViewBase, separatorColorProperty, itemTemplatesProperty } from './list-view-common';
|
|
1
|
+
import { ListViewBase, separatorColorProperty, itemTemplatesProperty, stickyHeaderProperty, stickyHeaderTemplateProperty, sectionedProperty, showSearchProperty } from './list-view-common';
|
|
2
|
+
import { View } from '../core/view';
|
|
3
|
+
import { PercentLength } from '../styling/length-shared';
|
|
2
4
|
import { unsetValue } from '../core/properties/property-shared';
|
|
3
5
|
import { Color } from '../../color';
|
|
4
6
|
import { Observable } from '../../data/observable';
|
|
@@ -6,10 +8,18 @@ import { StackLayout } from '../layouts/stack-layout';
|
|
|
6
8
|
import { ProxyViewContainer } from '../proxy-view-container';
|
|
7
9
|
import { LayoutBase } from '../layouts/layout-base';
|
|
8
10
|
import { profile } from '../../profiling';
|
|
11
|
+
import { Trace } from '../../trace';
|
|
12
|
+
import { Builder } from '../builder';
|
|
13
|
+
import { Label } from '../label';
|
|
9
14
|
export * from './list-view-common';
|
|
10
15
|
const ITEMLOADING = ListViewBase.itemLoadingEvent;
|
|
11
16
|
const LOADMOREITEMS = ListViewBase.loadMoreItemsEvent;
|
|
12
17
|
const ITEMTAP = ListViewBase.itemTapEvent;
|
|
18
|
+
const SEARCHCHANGE = ListViewBase.searchChangeEvent;
|
|
19
|
+
const STICKY_HEADER_Z_INDEX = 1000;
|
|
20
|
+
const SEARCH_VIEW_Z_INDEX = 2000;
|
|
21
|
+
// View type constants for sectioned lists
|
|
22
|
+
const ITEM_VIEW_TYPE = 0;
|
|
13
23
|
let ItemClickListener;
|
|
14
24
|
function initializeItemClickListener() {
|
|
15
25
|
if (ItemClickListener) {
|
|
@@ -48,6 +58,11 @@ export class ListView extends ListViewBase {
|
|
|
48
58
|
this._realizedItems = new Map();
|
|
49
59
|
this._availableViews = new Map();
|
|
50
60
|
this._realizedTemplates = new Map();
|
|
61
|
+
this._stickyHeaderHeight = 0;
|
|
62
|
+
this._hiddenHeaderPositions = new Set(); // Track which headers to hide
|
|
63
|
+
}
|
|
64
|
+
get hasSearchView() {
|
|
65
|
+
return !!this._searchView;
|
|
51
66
|
}
|
|
52
67
|
_ensureAvailableViews(templateKey) {
|
|
53
68
|
if (!this._availableViews.has(templateKey)) {
|
|
@@ -122,6 +137,7 @@ export class ListView extends ListViewBase {
|
|
|
122
137
|
this._androidViewId = android.view.View.generateViewId();
|
|
123
138
|
}
|
|
124
139
|
nativeView.setId(this._androidViewId);
|
|
140
|
+
// Don't setup search here - wait for onLoaded when context is properly available
|
|
125
141
|
}
|
|
126
142
|
disposeNativeView() {
|
|
127
143
|
const nativeView = this.nativeViewProtected;
|
|
@@ -132,13 +148,40 @@ export class ListView extends ListViewBase {
|
|
|
132
148
|
if (nativeView.adapter) {
|
|
133
149
|
nativeView.adapter.owner = null;
|
|
134
150
|
}
|
|
151
|
+
// Cleanup search
|
|
152
|
+
this._cleanupSearchView();
|
|
153
|
+
// Cleanup sticky header
|
|
154
|
+
this._cleanupStickyHeader();
|
|
135
155
|
this.clearRealizedCells();
|
|
136
156
|
super.disposeNativeView();
|
|
137
157
|
}
|
|
158
|
+
_cleanupStickyHeader() {
|
|
159
|
+
// Remove scroll listener
|
|
160
|
+
if (this._scrollListener) {
|
|
161
|
+
this.nativeViewProtected.setOnScrollListener(null);
|
|
162
|
+
this._scrollListener = null;
|
|
163
|
+
}
|
|
164
|
+
// Remove sticky header from parent
|
|
165
|
+
if (this._stickyHeaderView && this._stickyHeaderView.parent) {
|
|
166
|
+
this._stickyHeaderView.parent._removeView(this._stickyHeaderView);
|
|
167
|
+
}
|
|
168
|
+
this._stickyHeaderView = null;
|
|
169
|
+
this._stickyHeaderHeight = 0;
|
|
170
|
+
// Clear hidden headers
|
|
171
|
+
this._hiddenHeaderPositions.clear();
|
|
172
|
+
}
|
|
138
173
|
onLoaded() {
|
|
139
174
|
super.onLoaded();
|
|
140
175
|
// Without this call itemClick won't be fired... :(
|
|
141
176
|
this.requestLayout();
|
|
177
|
+
// Setup sticky header if enabled
|
|
178
|
+
if (this.stickyHeader && this.sectioned && this.stickyHeaderTemplate) {
|
|
179
|
+
this._setupStickyHeader();
|
|
180
|
+
}
|
|
181
|
+
// Setup search if enabled and not already set up
|
|
182
|
+
if (this.showSearch && !this._searchView && this.nativeViewProtected && this.nativeViewProtected.getAdapter()) {
|
|
183
|
+
this._setupSearchView();
|
|
184
|
+
}
|
|
142
185
|
}
|
|
143
186
|
refresh() {
|
|
144
187
|
const nativeView = this.nativeViewProtected;
|
|
@@ -151,7 +194,19 @@ export class ListView extends ListViewBase {
|
|
|
151
194
|
view.bindingContext = null;
|
|
152
195
|
}
|
|
153
196
|
});
|
|
154
|
-
|
|
197
|
+
// Safely refresh the adapter - no HeaderViewListAdapter issues since we don't use headers
|
|
198
|
+
const adapter = nativeView.getAdapter();
|
|
199
|
+
if (adapter instanceof android.widget.BaseAdapter) {
|
|
200
|
+
try {
|
|
201
|
+
adapter.notifyDataSetChanged();
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
if (Trace.isEnabled()) {
|
|
205
|
+
Trace.error('Error refreshing adapter, recreating: ' + error);
|
|
206
|
+
}
|
|
207
|
+
nativeView.setAdapter(new ListViewAdapterClass(this));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
155
210
|
}
|
|
156
211
|
scrollToIndex(index) {
|
|
157
212
|
const nativeView = this.nativeViewProtected;
|
|
@@ -212,6 +267,217 @@ export class ListView extends ListViewBase {
|
|
|
212
267
|
const end = nativeView.getLastVisiblePosition();
|
|
213
268
|
return index >= start && index <= end;
|
|
214
269
|
}
|
|
270
|
+
// Sticky header methods
|
|
271
|
+
_setupStickyHeader() {
|
|
272
|
+
if (!this.stickyHeader || !this.sectioned || !this.stickyHeaderTemplate) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
// Create the sticky header view
|
|
276
|
+
this._createStickyHeaderView();
|
|
277
|
+
// Add it as an overlay to the parent
|
|
278
|
+
this._addStickyHeaderToParent();
|
|
279
|
+
// Add padding to ListView so content doesn't hide behind sticky header
|
|
280
|
+
this._addListViewPadding();
|
|
281
|
+
// Setup scroll listener to update header content
|
|
282
|
+
this._setupScrollListener();
|
|
283
|
+
}
|
|
284
|
+
_createStickyHeaderView() {
|
|
285
|
+
if (this._stickyHeaderView) {
|
|
286
|
+
return; // Already created
|
|
287
|
+
}
|
|
288
|
+
// Create header view using the same template as section headers
|
|
289
|
+
if (typeof this.stickyHeaderTemplate === 'string') {
|
|
290
|
+
try {
|
|
291
|
+
this._stickyHeaderView = Builder.parse(this.stickyHeaderTemplate, this);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
// Fallback to simple label
|
|
295
|
+
this._stickyHeaderView = new Label();
|
|
296
|
+
this._stickyHeaderView.text = 'Header Error';
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (!this._stickyHeaderView) {
|
|
300
|
+
// Default header
|
|
301
|
+
this._stickyHeaderView = new Label();
|
|
302
|
+
this._stickyHeaderView.text = 'Section 0';
|
|
303
|
+
}
|
|
304
|
+
// Set initial binding context (section 0)
|
|
305
|
+
this._updateStickyHeader(0);
|
|
306
|
+
}
|
|
307
|
+
_addStickyHeaderToParent() {
|
|
308
|
+
if (!this._stickyHeaderView || !this.parent) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// Remove from current parent if it has one (likely the ListView from Builder.parse)
|
|
312
|
+
if (this._stickyHeaderView.parent) {
|
|
313
|
+
this._stickyHeaderView.parent._removeView(this._stickyHeaderView);
|
|
314
|
+
}
|
|
315
|
+
// Set proper sizing - don't stretch to fill parent
|
|
316
|
+
this._stickyHeaderView.width = { unit: '%', value: 100 };
|
|
317
|
+
this._stickyHeaderView.height = 'auto'; // Let it size to content
|
|
318
|
+
this._stickyHeaderView.verticalAlignment = 'top';
|
|
319
|
+
this._stickyHeaderView.horizontalAlignment = 'stretch';
|
|
320
|
+
// Add sticky header to the parent layout
|
|
321
|
+
// If search view exists, position sticky header after it (index 1), otherwise at top (index 0)
|
|
322
|
+
const parentLayout = this.parent;
|
|
323
|
+
const hasSearchView = this.showSearch && this._searchView && this._searchView._wrapper;
|
|
324
|
+
if (parentLayout instanceof StackLayout) {
|
|
325
|
+
const insertIndex = hasSearchView ? 1 : 0;
|
|
326
|
+
parentLayout.insertChild(this._stickyHeaderView, insertIndex);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
parentLayout._addView(this._stickyHeaderView);
|
|
330
|
+
}
|
|
331
|
+
// When search is enabled, position sticky header below search view with proper top margin
|
|
332
|
+
if (this.showSearch && this._searchView) {
|
|
333
|
+
// Add top margin to push sticky header below search view
|
|
334
|
+
this._stickyHeaderView.marginTop = 0; // Reset any previous margin
|
|
335
|
+
// Position sticky header with proper offset using native positioning
|
|
336
|
+
if (this._stickyHeaderView.nativeViewProtected) {
|
|
337
|
+
this._stickyHeaderView.nativeViewProtected.setZ(STICKY_HEADER_Z_INDEX);
|
|
338
|
+
// Use a timeout to ensure search view is measured first
|
|
339
|
+
setTimeout(() => {
|
|
340
|
+
if (this._searchView && this._searchView._wrapper) {
|
|
341
|
+
const searchWrapper = this._searchView._wrapper;
|
|
342
|
+
if (searchWrapper.nativeViewProtected) {
|
|
343
|
+
const searchHeight = searchWrapper.nativeViewProtected.getMeasuredHeight() || 50;
|
|
344
|
+
// Position sticky header below search view using translation
|
|
345
|
+
if (this._stickyHeaderView.nativeViewProtected) {
|
|
346
|
+
this._stickyHeaderView.nativeViewProtected.setTranslationY(searchHeight);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}, 100);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
// No search view - position at top
|
|
355
|
+
if (this._stickyHeaderView.nativeViewProtected) {
|
|
356
|
+
this._stickyHeaderView.nativeViewProtected.setZ(STICKY_HEADER_Z_INDEX);
|
|
357
|
+
this._stickyHeaderView.nativeViewProtected.setTranslationY(0);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
_addListViewPadding() {
|
|
362
|
+
if (!this._stickyHeaderView) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// Calculate total top padding: search view height + sticky header height
|
|
366
|
+
let searchViewHeight = 0;
|
|
367
|
+
if (this.showSearch && this._searchView && this._searchView._wrapper) {
|
|
368
|
+
const searchWrapper = this._searchView._wrapper;
|
|
369
|
+
if (searchWrapper.nativeViewProtected && searchWrapper.nativeViewProtected.getMeasuredHeight() > 0) {
|
|
370
|
+
searchViewHeight = searchWrapper.nativeViewProtected.getMeasuredHeight();
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
searchViewHeight = 50; // Default search view height
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Apply immediate padding with defaults to prevent content hiding
|
|
377
|
+
const defaultHeaderHeight = 50; // Reasonable default height in dp
|
|
378
|
+
const totalPadding = searchViewHeight + defaultHeaderHeight;
|
|
379
|
+
this.nativeViewProtected.setPadding(0, totalPadding, 0, 0);
|
|
380
|
+
this._stickyHeaderHeight = defaultHeaderHeight;
|
|
381
|
+
// Request layout to ensure proper measurement
|
|
382
|
+
this._stickyHeaderView.requestLayout();
|
|
383
|
+
// Then measure and adjust padding if needed using a layout listener for determinism
|
|
384
|
+
const stickyHeaderNativeView = this._stickyHeaderView && this._stickyHeaderView.nativeViewProtected;
|
|
385
|
+
if (stickyHeaderNativeView) {
|
|
386
|
+
const layoutListener = new android.view.View.OnLayoutChangeListener({
|
|
387
|
+
onLayoutChange: (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) => {
|
|
388
|
+
if (v.getMeasuredHeight() > 0) {
|
|
389
|
+
const measuredHeaderHeight = v.getMeasuredHeight();
|
|
390
|
+
let finalSearchHeight = searchViewHeight;
|
|
391
|
+
// Re-measure search view if needed
|
|
392
|
+
if (this.showSearch && this._searchView && this._searchView._wrapper) {
|
|
393
|
+
const searchWrapper = this._searchView._wrapper;
|
|
394
|
+
if (searchWrapper.nativeViewProtected && searchWrapper.nativeViewProtected.getMeasuredHeight() > 0) {
|
|
395
|
+
finalSearchHeight = searchWrapper.nativeViewProtected.getMeasuredHeight();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Calculate final padding: search height + sticky header height + small buffer
|
|
399
|
+
const totalPaddingHeight = finalSearchHeight + measuredHeaderHeight + 4;
|
|
400
|
+
this._stickyHeaderHeight = measuredHeaderHeight;
|
|
401
|
+
this.nativeViewProtected.setPadding(0, totalPaddingHeight, 0, 0);
|
|
402
|
+
this.scrollToIndex(0);
|
|
403
|
+
// Remove the listener after first valid layout
|
|
404
|
+
v.removeOnLayoutChangeListener(layoutListener);
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
stickyHeaderNativeView.addOnLayoutChangeListener(layoutListener);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
_setupScrollListener() {
|
|
412
|
+
if (this._scrollListener) {
|
|
413
|
+
return; // Already setup
|
|
414
|
+
}
|
|
415
|
+
const owner = this;
|
|
416
|
+
this._scrollListener = new android.widget.AbsListView.OnScrollListener({
|
|
417
|
+
onScrollStateChanged(view, scrollState) {
|
|
418
|
+
// Not needed for sticky headers
|
|
419
|
+
},
|
|
420
|
+
onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount) {
|
|
421
|
+
if (owner.sectioned && owner._stickyHeaderView) {
|
|
422
|
+
const currentSection = owner._getCurrentSection(firstVisibleItem);
|
|
423
|
+
owner._updateStickyHeader(currentSection);
|
|
424
|
+
// Hide section headers when they would appear right below sticky header
|
|
425
|
+
owner._updateHiddenHeaders(firstVisibleItem);
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
this.nativeViewProtected.setOnScrollListener(this._scrollListener);
|
|
430
|
+
}
|
|
431
|
+
_getCurrentSection(firstVisibleItem) {
|
|
432
|
+
if (!this.sectioned) {
|
|
433
|
+
return 0;
|
|
434
|
+
}
|
|
435
|
+
// Convert the first visible list position to section number
|
|
436
|
+
let currentPosition = 0;
|
|
437
|
+
const sectionCount = this._getSectionCount();
|
|
438
|
+
for (let section = 0; section < sectionCount; section++) {
|
|
439
|
+
// Check if firstVisibleItem is in this section (header or items)
|
|
440
|
+
const sectionItems = this._getItemsInSection(section) || [];
|
|
441
|
+
const itemsInSection = sectionItems.length || 0;
|
|
442
|
+
const sectionEndPosition = currentPosition + 1 + itemsInSection; // +1 for header
|
|
443
|
+
if (firstVisibleItem < sectionEndPosition) {
|
|
444
|
+
return section;
|
|
445
|
+
}
|
|
446
|
+
currentPosition = sectionEndPosition;
|
|
447
|
+
}
|
|
448
|
+
return Math.max(0, sectionCount - 1); // Fallback to last section
|
|
449
|
+
}
|
|
450
|
+
_updateStickyHeader(section) {
|
|
451
|
+
if (!this._stickyHeaderView) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
// Update binding context to match the current section
|
|
455
|
+
const sectionData = this._getSectionData(section);
|
|
456
|
+
if (sectionData) {
|
|
457
|
+
this._stickyHeaderView.bindingContext = sectionData;
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
this._stickyHeaderView.bindingContext = { title: `Section ${section}`, section: section };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
_updateHiddenHeaders(firstVisibleItem) {
|
|
464
|
+
const previousHiddenHeaders = new Set(this._hiddenHeaderPositions);
|
|
465
|
+
this._hiddenHeaderPositions.clear();
|
|
466
|
+
// If we're at the very top (first item is position 0, which is the first section header),
|
|
467
|
+
// hide that section header to avoid duplication with sticky header
|
|
468
|
+
if (firstVisibleItem === 0) {
|
|
469
|
+
this._hiddenHeaderPositions.add(0); // Hide the first section header position
|
|
470
|
+
}
|
|
471
|
+
// If hidden headers changed, refresh the adapter
|
|
472
|
+
const hiddenHeadersChanged = previousHiddenHeaders.size !== this._hiddenHeaderPositions.size || [...previousHiddenHeaders].some((pos) => !this._hiddenHeaderPositions.has(pos));
|
|
473
|
+
if (hiddenHeadersChanged) {
|
|
474
|
+
// Refresh adapter to update visibility
|
|
475
|
+
const adapter = this.nativeViewProtected.getAdapter();
|
|
476
|
+
if (adapter instanceof android.widget.BaseAdapter) {
|
|
477
|
+
adapter.notifyDataSetChanged();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
215
481
|
[separatorColorProperty.getDefault]() {
|
|
216
482
|
const nativeView = this.nativeViewProtected;
|
|
217
483
|
return {
|
|
@@ -238,8 +504,199 @@ export class ListView extends ListViewBase {
|
|
|
238
504
|
if (value) {
|
|
239
505
|
this._itemTemplatesInternal = this._itemTemplatesInternal.concat(value);
|
|
240
506
|
}
|
|
241
|
-
this.nativeViewProtected
|
|
242
|
-
|
|
507
|
+
if (this.nativeViewProtected) {
|
|
508
|
+
this.nativeViewProtected.setAdapter(new ListViewAdapterClass(this));
|
|
509
|
+
this.refresh();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Sticky header property handlers
|
|
513
|
+
[stickyHeaderProperty.setNative](value) {
|
|
514
|
+
// Refresh adapter to handle sectioned vs non-sectioned display
|
|
515
|
+
if (this.nativeViewProtected && this.nativeViewProtected.getAdapter()) {
|
|
516
|
+
this.nativeViewProtected.setAdapter(new ListViewAdapterClass(this));
|
|
517
|
+
}
|
|
518
|
+
// Setup or cleanup sticky header
|
|
519
|
+
if (value && this.sectioned && this.stickyHeaderTemplate && this.isLoaded) {
|
|
520
|
+
this._setupStickyHeader();
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
this._cleanupStickyHeader();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
[stickyHeaderTemplateProperty.setNative](value) {
|
|
527
|
+
// Refresh adapter when template changes
|
|
528
|
+
if (this.nativeViewProtected && this.nativeViewProtected.getAdapter()) {
|
|
529
|
+
this.nativeViewProtected.setAdapter(new ListViewAdapterClass(this));
|
|
530
|
+
}
|
|
531
|
+
// Recreate sticky header with new template
|
|
532
|
+
this._cleanupStickyHeader();
|
|
533
|
+
if (value && this.stickyHeader && this.sectioned && this.isLoaded) {
|
|
534
|
+
this._setupStickyHeader();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
[sectionedProperty.setNative](value) {
|
|
538
|
+
// Refresh adapter to handle sectioned vs non-sectioned data
|
|
539
|
+
if (this.nativeViewProtected && this.nativeViewProtected.getAdapter()) {
|
|
540
|
+
this.nativeViewProtected.setAdapter(new ListViewAdapterClass(this));
|
|
541
|
+
}
|
|
542
|
+
// Setup or cleanup sticky header based on sectioned state
|
|
543
|
+
if (value && this.stickyHeader && this.stickyHeaderTemplate && this.isLoaded) {
|
|
544
|
+
this._setupStickyHeader();
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
this._cleanupStickyHeader();
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Search methods
|
|
551
|
+
_setupSearchView() {
|
|
552
|
+
if (this._searchView || !this.showSearch || !this.nativeViewProtected) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
// Create SearchView using the ListView's context
|
|
556
|
+
this._searchView = new android.widget.SearchView(this.nativeViewProtected.getContext());
|
|
557
|
+
this._searchView.setQueryHint('Search...');
|
|
558
|
+
this._searchView.setIconifiedByDefault(false);
|
|
559
|
+
this._searchView.setSubmitButtonEnabled(false);
|
|
560
|
+
// Setup search listener
|
|
561
|
+
const owner = this;
|
|
562
|
+
this._searchListener = new android.widget.SearchView.OnQueryTextListener({
|
|
563
|
+
onQueryTextChange(newText) {
|
|
564
|
+
const args = {
|
|
565
|
+
eventName: SEARCHCHANGE,
|
|
566
|
+
object: owner,
|
|
567
|
+
text: newText,
|
|
568
|
+
android: owner._searchView,
|
|
569
|
+
};
|
|
570
|
+
owner.notify(args);
|
|
571
|
+
return true;
|
|
572
|
+
},
|
|
573
|
+
onQueryTextSubmit(query) {
|
|
574
|
+
const args = {
|
|
575
|
+
eventName: SEARCHCHANGE,
|
|
576
|
+
object: owner,
|
|
577
|
+
text: query,
|
|
578
|
+
android: owner._searchView,
|
|
579
|
+
};
|
|
580
|
+
owner.notify(args);
|
|
581
|
+
return true;
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
this._searchView.setOnQueryTextListener(this._searchListener);
|
|
585
|
+
// Add search view to the parent container above the ListView
|
|
586
|
+
this._addSearchToParent();
|
|
587
|
+
// Add padding to ListView if no sticky header (otherwise sticky header method handles it)
|
|
588
|
+
if (!this.stickyHeader || !this._stickyHeaderView) {
|
|
589
|
+
this._addSearchPadding();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
_addSearchPadding() {
|
|
593
|
+
if (!this._searchView) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
// Add basic padding for search view
|
|
597
|
+
const defaultSearchHeight = 50; // Default search view height
|
|
598
|
+
this.nativeViewProtected.setPadding(0, defaultSearchHeight, 0, 0);
|
|
599
|
+
// Measure and adjust if needed
|
|
600
|
+
setTimeout(() => {
|
|
601
|
+
if (this._searchView && this._searchView._wrapper) {
|
|
602
|
+
const searchWrapper = this._searchView._wrapper;
|
|
603
|
+
if (searchWrapper.nativeViewProtected && searchWrapper.nativeViewProtected.getMeasuredHeight() > 0) {
|
|
604
|
+
const measuredHeight = searchWrapper.nativeViewProtected.getMeasuredHeight();
|
|
605
|
+
this.nativeViewProtected.setPadding(0, measuredHeight + 4, 0, 0);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}, 100);
|
|
609
|
+
}
|
|
610
|
+
_addSearchToParent() {
|
|
611
|
+
if (!this._searchView || !this.parent) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
// Get the parent layout
|
|
615
|
+
const parentLayout = this.parent;
|
|
616
|
+
// Create a simple NativeScript wrapper for the native SearchView
|
|
617
|
+
const searchView = this._searchView;
|
|
618
|
+
const searchViewWrapper = new (class extends View {
|
|
619
|
+
createNativeView() {
|
|
620
|
+
return searchView;
|
|
621
|
+
}
|
|
622
|
+
})();
|
|
623
|
+
// Set layout properties - ensure it's at the top
|
|
624
|
+
searchViewWrapper.height = 'auto';
|
|
625
|
+
searchViewWrapper.width = { unit: '%', value: 100 };
|
|
626
|
+
searchViewWrapper.verticalAlignment = 'top';
|
|
627
|
+
searchViewWrapper.horizontalAlignment = 'stretch';
|
|
628
|
+
// Always insert at position 0 (top) regardless of ListView position
|
|
629
|
+
if (parentLayout instanceof StackLayout) {
|
|
630
|
+
parentLayout.insertChild(searchViewWrapper, 0);
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
// For other layout types, add as first child
|
|
634
|
+
parentLayout._addView(searchViewWrapper);
|
|
635
|
+
}
|
|
636
|
+
// Ensure search view appears above everything else
|
|
637
|
+
if (searchViewWrapper.nativeViewProtected) {
|
|
638
|
+
searchViewWrapper.nativeViewProtected.setZ(SEARCH_VIEW_Z_INDEX);
|
|
639
|
+
}
|
|
640
|
+
// Store reference for cleanup
|
|
641
|
+
this._searchView._wrapper = searchViewWrapper;
|
|
642
|
+
}
|
|
643
|
+
_cleanupSearchView() {
|
|
644
|
+
if (this._searchView) {
|
|
645
|
+
// Remove search view wrapper from parent
|
|
646
|
+
const wrapper = this._searchView._wrapper;
|
|
647
|
+
if (wrapper && wrapper.parent) {
|
|
648
|
+
wrapper.parent._removeView(wrapper);
|
|
649
|
+
}
|
|
650
|
+
// Clear listener
|
|
651
|
+
if (this._searchListener) {
|
|
652
|
+
this._searchView.setOnQueryTextListener(null);
|
|
653
|
+
this._searchListener = null;
|
|
654
|
+
}
|
|
655
|
+
this._searchView = null;
|
|
656
|
+
// Reset ListView padding if no sticky header
|
|
657
|
+
if (!this.stickyHeader || !this._stickyHeaderView) {
|
|
658
|
+
this.nativeViewProtected.setPadding(0, 0, 0, 0);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
[showSearchProperty.setNative](value) {
|
|
663
|
+
if (value) {
|
|
664
|
+
if (this.isLoaded && this.nativeViewProtected && this.nativeViewProtected.getAdapter()) {
|
|
665
|
+
this._setupSearchView();
|
|
666
|
+
// Reposition sticky header if it exists
|
|
667
|
+
if (this._stickyHeaderView) {
|
|
668
|
+
this._repositionStickyHeader();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
this._cleanupSearchView();
|
|
674
|
+
// Reposition sticky header if it exists
|
|
675
|
+
if (this._stickyHeaderView) {
|
|
676
|
+
this._repositionStickyHeader();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
_repositionStickyHeader() {
|
|
681
|
+
if (!this._stickyHeaderView || !this._stickyHeaderView.nativeViewProtected) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
// Reset positioning
|
|
685
|
+
this._stickyHeaderView.nativeViewProtected.setTranslationY(0);
|
|
686
|
+
// If search is enabled, position below search view
|
|
687
|
+
if (this.showSearch && this._searchView && this._searchView._wrapper) {
|
|
688
|
+
setTimeout(() => {
|
|
689
|
+
const searchWrapper = this._searchView._wrapper;
|
|
690
|
+
if (searchWrapper.nativeViewProtected) {
|
|
691
|
+
const searchHeight = searchWrapper.nativeViewProtected.getMeasuredHeight() || 50;
|
|
692
|
+
this._stickyHeaderView.nativeViewProtected.setTranslationY(searchHeight);
|
|
693
|
+
}
|
|
694
|
+
}, 100);
|
|
695
|
+
}
|
|
696
|
+
// Update ListView padding
|
|
697
|
+
if (this.stickyHeader && this._stickyHeaderView) {
|
|
698
|
+
this._addListViewPadding();
|
|
699
|
+
}
|
|
243
700
|
}
|
|
244
701
|
}
|
|
245
702
|
__decorate([
|
|
@@ -261,15 +718,89 @@ function ensureListViewAdapterClass() {
|
|
|
261
718
|
return global.__native(_this);
|
|
262
719
|
}
|
|
263
720
|
ListViewAdapter.prototype.getCount = function () {
|
|
264
|
-
|
|
721
|
+
if (!this.owner) {
|
|
722
|
+
return 0;
|
|
723
|
+
}
|
|
724
|
+
// Ensure we always have at least the items array length, even if empty
|
|
725
|
+
var count = 0;
|
|
726
|
+
if (this.owner.sectioned) {
|
|
727
|
+
// If items are not ready, report 0 to avoid early crashes
|
|
728
|
+
var sectionCount = this.owner._getSectionCount();
|
|
729
|
+
if (!this.owner.items || sectionCount <= 0) {
|
|
730
|
+
return 0;
|
|
731
|
+
}
|
|
732
|
+
// Count items + section headers
|
|
733
|
+
for (var i = 0; i < sectionCount; i++) {
|
|
734
|
+
var itemsInSection = this.owner._getItemsInSection(i) || [];
|
|
735
|
+
// Only add header if section has items
|
|
736
|
+
if (itemsInSection.length > 0) {
|
|
737
|
+
count += 1; // Section header
|
|
738
|
+
count += itemsInSection.length; // Items in section
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
var src = this.owner.items;
|
|
744
|
+
count = src && typeof src.length === 'number' ? src.length : 0;
|
|
745
|
+
}
|
|
746
|
+
// Return the count, ensuring it's never negative
|
|
747
|
+
return Math.max(0, count);
|
|
265
748
|
};
|
|
266
749
|
ListViewAdapter.prototype.getItem = function (i) {
|
|
267
|
-
if (this.owner
|
|
268
|
-
|
|
269
|
-
|
|
750
|
+
if (!this.owner || !this.owner.items) {
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
// Safety check for index bounds
|
|
754
|
+
var totalCount = this.getCount();
|
|
755
|
+
if (i < 0 || i >= totalCount) {
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
if (this.owner.sectioned) {
|
|
759
|
+
var positionInfo = this._getPositionInfo(i);
|
|
760
|
+
if (positionInfo.isHeader) {
|
|
761
|
+
return this.owner._getSectionData(positionInfo.section);
|
|
762
|
+
}
|
|
763
|
+
else {
|
|
764
|
+
return this.owner._getDataItemInSection(positionInfo.section, positionInfo.itemIndex);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
var src = this.owner.items;
|
|
769
|
+
if (src && typeof src.length === 'number' && i < src.length) {
|
|
770
|
+
var getItem = src.getItem;
|
|
771
|
+
return getItem ? getItem.call(src, i) : src[i];
|
|
772
|
+
}
|
|
270
773
|
}
|
|
271
774
|
return null;
|
|
272
775
|
};
|
|
776
|
+
// Helper method to determine if position is header and get section/item info
|
|
777
|
+
ListViewAdapter.prototype._getPositionInfo = function (position) {
|
|
778
|
+
if (!this.owner.sectioned) {
|
|
779
|
+
return { isHeader: false, section: 0, itemIndex: position };
|
|
780
|
+
}
|
|
781
|
+
var currentPosition = 0;
|
|
782
|
+
var sectionCount = this.owner._getSectionCount();
|
|
783
|
+
for (var section = 0; section < sectionCount; section++) {
|
|
784
|
+
var itemsInSection = this.owner._getItemsInSection(section) || [];
|
|
785
|
+
// Skip sections with no items (they won't have headers in our count)
|
|
786
|
+
if (itemsInSection.length === 0) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
// Check if this position is the section header
|
|
790
|
+
if (currentPosition === position) {
|
|
791
|
+
return { isHeader: true, section: section, itemIndex: -1 };
|
|
792
|
+
}
|
|
793
|
+
currentPosition++; // Move past header
|
|
794
|
+
// Check if position is within this section's items
|
|
795
|
+
if (position < currentPosition + itemsInSection.length) {
|
|
796
|
+
var itemIndex = position - currentPosition;
|
|
797
|
+
return { isHeader: false, section: section, itemIndex: itemIndex };
|
|
798
|
+
}
|
|
799
|
+
currentPosition += itemsInSection.length; // Move past items
|
|
800
|
+
}
|
|
801
|
+
// Fallback - should not reach here with proper bounds checking
|
|
802
|
+
return { isHeader: false, section: 0, itemIndex: 0 };
|
|
803
|
+
};
|
|
273
804
|
ListViewAdapter.prototype.getItemId = function (i) {
|
|
274
805
|
var item = this.getItem(i);
|
|
275
806
|
var id = i;
|
|
@@ -281,28 +812,152 @@ function ensureListViewAdapterClass() {
|
|
|
281
812
|
ListViewAdapter.prototype.hasStableIds = function () {
|
|
282
813
|
return true;
|
|
283
814
|
};
|
|
815
|
+
ListViewAdapter.prototype.isEnabled = function (position) {
|
|
816
|
+
// Safety check to prevent crashes when adapter is empty
|
|
817
|
+
var totalCount = this.getCount();
|
|
818
|
+
if (totalCount === 0 || position < 0 || position >= totalCount) {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
// For sectioned lists, check if this is a header position
|
|
822
|
+
if (this.owner.sectioned) {
|
|
823
|
+
var positionInfo = this._getPositionInfo(position);
|
|
824
|
+
// Headers are typically not clickable, items are
|
|
825
|
+
return !positionInfo.isHeader;
|
|
826
|
+
}
|
|
827
|
+
return true;
|
|
828
|
+
};
|
|
284
829
|
ListViewAdapter.prototype.getViewTypeCount = function () {
|
|
285
|
-
|
|
830
|
+
var count = this.owner._itemTemplatesInternal.length;
|
|
831
|
+
// Add 1 for header view type when sectioned
|
|
832
|
+
if (this.owner.sectioned && this.owner.stickyHeaderTemplate) {
|
|
833
|
+
count += 1;
|
|
834
|
+
}
|
|
835
|
+
return count;
|
|
286
836
|
};
|
|
287
837
|
ListViewAdapter.prototype.getItemViewType = function (index) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
838
|
+
if (this.owner.sectioned) {
|
|
839
|
+
var positionInfo = this._getPositionInfo(index);
|
|
840
|
+
if (positionInfo.isHeader) {
|
|
841
|
+
// Header view type is the last index (after all item template types)
|
|
842
|
+
return this.owner._itemTemplatesInternal.length;
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
// Get template for the actual item
|
|
846
|
+
var template = this.owner._getItemTemplate(positionInfo.itemIndex);
|
|
847
|
+
return this.owner._itemTemplatesInternal.indexOf(template);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
var template = this.owner._getItemTemplate(index);
|
|
852
|
+
return this.owner._itemTemplatesInternal.indexOf(template);
|
|
853
|
+
}
|
|
291
854
|
};
|
|
292
855
|
ListViewAdapter.prototype.getView = function (index, convertView, parent) {
|
|
293
856
|
//this.owner._dumpRealizedTemplates();
|
|
294
857
|
if (!this.owner) {
|
|
295
858
|
return null;
|
|
296
859
|
}
|
|
297
|
-
|
|
298
|
-
|
|
860
|
+
// Safety check for empty adapter
|
|
861
|
+
var totalCount = this.getCount();
|
|
862
|
+
if (index < 0 || index >= totalCount) {
|
|
863
|
+
// Return a minimal empty view to prevent crashes
|
|
864
|
+
var emptyView = new android.view.View(this.owner._context);
|
|
865
|
+
var layoutParams = new android.view.ViewGroup.LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT, 0);
|
|
866
|
+
emptyView.setLayoutParams(layoutParams);
|
|
867
|
+
return emptyView;
|
|
868
|
+
}
|
|
869
|
+
// Trigger loadMoreItems when binding the last visible row (matches prior Android behavior)
|
|
870
|
+
if (index === totalCount - 1) {
|
|
299
871
|
this.owner.notify({
|
|
300
872
|
eventName: LOADMOREITEMS,
|
|
301
873
|
object: this.owner,
|
|
302
874
|
});
|
|
303
875
|
}
|
|
304
|
-
|
|
305
|
-
|
|
876
|
+
if (this.owner.sectioned) {
|
|
877
|
+
var positionInfo = this._getPositionInfo(index);
|
|
878
|
+
if (positionInfo.isHeader) {
|
|
879
|
+
// Create section header view (pass index for hiding logic)
|
|
880
|
+
return this._createHeaderView(positionInfo.section, convertView, parent, index);
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
// Create regular item view with adjusted index
|
|
884
|
+
return this._createItemView(positionInfo.section, positionInfo.itemIndex, convertView, parent);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
// Non-sectioned - use original logic
|
|
889
|
+
return this._createItemView(0, index, convertView, parent);
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
ListViewAdapter.prototype._createHeaderView = function (section, convertView, parent, index) {
|
|
893
|
+
// Check if this header should be hidden to avoid duplication with sticky header
|
|
894
|
+
if (this.owner._hiddenHeaderPositions.has(index)) {
|
|
895
|
+
// Return an empty view with zero height
|
|
896
|
+
var emptyView = new android.view.View(this.owner._context);
|
|
897
|
+
var layoutParams = new android.view.ViewGroup.LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT, 0);
|
|
898
|
+
emptyView.setLayoutParams(layoutParams);
|
|
899
|
+
return emptyView;
|
|
900
|
+
}
|
|
901
|
+
var headerView = null;
|
|
902
|
+
var headerViewType = this.owner._itemTemplatesInternal.length; // Same as getItemViewType for headers
|
|
903
|
+
// Try to reuse convertView if it's the right type
|
|
904
|
+
if (convertView) {
|
|
905
|
+
var existingData = this.owner._realizedItems.get(convertView);
|
|
906
|
+
if (existingData && existingData.templateKey === "header_".concat(headerViewType)) {
|
|
907
|
+
headerView = existingData.view;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
// Create new header view if we can't reuse
|
|
911
|
+
if (!headerView) {
|
|
912
|
+
if (this.owner.stickyHeaderTemplate) {
|
|
913
|
+
if (typeof this.owner.stickyHeaderTemplate === 'string') {
|
|
914
|
+
try {
|
|
915
|
+
headerView = Builder.parse(this.owner.stickyHeaderTemplate, this.owner);
|
|
916
|
+
}
|
|
917
|
+
catch (error) {
|
|
918
|
+
// Fallback to simple label
|
|
919
|
+
headerView = new Label();
|
|
920
|
+
headerView.text = 'Header Error';
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if (!headerView) {
|
|
925
|
+
// Default header
|
|
926
|
+
headerView = new Label();
|
|
927
|
+
headerView.text = "Section ".concat(section);
|
|
928
|
+
}
|
|
929
|
+
// Add to parent and get native view
|
|
930
|
+
if (!headerView.parent) {
|
|
931
|
+
if (headerView instanceof LayoutBase && !(headerView instanceof ProxyViewContainer)) {
|
|
932
|
+
this.owner._addView(headerView);
|
|
933
|
+
convertView = headerView.nativeViewProtected;
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
var sp = new StackLayout();
|
|
937
|
+
sp.addChild(headerView);
|
|
938
|
+
this.owner._addView(sp);
|
|
939
|
+
convertView = sp.nativeViewProtected;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
// Register the header view for recycling
|
|
943
|
+
this.owner._realizedItems.set(convertView, {
|
|
944
|
+
view: headerView,
|
|
945
|
+
templateKey: "header_".concat(headerViewType),
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
// Set binding context to section data (always update, even for recycled views)
|
|
949
|
+
var sectionData = this.owner._getSectionData(section);
|
|
950
|
+
if (sectionData) {
|
|
951
|
+
headerView.bindingContext = sectionData;
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
headerView.bindingContext = { title: "Section ".concat(section), section: section };
|
|
955
|
+
}
|
|
956
|
+
return convertView;
|
|
957
|
+
};
|
|
958
|
+
ListViewAdapter.prototype._createItemView = function (section, itemIndex, convertView, parent) {
|
|
959
|
+
// Use existing item creation logic but with sectioned data
|
|
960
|
+
var template = this.owner._getItemTemplate(itemIndex);
|
|
306
961
|
var view;
|
|
307
962
|
// convertView is of the wrong type
|
|
308
963
|
if (convertView && this.owner._getKeyFromView(convertView) !== template.key) {
|
|
@@ -318,14 +973,14 @@ function ensureListViewAdapterClass() {
|
|
|
318
973
|
var args = {
|
|
319
974
|
eventName: ITEMLOADING,
|
|
320
975
|
object: this.owner,
|
|
321
|
-
index:
|
|
976
|
+
index: itemIndex,
|
|
322
977
|
view: view,
|
|
323
978
|
android: parent,
|
|
324
979
|
ios: undefined,
|
|
325
980
|
};
|
|
326
981
|
this.owner.notify(args);
|
|
327
982
|
if (!args.view) {
|
|
328
|
-
args.view = this.owner._getDefaultItemContent(
|
|
983
|
+
args.view = this.owner._getDefaultItemContent(itemIndex);
|
|
329
984
|
}
|
|
330
985
|
if (args.view) {
|
|
331
986
|
if (this.owner._effectiveRowHeight > -1) {
|
|
@@ -334,13 +989,36 @@ function ensureListViewAdapterClass() {
|
|
|
334
989
|
else {
|
|
335
990
|
args.view.height = unsetValue;
|
|
336
991
|
}
|
|
337
|
-
|
|
992
|
+
// Use sectioned item preparation
|
|
993
|
+
if (this.owner.sectioned) {
|
|
994
|
+
this.owner._prepareItemInSection(args.view, section, itemIndex);
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
this.owner._prepareItem(args.view, itemIndex);
|
|
998
|
+
}
|
|
338
999
|
if (!args.view.parent) {
|
|
339
|
-
//
|
|
340
|
-
//
|
|
1000
|
+
// Ensure margins defined on the template root are honored on Android ListView.
|
|
1001
|
+
// ListView's children don't support layout margins, so we insert an outer wrapper
|
|
1002
|
+
// and keep the original view (with its margins) inside. This mirrors iOS spacing.
|
|
1003
|
+
// If the view is already a LayoutBase (typical for templates like GridLayout),
|
|
1004
|
+
// we wrap it so its margins take effect. For non-layout roots (labels, etc.)
|
|
1005
|
+
// we already wrap below with a StackLayout.
|
|
341
1006
|
if (args.view instanceof LayoutBase && !(args.view instanceof ProxyViewContainer)) {
|
|
342
|
-
|
|
343
|
-
|
|
1007
|
+
var mt = PercentLength.toDevicePixels(args.view.marginTop, 0, Number.NaN);
|
|
1008
|
+
var mb = PercentLength.toDevicePixels(args.view.marginBottom, 0, Number.NaN);
|
|
1009
|
+
var ml = PercentLength.toDevicePixels(args.view.marginLeft, 0, Number.NaN);
|
|
1010
|
+
var mr = PercentLength.toDevicePixels(args.view.marginRight, 0, Number.NaN);
|
|
1011
|
+
var hasMargins = mt > 0 || mb > 0 || ml > 0 || mr > 0;
|
|
1012
|
+
if (hasMargins) {
|
|
1013
|
+
var outer = new StackLayout();
|
|
1014
|
+
outer.addChild(args.view);
|
|
1015
|
+
this.owner._addView(outer);
|
|
1016
|
+
convertView = outer.nativeViewProtected;
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
this.owner._addView(args.view);
|
|
1020
|
+
convertView = args.view.nativeViewProtected;
|
|
1021
|
+
}
|
|
344
1022
|
}
|
|
345
1023
|
else {
|
|
346
1024
|
var sp = new StackLayout();
|