livefootballtv-tizen 1.0.0

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.
@@ -0,0 +1,663 @@
1
+ // navigation.js - ES5 D-Pad Navigation for Tizen v4
2
+ // Handles arrow key navigation for categories, posts grid, and player controls
3
+
4
+ (function (window) {
5
+ 'use strict';
6
+
7
+ var DPadNavigator = {
8
+ currentZone: 'categories', // 'categories', 'posts', 'player', 'settings'
9
+ focusedCategoryIndex: 0,
10
+ focusedPostIndex: 0,
11
+ focusedPlayerControlIndex: 0,
12
+ lastFocusedPostIndex: 0,
13
+ gridColumns: 4, // Default columns, will be calculated dynamically
14
+ isTransitioning: false, // Flag to prevent key handling during transitions
15
+
16
+ init: function () {
17
+ var self = this;
18
+ console.log('DPadNavigator: Initializing');
19
+
20
+ // Global keydown handler
21
+ document.addEventListener('keydown', function (e) {
22
+ self.handleKeyDown(e);
23
+ });
24
+
25
+ // Focus first category on load
26
+ setTimeout(function () {
27
+ self.focusCategory(0);
28
+ console.log('DPadNavigator: Initial focus set to category 0');
29
+ }, 500);
30
+ },
31
+
32
+ handleKeyDown: function (e) {
33
+ var key = e.key || e.keyCode;
34
+ var keyName = typeof key === 'string' ? key : this.getKeyName(key);
35
+
36
+ // Ignore key repeat for Enter key to prevent multiple triggers
37
+ if (e.repeat && keyName === 'Enter') {
38
+ e.preventDefault();
39
+ e.stopPropagation();
40
+ return;
41
+ }
42
+
43
+ // Block all input during transitions
44
+ if (this.isTransitioning) {
45
+ e.preventDefault();
46
+ e.stopPropagation();
47
+ return;
48
+ }
49
+
50
+ // Don't interfere with text input (except for Escape)
51
+ if (e.target.tagName === 'INPUT' && keyName !== 'Escape') {
52
+ return;
53
+ }
54
+
55
+ // Don't interfere with select elements
56
+ if (e.target.tagName === 'SELECT') {
57
+ return;
58
+ }
59
+
60
+ var handled = false;
61
+
62
+ switch (this.currentZone) {
63
+ case 'categories':
64
+ handled = this.handleCategoryNavigation(keyName, e);
65
+ break;
66
+ case 'posts':
67
+ handled = this.handlePostsNavigation(keyName, e);
68
+ break;
69
+ case 'player':
70
+ handled = this.handlePlayerNavigation(keyName, e);
71
+ break;
72
+ case 'settings':
73
+ handled = this.handleSettingsNavigation(keyName, e);
74
+ break;
75
+ case 'display':
76
+ handled = this.handleDisplayNavigation(keyName, e);
77
+ break;
78
+ case 'header':
79
+ handled = this.handleHeaderNavigation(keyName, e);
80
+ break;
81
+ }
82
+
83
+ if (handled) {
84
+ e.preventDefault();
85
+ e.stopPropagation();
86
+ }
87
+ },
88
+
89
+ getKeyName: function (keyCode) {
90
+ var keys = {
91
+ 37: 'ArrowLeft',
92
+ 38: 'ArrowUp',
93
+ 39: 'ArrowRight',
94
+ 40: 'ArrowDown',
95
+ 13: 'Enter',
96
+ 27: 'Escape',
97
+ 8: 'Backspace',
98
+ 10009: 'Back' // Tizen back button
99
+ };
100
+ return keys[keyCode] || '';
101
+ },
102
+
103
+ handleCategoryNavigation: function (key, event) {
104
+ var categories = document.querySelectorAll('.category');
105
+ if (!categories.length) return false;
106
+
107
+ switch (key) {
108
+ case 'ArrowDown':
109
+ this.focusedCategoryIndex = Math.min(this.focusedCategoryIndex + 1, categories.length - 1);
110
+ this.focusCategory(this.focusedCategoryIndex);
111
+ return true;
112
+
113
+ case 'ArrowUp':
114
+ if (this.focusedCategoryIndex === 0) {
115
+ // Move to proxy indicator in header
116
+ var proxyIndicator = document.getElementById('proxyStatusIndicator');
117
+ if (proxyIndicator) {
118
+ this.setZone('header');
119
+ proxyIndicator.focus();
120
+ }
121
+ } else {
122
+ this.focusedCategoryIndex = Math.max(this.focusedCategoryIndex - 1, 0);
123
+ this.focusCategory(this.focusedCategoryIndex);
124
+ }
125
+ return true;
126
+
127
+ case 'ArrowRight':
128
+ // Move to posts
129
+ this.setZone('posts');
130
+ this.focusPost(this.focusedPostIndex);
131
+ return true;
132
+
133
+ case 'Enter':
134
+ if (categories[this.focusedCategoryIndex]) {
135
+ console.log('DPadNavigator: Clicking category ' + this.focusedCategoryIndex);
136
+ categories[this.focusedCategoryIndex].click();
137
+ }
138
+ return true;
139
+ }
140
+ return false;
141
+ },
142
+
143
+ handlePostsNavigation: function (key, event) {
144
+ var posts = document.querySelectorAll('.post');
145
+ if (!posts.length) {
146
+ console.log('DPadNavigator: No posts found');
147
+ return false;
148
+ }
149
+
150
+ // Calculate columns dynamically based on window width
151
+ this.calculateGridColumns();
152
+
153
+ switch (key) {
154
+ case 'ArrowRight':
155
+ if ((this.focusedPostIndex + 1) % this.gridColumns === 0) {
156
+ // End of row, wrap to next row
157
+ if (this.focusedPostIndex + 1 < posts.length) {
158
+ this.focusedPostIndex++;
159
+ }
160
+ } else {
161
+ this.focusedPostIndex = Math.min(this.focusedPostIndex + 1, posts.length - 1);
162
+ }
163
+ this.focusPost(this.focusedPostIndex);
164
+ return true;
165
+
166
+ case 'ArrowLeft':
167
+ if (this.focusedPostIndex % this.gridColumns === 0) {
168
+ // Start of row, move to categories
169
+ this.setZone('categories');
170
+ this.focusCategory(this.focusedCategoryIndex);
171
+ } else {
172
+ this.focusedPostIndex = Math.max(this.focusedPostIndex - 1, 0);
173
+ this.focusPost(this.focusedPostIndex);
174
+ }
175
+ return true;
176
+
177
+ case 'ArrowDown':
178
+ var nextIndex = this.focusedPostIndex + this.gridColumns;
179
+ if (nextIndex < posts.length) {
180
+ this.focusedPostIndex = nextIndex;
181
+ this.focusPost(this.focusedPostIndex);
182
+ }
183
+ return true;
184
+
185
+ case 'ArrowUp':
186
+ var prevIndex = this.focusedPostIndex - this.gridColumns;
187
+ if (prevIndex >= 0) {
188
+ this.focusedPostIndex = prevIndex;
189
+ this.focusPost(this.focusedPostIndex);
190
+ } else {
191
+ // Move to proxy indicator in header
192
+ var proxyIndicator = document.getElementById('proxyStatusIndicator');
193
+ if (proxyIndicator) {
194
+ this.setZone('header');
195
+ proxyIndicator.focus();
196
+ }
197
+ }
198
+ return true;
199
+
200
+ case 'Enter':
201
+ if (posts[this.focusedPostIndex]) {
202
+ console.log('DPadNavigator: Opening post ' + this.focusedPostIndex);
203
+ this.lastFocusedPostIndex = this.focusedPostIndex;
204
+ posts[this.focusedPostIndex].click();
205
+ // Note: enterPlayerMode will be called from openPost in app-es5.js
206
+ }
207
+ return true;
208
+
209
+ case 'Escape':
210
+ case 'Back':
211
+ case 'Backspace':
212
+ // Go back to categories
213
+ this.setZone('categories');
214
+ this.focusCategory(this.focusedCategoryIndex);
215
+ return true;
216
+ }
217
+ return false;
218
+ },
219
+
220
+ handlePlayerNavigation: function (key, event) {
221
+ var modal = document.getElementById('videoPlayerModal');
222
+
223
+ // Check if modal is visible
224
+ var isModalVisible = modal && (
225
+ modal.style.display === 'flex' ||
226
+ modal.style.display === 'block' ||
227
+ (!modal.hidden && modal.style.display !== 'none')
228
+ );
229
+
230
+ if (!isModalVisible) {
231
+ console.log('DPadNavigator: Player modal not visible, switching to posts');
232
+ this.setZone('posts');
233
+ return false;
234
+ }
235
+
236
+ var closeBtn = document.getElementById('closePlayer');
237
+ var video = document.getElementById('videoPlayer');
238
+
239
+ // Get Shaka UI overflow menu and its button
240
+ var overflowMenuBtn = modal.querySelector('.shaka-overflow-menu-button');
241
+ var overflowMenu = modal.querySelector('.shaka-overflow-menu');
242
+ var isOverflowOpen = overflowMenu && overflowMenu.classList.contains('shaka-displayed');
243
+
244
+ // Get Shaka UI control panel buttons
245
+ var controlPanel = modal.querySelector('.shaka-controls-button-panel');
246
+ var controlButtons = controlPanel ? controlPanel.querySelectorAll('button') : [];
247
+
248
+ // Debug logging - only log once per player session
249
+ if (!window.shakaNavDebugLogged) {
250
+ console.log('DPadNavigator: Shaka UI Debug');
251
+ console.log(' - Control panel found:', !!controlPanel);
252
+ console.log(' - Control buttons found:', controlButtons.length);
253
+ console.log(' - Overflow menu button found:', !!overflowMenuBtn);
254
+ console.log(' - Overflow menu found:', !!overflowMenu);
255
+ if (controlButtons.length > 0) {
256
+ for (var k = 0; k < controlButtons.length; k++) {
257
+ console.log(' - Button ' + k + ':', controlButtons[k].className);
258
+ // Make buttons focusable for D-pad navigation
259
+ controlButtons[k].tabIndex = 0;
260
+ // Add focus event to show visual indicator
261
+ controlButtons[k].onfocus = function () {
262
+ this.style.outline = '3px solid #3ea6ff';
263
+ this.style.outlineOffset = '2px';
264
+ };
265
+ controlButtons[k].onblur = function () {
266
+ this.style.outline = 'none';
267
+ };
268
+ }
269
+ }
270
+ window.shakaNavDebugLogged = true;
271
+ }
272
+
273
+ // Handle navigation within Shaka overflow menu when open
274
+ if (isOverflowOpen && overflowMenu) {
275
+ var menuButtons = overflowMenu.querySelectorAll('button');
276
+ var currentFocused = document.activeElement;
277
+ var currentIndex = -1;
278
+ for (var i = 0; i < menuButtons.length; i++) {
279
+ if (menuButtons[i] === currentFocused) {
280
+ currentIndex = i;
281
+ break;
282
+ }
283
+ }
284
+
285
+ switch (key) {
286
+ case 'ArrowDown':
287
+ var nextIdx = Math.min(currentIndex + 1, menuButtons.length - 1);
288
+ if (nextIdx >= 0 && menuButtons[nextIdx]) menuButtons[nextIdx].focus();
289
+ return true;
290
+
291
+ case 'ArrowUp':
292
+ if (currentIndex <= 0) {
293
+ // Close menu and focus overflow button
294
+ if (overflowMenuBtn) overflowMenuBtn.click();
295
+ } else {
296
+ menuButtons[currentIndex - 1].focus();
297
+ }
298
+ return true;
299
+
300
+ case 'Enter':
301
+ if (currentFocused) currentFocused.click();
302
+ return true;
303
+
304
+ case 'Escape':
305
+ case 'Back':
306
+ case 'Backspace':
307
+ // Close overflow menu
308
+ if (overflowMenuBtn) overflowMenuBtn.click();
309
+ return true;
310
+ }
311
+ return false;
312
+ }
313
+
314
+ // Build focusable elements list: close button, video, then Shaka control buttons
315
+ var focusableElements = [];
316
+ if (closeBtn) focusableElements.push(closeBtn);
317
+ if (video) focusableElements.push(video);
318
+ for (var j = 0; j < controlButtons.length; j++) {
319
+ focusableElements.push(controlButtons[j]);
320
+ }
321
+
322
+
323
+ if (!focusableElements.length) return false;
324
+
325
+ // Clamp index to valid range
326
+ this.focusedPlayerControlIndex = Math.max(0, Math.min(this.focusedPlayerControlIndex, focusableElements.length - 1));
327
+
328
+ switch (key) {
329
+ case 'ArrowRight':
330
+ this.focusedPlayerControlIndex = Math.min(this.focusedPlayerControlIndex + 1, focusableElements.length - 1);
331
+ focusableElements[this.focusedPlayerControlIndex].focus();
332
+ return true;
333
+
334
+ case 'ArrowLeft':
335
+ this.focusedPlayerControlIndex = Math.max(this.focusedPlayerControlIndex - 1, 0);
336
+ focusableElements[this.focusedPlayerControlIndex].focus();
337
+ return true;
338
+
339
+ case 'ArrowDown':
340
+ // If on video (index 1), move to first control button
341
+ if (this.focusedPlayerControlIndex === 1 && controlButtons.length > 0) {
342
+ this.focusedPlayerControlIndex = 2;
343
+ focusableElements[this.focusedPlayerControlIndex].focus();
344
+ } else {
345
+ this.focusedPlayerControlIndex = Math.min(this.focusedPlayerControlIndex + 1, focusableElements.length - 1);
346
+ focusableElements[this.focusedPlayerControlIndex].focus();
347
+ }
348
+ return true;
349
+
350
+ case 'ArrowUp':
351
+ // If on control buttons (index >= 2), move to video
352
+ if (this.focusedPlayerControlIndex >= 2) {
353
+ this.focusedPlayerControlIndex = 1;
354
+ if (video) video.focus();
355
+ } else {
356
+ this.focusedPlayerControlIndex = Math.max(this.focusedPlayerControlIndex - 1, 0);
357
+ focusableElements[this.focusedPlayerControlIndex].focus();
358
+ }
359
+ return true;
360
+
361
+
362
+ case 'Enter':
363
+ var focused = focusableElements[this.focusedPlayerControlIndex];
364
+ // If focused on video, toggle play/pause
365
+ if (focused === video) {
366
+ if (video.paused) {
367
+ video.play().catch(function (e) { console.log('Play failed:', e); });
368
+ this.showPlayPauseIndicator('▶');
369
+ } else {
370
+ video.pause();
371
+ this.showPlayPauseIndicator('⏸');
372
+ }
373
+ return true;
374
+ }
375
+ // Otherwise click the focused element
376
+ if (focused) {
377
+ focused.click();
378
+ }
379
+ return true;
380
+
381
+ case 'Escape':
382
+ case 'Back':
383
+ case 'Backspace':
384
+ console.log('DPadNavigator: Closing player');
385
+ if (typeof window.closePlayer === 'function') {
386
+ window.closePlayer();
387
+ } else if (closeBtn) {
388
+ closeBtn.click();
389
+ }
390
+ return true;
391
+ }
392
+ return false;
393
+ },
394
+
395
+ // Show play/pause indicator overlay
396
+ showPlayPauseIndicator: function (symbol) {
397
+ var video = document.getElementById('videoPlayer');
398
+ if (!video) return;
399
+
400
+ var container = video.parentElement;
401
+ var indicator = container.querySelector('.play-pause-indicator');
402
+ if (!indicator) {
403
+ indicator = document.createElement('div');
404
+ indicator.className = 'play-pause-indicator';
405
+ indicator.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:80px;color:#fff;background:rgba(0,0,0,0.6);padding:30px 50px;border-radius:20px;pointer-events:none;opacity:0;transition:opacity 0.3s;z-index:50;';
406
+ container.appendChild(indicator);
407
+ }
408
+
409
+ indicator.textContent = symbol;
410
+ indicator.style.opacity = '1';
411
+
412
+ // Hide after 1 second
413
+ setTimeout(function () {
414
+ indicator.style.opacity = '0';
415
+ }, 1000);
416
+ },
417
+
418
+
419
+ handleSettingsNavigation: function (key, event) {
420
+ var settingsBtn = document.getElementById('settingsBtn');
421
+ var settingsDropdown = document.getElementById('settingsDropdown');
422
+ var displayBtn = document.getElementById('displayBtn');
423
+ var logLevelSelect = document.getElementById('logLevelSelect');
424
+
425
+ switch (key) {
426
+ case 'ArrowDown':
427
+ // If dropdown is open and has select, focus it
428
+ if (settingsDropdown && settingsDropdown.style.display === 'block' && logLevelSelect) {
429
+ logLevelSelect.focus();
430
+ return true;
431
+ }
432
+ // Otherwise go to categories
433
+ if (settingsDropdown) settingsDropdown.style.display = 'none';
434
+ this.setZone('categories');
435
+ this.focusCategory(this.focusedCategoryIndex);
436
+ return true;
437
+
438
+ case 'ArrowLeft':
439
+ // Move to display button
440
+ if (settingsDropdown) settingsDropdown.style.display = 'none';
441
+ if (displayBtn) {
442
+ this.setZone('display');
443
+ displayBtn.focus();
444
+ }
445
+ return true;
446
+
447
+ case 'ArrowRight':
448
+ // Go to posts
449
+ if (settingsDropdown) settingsDropdown.style.display = 'none';
450
+ this.setZone('posts');
451
+ this.focusPost(this.focusedPostIndex);
452
+ return true;
453
+
454
+ case 'Escape':
455
+ case 'Back':
456
+ case 'Backspace':
457
+ // Close dropdown and go to categories
458
+ if (settingsDropdown) settingsDropdown.style.display = 'none';
459
+ this.setZone('categories');
460
+ this.focusCategory(this.focusedCategoryIndex);
461
+ return true;
462
+
463
+ case 'Enter':
464
+ // Toggle settings dropdown
465
+ if (settingsBtn) {
466
+ settingsBtn.click();
467
+ }
468
+ return true;
469
+ }
470
+ return false;
471
+ },
472
+
473
+ handleDisplayNavigation: function (key, event) {
474
+ var displayBtn = document.getElementById('displayBtn');
475
+ var displayPopup = document.getElementById('displayPopup');
476
+ var slider = document.getElementById('iconsPerRowSlider');
477
+ var settingsBtn = document.getElementById('settingsBtn');
478
+
479
+ switch (key) {
480
+ case 'ArrowDown':
481
+ // Close popup and go to categories
482
+ if (displayPopup) displayPopup.style.display = 'none';
483
+ this.setZone('categories');
484
+ this.focusCategory(this.focusedCategoryIndex);
485
+ return true;
486
+
487
+ case 'ArrowUp':
488
+ // Stay on slider if popup is open, otherwise do nothing
489
+ return true;
490
+
491
+ case 'ArrowLeft':
492
+ // Move to proxy indicator
493
+ if (displayPopup) displayPopup.style.display = 'none';
494
+ var proxyIndicator = document.getElementById('proxyStatusIndicator');
495
+ if (proxyIndicator) {
496
+ this.setZone('header');
497
+ proxyIndicator.focus();
498
+ }
499
+ return true;
500
+
501
+ case 'ArrowRight':
502
+ // Move to settings button
503
+ if (displayPopup) displayPopup.style.display = 'none';
504
+ if (settingsBtn) {
505
+ this.setZone('settings');
506
+ settingsBtn.focus();
507
+ }
508
+ return true;
509
+
510
+ case 'Escape':
511
+ case 'Back':
512
+ case 'Backspace':
513
+ // Close popup and go to categories
514
+ if (displayPopup) displayPopup.style.display = 'none';
515
+ this.setZone('categories');
516
+ this.focusCategory(this.focusedCategoryIndex);
517
+ return true;
518
+
519
+ case 'Enter':
520
+ // Toggle display popup
521
+ if (displayBtn) {
522
+ displayBtn.click();
523
+ }
524
+ return true;
525
+ }
526
+ return false;
527
+ },
528
+
529
+ handleHeaderNavigation: function (key, event) {
530
+ var proxyIndicator = document.getElementById('proxyStatusIndicator');
531
+ var displayBtn = document.getElementById('displayBtn');
532
+
533
+ switch (key) {
534
+ case 'ArrowDown':
535
+ // Go to categories
536
+ this.setZone('categories');
537
+ this.focusCategory(this.focusedCategoryIndex);
538
+ return true;
539
+
540
+ case 'ArrowUp':
541
+ // Stay at proxy indicator
542
+ return true;
543
+
544
+ case 'ArrowRight':
545
+ // Move to display button
546
+ if (displayBtn) {
547
+ this.setZone('display');
548
+ displayBtn.focus();
549
+ }
550
+ return true;
551
+
552
+ case 'ArrowLeft':
553
+ // Stay at proxy indicator (leftmost header item)
554
+ return true;
555
+
556
+ case 'Escape':
557
+ case 'Back':
558
+ case 'Backspace':
559
+ // Go to categories
560
+ this.setZone('categories');
561
+ this.focusCategory(this.focusedCategoryIndex);
562
+ return true;
563
+
564
+ case 'Enter':
565
+ // Trigger proxy status check
566
+ if (proxyIndicator) {
567
+ proxyIndicator.click();
568
+ }
569
+ return true;
570
+ }
571
+ return false;
572
+ },
573
+
574
+ calculateGridColumns: function () {
575
+ var savedIconsPerRow = localStorage.getItem('iconsPerRow');
576
+ if (savedIconsPerRow) {
577
+ this.gridColumns = parseInt(savedIconsPerRow, 10);
578
+ return;
579
+ }
580
+
581
+ var width = window.innerWidth;
582
+ if (width > 1400) {
583
+ this.gridColumns = 4;
584
+ } else if (width > 1000) {
585
+ this.gridColumns = 3;
586
+ } else if (width > 600) {
587
+ this.gridColumns = 2;
588
+ } else {
589
+ this.gridColumns = 1;
590
+ }
591
+ },
592
+
593
+ focusCategory: function (index) {
594
+ var categories = document.querySelectorAll('.category');
595
+ if (categories[index]) {
596
+ categories[index].focus();
597
+ this.scrollIntoViewIfNeeded(categories[index]);
598
+ }
599
+ },
600
+
601
+ focusPost: function (index) {
602
+ var posts = document.querySelectorAll('.post');
603
+ if (posts[index]) {
604
+ posts[index].focus();
605
+ this.scrollIntoViewIfNeeded(posts[index]);
606
+ }
607
+ },
608
+
609
+ scrollIntoViewIfNeeded: function (element) {
610
+ if (!element) return;
611
+
612
+ var rect = element.getBoundingClientRect();
613
+ var isVisible = (
614
+ rect.top >= 0 &&
615
+ rect.left >= 0 &&
616
+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
617
+ rect.right <= (window.innerWidth || document.documentElement.clientWidth)
618
+ );
619
+
620
+ if (!isVisible) {
621
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
622
+ }
623
+ },
624
+
625
+ setZone: function (zone) {
626
+ console.log('DPadNavigator: Zone changed from ' + this.currentZone + ' to ' + zone);
627
+ this.currentZone = zone;
628
+ },
629
+
630
+ enterPlayerMode: function () {
631
+ var self = this;
632
+ console.log('DPadNavigator: Entering player mode');
633
+
634
+ // Set transition flag to block input
635
+ this.isTransitioning = true;
636
+
637
+ this.setZone('player');
638
+ this.focusedPlayerControlIndex = 1; // Start at video element (index 1), not close button
639
+
640
+ // Focus video element after transition delay
641
+ setTimeout(function () {
642
+ var video = document.getElementById('videoPlayer');
643
+ if (video) {
644
+ video.focus();
645
+ console.log('DPadNavigator: Focused video element');
646
+ }
647
+ // Clear transition flag after focus is set
648
+ self.isTransitioning = false;
649
+ }, 300); // Delay to avoid key repeat issues
650
+ },
651
+
652
+ exitPlayerMode: function () {
653
+ console.log('DPadNavigator: Exiting player mode');
654
+ this.setZone('posts');
655
+ // Restore focus to last selected post
656
+ this.focusPost(this.lastFocusedPostIndex);
657
+ }
658
+ };
659
+
660
+ // Expose to global scope
661
+ window.DPadNavigator = DPadNavigator;
662
+
663
+ })(window);