nuvio-tizen 1.8.4 → 1.8.11
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/config.xml +1 -1
- package/css/style.css +22 -3
- package/index.html +9 -9
- package/js/focus.js +49 -3
- package/js/stremio.js +65 -0
- package/js/views.js +124 -17
- package/package.json +1 -1
package/config.xml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
2
|
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:tizen="http://tizen.org/ns/widgets"
|
|
3
|
-
id="http://nuvio.tizen/NuvioTizen" version="1.
|
|
3
|
+
id="http://nuvio.tizen/NuvioTizen" version="1.8.11" viewmodes="maximized">
|
|
4
4
|
<tizen:application id="Nuvio00001.Nuvio" package="Nuvio00001" required_version="2.4"/>
|
|
5
5
|
<content src="index.html"/>
|
|
6
6
|
<feature name="http://tizen.org/feature/screen.size.all"/>
|
package/css/style.css
CHANGED
|
@@ -36,6 +36,8 @@ html, body {
|
|
|
36
36
|
.brand .dot { color: #e6e9ef; }
|
|
37
37
|
.header .spacer { flex: 1; }
|
|
38
38
|
.header .gear { font-size: 30px; opacity: 0.7; }
|
|
39
|
+
.header .hchip { font-size: 24px; opacity: 0.9; padding: 10px 24px; border-radius: 30px; background: #1a1f2b; border: 4px solid transparent; }
|
|
40
|
+
.header .hchip.focusable.focused { background: #818cf8; color: #0e1117; border-color: #fff; opacity: 1; }
|
|
39
41
|
|
|
40
42
|
/* --- Rows --- */
|
|
41
43
|
.rows { position: absolute; top: 110px; left: 0; right: 0; bottom: 0; overflow: hidden; }
|
|
@@ -104,13 +106,22 @@ html, body {
|
|
|
104
106
|
.ep-row { display: flex; align-items: flex-start; padding: 12px 16px; }
|
|
105
107
|
.ep-thumb { width: 120px; height: 68px; flex: 0 0 120px; margin-right: 18px; border-radius: 6px; background-color: #0e1117; background-size: cover; background-position: center; }
|
|
106
108
|
.ep-body { flex: 1; min-width: 0; }
|
|
107
|
-
.ep-line { font-size: 22px; }
|
|
108
|
-
.ep-
|
|
109
|
+
.ep-line { display: flex; align-items: flex-start; font-size: 22px; }
|
|
110
|
+
.ep-title { flex: 1 1 auto; min-width: 0; }
|
|
111
|
+
.ep-rating { flex: 0 0 auto; margin-left: 14px; color: #ffd257; font-size: 19px; white-space: nowrap; }
|
|
112
|
+
.ep-rating:empty { margin-left: 0; }
|
|
109
113
|
.ep-ov { display: block; font-size: 17px; color: #9aa2b1; margin-top: 6px; max-height: 44px; overflow: hidden; }
|
|
110
114
|
.list-scroll { position: absolute; top: 0; left: 0; right: 0; bottom: 0; overflow: hidden; }
|
|
111
115
|
|
|
112
116
|
/* Settings */
|
|
113
|
-
.settings-wrap { position: absolute; top: 120px; left: 60px; right: 60px; }
|
|
117
|
+
.settings-wrap { position: absolute; top: 120px; left: 60px; right: 60px; bottom: 40px; overflow: hidden; }
|
|
118
|
+
.back-row { color: #c7cbd4; }
|
|
119
|
+
/* Home Feeds ordering */
|
|
120
|
+
.feed-row { display: flex; align-items: center; margin-bottom: 8px; }
|
|
121
|
+
.feed-btn { width: 60px; text-align: center; padding: 14px 0; margin-right: 8px; background: #161b26; border-radius: 8px; font-size: 22px; border: 4px solid transparent; }
|
|
122
|
+
.feed-name { flex: 1; padding: 14px 18px; background: #161b26; border-radius: 8px; font-size: 22px; border: 4px solid transparent; white-space: normal; }
|
|
123
|
+
.feed-off .feed-name { opacity: 0.5; }
|
|
124
|
+
.feed-btn.focusable.focused, .feed-name.focusable.focused { background: #818cf8; color: #0e1117; border-color: #fff; }
|
|
114
125
|
.settings-wrap h2 { font-size: 30px; margin-bottom: 20px; }
|
|
115
126
|
.addon-item { padding: 16px 20px; margin-bottom: 10px; background: #161b26; border-radius: 8px; font-size: 20px; border: 4px solid transparent; word-break: break-all; white-space: normal; }
|
|
116
127
|
.addon-item .a-name { font-weight: bold; }
|
|
@@ -180,6 +191,14 @@ html, body {
|
|
|
180
191
|
.modal-msg { font-size: 30px; line-height: 1.4; color: #e6e9ef; margin-bottom: 36px; }
|
|
181
192
|
.modal-row { display: flex; justify-content: center; }
|
|
182
193
|
.modal-btn { min-width: 200px; text-align: center; }
|
|
194
|
+
/* list picker (single choice) */
|
|
195
|
+
.pick-box { width: 720px; text-align: left; }
|
|
196
|
+
.pick-box .modal-msg { text-align: left; margin-bottom: 20px; }
|
|
197
|
+
.pick-list { max-height: 620px; overflow: hidden; }
|
|
198
|
+
.pick-item { padding: 14px 20px; margin-bottom: 8px; border-radius: 8px; background: #0e1117; border: 4px solid transparent; font-size: 24px; }
|
|
199
|
+
.pick-item.sel { background: #2a3346; }
|
|
200
|
+
.pick-item.focusable.focused { background: #818cf8; color: #0e1117; border-color: #fff; }
|
|
201
|
+
|
|
183
202
|
.pill.danger { background: #3a2130; }
|
|
184
203
|
.pill.danger.focusable.focused { background: #ef4444; color: #fff; }
|
|
185
204
|
.reset-item .a-name { color: #f0a5a5; }
|
package/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=1920, height=1080, user-scalable=no">
|
|
6
6
|
<title>Nuvio</title>
|
|
7
|
-
<link rel="stylesheet" href="css/style.css?v=1.8.
|
|
7
|
+
<link rel="stylesheet" href="css/style.css?v=1.8.11">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<!-- CSS-applied sentinel (kept off-screen); app.js waits for its width before rendering -->
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
|
|
48
48
|
<!-- Load order matters: polyfills -> util -> data -> input -> player -> views -> app -->
|
|
49
49
|
<!-- ?v= busts the TV webview cache on every release (keep in sync with package.json) -->
|
|
50
|
-
<script src="js/polyfills.js?v=1.8.
|
|
51
|
-
<script src="js/util.js?v=1.8.
|
|
52
|
-
<script src="js/stremio.js?v=1.8.
|
|
53
|
-
<script src="js/keys.js?v=1.8.
|
|
54
|
-
<script src="js/focus.js?v=1.8.
|
|
55
|
-
<script src="js/player.js?v=1.8.
|
|
56
|
-
<script src="js/views.js?v=1.8.
|
|
57
|
-
<script src="js/app.js?v=1.8.
|
|
50
|
+
<script src="js/polyfills.js?v=1.8.11"></script>
|
|
51
|
+
<script src="js/util.js?v=1.8.11"></script>
|
|
52
|
+
<script src="js/stremio.js?v=1.8.11"></script>
|
|
53
|
+
<script src="js/keys.js?v=1.8.11"></script>
|
|
54
|
+
<script src="js/focus.js?v=1.8.11"></script>
|
|
55
|
+
<script src="js/player.js?v=1.8.11"></script>
|
|
56
|
+
<script src="js/views.js?v=1.8.11"></script>
|
|
57
|
+
<script src="js/app.js?v=1.8.11"></script>
|
|
58
58
|
</body>
|
|
59
59
|
</html>
|
package/js/focus.js
CHANGED
|
@@ -20,6 +20,16 @@ var Focus = (function () {
|
|
|
20
20
|
|
|
21
21
|
function setFocus(node) {
|
|
22
22
|
if (!node) { return; }
|
|
23
|
+
// Moving to a non-input control must blur any text field that still holds
|
|
24
|
+
// DOM keyboard focus — otherwise the remote's OK/Enter is delivered to the
|
|
25
|
+
// input (e.g. re-running a search) instead of the visually-focused element.
|
|
26
|
+
try {
|
|
27
|
+
var ae = document.activeElement;
|
|
28
|
+
if (ae && ae !== node && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA') &&
|
|
29
|
+
node.tagName !== 'INPUT' && node.tagName !== 'TEXTAREA') {
|
|
30
|
+
ae.blur();
|
|
31
|
+
}
|
|
32
|
+
} catch (e) {}
|
|
23
33
|
if (_current && _current !== node && _current.classList) { _current.classList.remove('focused'); }
|
|
24
34
|
_current = node;
|
|
25
35
|
if (node.classList) { node.classList.add('focused'); }
|
|
@@ -56,8 +66,10 @@ var Focus = (function () {
|
|
|
56
66
|
|
|
57
67
|
var r = _current.getBoundingClientRect();
|
|
58
68
|
var cx = r.left + r.width / 2, cy = r.top + r.height / 2;
|
|
59
|
-
var best = null, bestScore = Infinity;
|
|
60
69
|
|
|
70
|
+
// Collect candidates in the pressed direction with their primary (distance
|
|
71
|
+
// along the axis of travel) and cross (offset on the other axis).
|
|
72
|
+
var cands = [];
|
|
61
73
|
for (var i = 0; i < list.length; i++) {
|
|
62
74
|
var node = list[i];
|
|
63
75
|
if (node === _current) { continue; }
|
|
@@ -69,8 +81,42 @@ var Focus = (function () {
|
|
|
69
81
|
else if (dir === 'RIGHT') { if (dx < 1) { continue; } primary = dx; cross = Math.abs(dy); }
|
|
70
82
|
else if (dir === 'UP') { if (dy > -1) { continue; } primary = -dy; cross = Math.abs(dx); }
|
|
71
83
|
else { if (dy < 1) { continue; } primary = dy; cross = Math.abs(dx); }
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
cands.push({ node: node, primary: primary, cross: cross });
|
|
85
|
+
}
|
|
86
|
+
if (!cands.length) { return; }
|
|
87
|
+
|
|
88
|
+
// Two-phase, direction-aware:
|
|
89
|
+
// - Vertical (UP/DOWN): first restrict to the NEAREST row (min primary =
|
|
90
|
+
// vertical distance), then pick the horizontally closest (min cross).
|
|
91
|
+
// - Horizontal (LEFT/RIGHT): first restrict to the SAME row (min cross =
|
|
92
|
+
// vertical offset), then pick the horizontally closest (min primary).
|
|
93
|
+
// This stops vertical moves skipping a short row, and stops horizontal moves
|
|
94
|
+
// jumping into another row (e.g. Settings -> LEFT should land on Search).
|
|
95
|
+
var vertical = (dir === 'UP' || dir === 'DOWN');
|
|
96
|
+
var best = null, bestSel = Infinity, bestTie = Infinity;
|
|
97
|
+
|
|
98
|
+
if (vertical) {
|
|
99
|
+
// nearest row first (min primary band), then closest horizontally
|
|
100
|
+
var BAND = 120, minP = Infinity;
|
|
101
|
+
for (var j = 0; j < cands.length; j++) { if (cands[j].primary < minP) { minP = cands[j].primary; } }
|
|
102
|
+
for (var k = 0; k < cands.length; k++) {
|
|
103
|
+
var c = cands[k];
|
|
104
|
+
if (c.primary > minP + BAND) { continue; }
|
|
105
|
+
if (c.cross < bestSel || (c.cross === bestSel && c.primary < bestTie)) {
|
|
106
|
+
best = c.node; bestSel = c.cross; bestTie = c.primary;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
// same row only (absolute vertical tolerance), then nearest horizontally.
|
|
111
|
+
// No jump to another row/header at a row end — just don't move.
|
|
112
|
+
var ROW_TOL = 140;
|
|
113
|
+
for (var m = 0; m < cands.length; m++) {
|
|
114
|
+
var d = cands[m];
|
|
115
|
+
if (d.cross > ROW_TOL) { continue; }
|
|
116
|
+
if (d.primary < bestSel || (d.primary === bestSel && d.cross < bestTie)) {
|
|
117
|
+
best = d.node; bestSel = d.primary; bestTie = d.cross;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
74
120
|
}
|
|
75
121
|
if (best) { setFocus(best); }
|
|
76
122
|
}
|
package/js/stremio.js
CHANGED
|
@@ -178,6 +178,69 @@ var Stremio = (function () {
|
|
|
178
178
|
.then(function (res) { return (res && res.metas) ? res.metas : []; });
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
// ---- Home feed ordering / visibility ----
|
|
182
|
+
var FEEDS_KEY = 'nuvio.feeds';
|
|
183
|
+
function catalogKey(cat) {
|
|
184
|
+
var aid = (cat.addon.manifest && cat.addon.manifest.id) || cat.addon.url;
|
|
185
|
+
return cat.type + '|' + cat.id + '|' + aid;
|
|
186
|
+
}
|
|
187
|
+
function getFeedCfg() {
|
|
188
|
+
var f = U.store.get(FEEDS_KEY, {}) || {};
|
|
189
|
+
return { order: f.order || [], hidden: f.hidden || [] };
|
|
190
|
+
}
|
|
191
|
+
function setFeedCfg(cfg) { U.store.set(FEEDS_KEY, cfg); }
|
|
192
|
+
|
|
193
|
+
function sortCatalogsByPref(cats) {
|
|
194
|
+
var order = getFeedCfg().order;
|
|
195
|
+
var pos = {};
|
|
196
|
+
order.forEach(function (k, i) { pos[k] = i; });
|
|
197
|
+
var withIdx = cats.map(function (c, i) { return { c: c, i: i, k: catalogKey(c) }; });
|
|
198
|
+
withIdx.sort(function (a, b) {
|
|
199
|
+
var pa = (pos[a.k] !== undefined) ? pos[a.k] : (100000 + a.i);
|
|
200
|
+
var pb = (pos[b.k] !== undefined) ? pos[b.k] : (100000 + b.i);
|
|
201
|
+
return pa - pb;
|
|
202
|
+
});
|
|
203
|
+
return withIdx.map(function (x) { return x.c; });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// All catalogs in the user's order (for the ordering settings page).
|
|
207
|
+
function listCatalogsOrdered() {
|
|
208
|
+
return listCatalogs().then(function (cats) { return sortCatalogsByPref(cats); });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Ordered + visible catalogs (for the home page).
|
|
212
|
+
function catalogsForHome() {
|
|
213
|
+
return listCatalogs().then(function (cats) {
|
|
214
|
+
var hidden = {};
|
|
215
|
+
getFeedCfg().hidden.forEach(function (k) { hidden[k] = 1; });
|
|
216
|
+
return sortCatalogsByPref(cats).filter(function (c) { return !hidden[catalogKey(c)]; });
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function moveFeed(key, dir) {
|
|
221
|
+
return listCatalogs().then(function (cats) {
|
|
222
|
+
var ordered = sortCatalogsByPref(cats).map(catalogKey);
|
|
223
|
+
var i = ordered.indexOf(key);
|
|
224
|
+
if (i < 0) { return; }
|
|
225
|
+
var j = i + dir;
|
|
226
|
+
if (j < 0 || j >= ordered.length) { return; }
|
|
227
|
+
var t = ordered[i]; ordered[i] = ordered[j]; ordered[j] = t;
|
|
228
|
+
var cfg = getFeedCfg(); cfg.order = ordered; setFeedCfg(cfg);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function toggleFeed(key) {
|
|
233
|
+
var cfg = getFeedCfg();
|
|
234
|
+
var h = {};
|
|
235
|
+
cfg.hidden.forEach(function (k) { h[k] = 1; });
|
|
236
|
+
if (h[key]) { delete h[key]; } else { h[key] = 1; }
|
|
237
|
+
cfg.hidden = Object.keys(h);
|
|
238
|
+
setFeedCfg(cfg);
|
|
239
|
+
}
|
|
240
|
+
function isFeedHidden(key) {
|
|
241
|
+
return getFeedCfg().hidden.indexOf(key) !== -1;
|
|
242
|
+
}
|
|
243
|
+
|
|
181
244
|
// ---- Meta ----
|
|
182
245
|
function getMeta(type, id) {
|
|
183
246
|
return loadAddons().then(function (addons) {
|
|
@@ -450,6 +513,8 @@ var Stremio = (function () {
|
|
|
450
513
|
tmdbFindTvId: tmdbFindTvId, tmdbSeasonEpisodes: tmdbSeasonEpisodes, tmdbImg: tmdbImg,
|
|
451
514
|
omdbSeasonRatings: omdbSeasonRatings,
|
|
452
515
|
loadAddons: loadAddons, listCatalogs: listCatalogs, getCatalog: getCatalog,
|
|
516
|
+
catalogsForHome: catalogsForHome, listCatalogsOrdered: listCatalogsOrdered,
|
|
517
|
+
catalogKey: catalogKey, moveFeed: moveFeed, toggleFeed: toggleFeed, isFeedHidden: isFeedHidden,
|
|
453
518
|
getMeta: getMeta, getStreams: getStreams, getSubtitles: getSubtitles,
|
|
454
519
|
getSearchCatalogs: getSearchCatalogs, search: search,
|
|
455
520
|
isPlayable: isPlayable, streamLabel: streamLabel, streamName: streamName,
|
package/js/views.js
CHANGED
|
@@ -42,6 +42,31 @@ var Views = (function () {
|
|
|
42
42
|
_modalScope = null; _modalPrevFocus = null;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// A scrollable single-choice list overlay (D-pad). options = [[value,label],...]
|
|
46
|
+
function pickList(title, options, current, onPick) {
|
|
47
|
+
var existing = document.querySelector('.modal-overlay');
|
|
48
|
+
if (existing) { existing.parentNode.removeChild(existing); }
|
|
49
|
+
var overlay = U.el('div', 'modal-overlay');
|
|
50
|
+
var box = U.el('div', 'modal-box pick-box');
|
|
51
|
+
box.appendChild(U.el('div', 'modal-msg', title));
|
|
52
|
+
var listEl = U.el('div', 'pick-list');
|
|
53
|
+
var pref = null;
|
|
54
|
+
options.forEach(function (opt) {
|
|
55
|
+
var it = U.el('div', 'pick-item focusable' + (opt[0] === current ? ' sel' : ''), opt[1]);
|
|
56
|
+
it.__onselect = function () { closeModal(); onPick(opt[0]); };
|
|
57
|
+
if (opt[0] === current) { pref = it; }
|
|
58
|
+
listEl.appendChild(it);
|
|
59
|
+
});
|
|
60
|
+
box.appendChild(listEl);
|
|
61
|
+
overlay.appendChild(box);
|
|
62
|
+
document.body.appendChild(overlay);
|
|
63
|
+
|
|
64
|
+
_modalPrevFocus = Focus.current();
|
|
65
|
+
_modalScope = _current();
|
|
66
|
+
_modalOpen = true;
|
|
67
|
+
Focus.setScope(overlay, pref);
|
|
68
|
+
}
|
|
69
|
+
|
|
45
70
|
function _current() {
|
|
46
71
|
var names = ['home', 'detail', 'search', 'settings', 'addaddon'];
|
|
47
72
|
for (var i = 0; i < names.length; i++) {
|
|
@@ -59,14 +84,14 @@ var Views = (function () {
|
|
|
59
84
|
h.appendChild(brand);
|
|
60
85
|
h.appendChild(U.el('div', 'spacer'));
|
|
61
86
|
if (withSearch) {
|
|
62
|
-
var srch = U.el('div', 'gear focusable', '🔍');
|
|
63
|
-
srch.style.marginRight = '
|
|
87
|
+
var srch = U.el('div', 'gear hchip focusable', 'Search 🔍');
|
|
88
|
+
srch.style.marginRight = '20px';
|
|
64
89
|
srch.__onselect = function () { App.openSearch(); };
|
|
65
90
|
srch.__navDOWN = focusFirstRowItem;
|
|
66
91
|
h.appendChild(srch);
|
|
67
92
|
}
|
|
68
93
|
if (withGear) {
|
|
69
|
-
var g = U.el('div', 'gear focusable', '⚙');
|
|
94
|
+
var g = U.el('div', 'gear hchip focusable', 'Settings ⚙');
|
|
70
95
|
g.__onselect = function () { App.openSettings(); };
|
|
71
96
|
g.__navDOWN = focusFirstRowItem;
|
|
72
97
|
h.appendChild(g);
|
|
@@ -163,8 +188,8 @@ var Views = (function () {
|
|
|
163
188
|
}
|
|
164
189
|
|
|
165
190
|
U.spinner(true);
|
|
166
|
-
Stremio.
|
|
167
|
-
var pick = cats.slice(0,
|
|
191
|
+
Stremio.catalogsForHome().then(function (cats) {
|
|
192
|
+
var pick = cats.slice(0, 15);
|
|
168
193
|
var rowNodes = pick.map(function (cat) { var ph = buildRow(cat.name, []); rows.appendChild(ph); return ph; });
|
|
169
194
|
focusHome(s);
|
|
170
195
|
U.spinner(false);
|
|
@@ -282,6 +307,7 @@ var Views = (function () {
|
|
|
282
307
|
}
|
|
283
308
|
Focus.setScope(s, _d._epFocus);
|
|
284
309
|
_d.target = null; // consume: don't re-jump after the user navigates
|
|
310
|
+
_d._focusSeason = null;
|
|
285
311
|
}
|
|
286
312
|
|
|
287
313
|
function movieActions() {
|
|
@@ -310,7 +336,9 @@ var Views = (function () {
|
|
|
310
336
|
var bar = U.el('div', 'season-bar');
|
|
311
337
|
ss.forEach(function (sn) {
|
|
312
338
|
var p = U.el('div', 'pill focusable' + (sn === _d.season ? ' season-sel' : ''), 'Season ' + sn);
|
|
313
|
-
p.__onselect = function () { _d.season = sn; drawDetail(); };
|
|
339
|
+
p.__onselect = function () { _d.season = sn; _d._focusSeason = sn; drawDetail(); };
|
|
340
|
+
// keep focus on the season pill the user just picked
|
|
341
|
+
if (_d._focusSeason === sn) { _d._epFocus = p; }
|
|
314
342
|
bar.appendChild(p);
|
|
315
343
|
});
|
|
316
344
|
panel.appendChild(bar);
|
|
@@ -332,8 +360,9 @@ var Views = (function () {
|
|
|
332
360
|
|
|
333
361
|
var body = U.el('div', 'ep-body');
|
|
334
362
|
var line = U.el('div', 'ep-line');
|
|
335
|
-
|
|
363
|
+
var title = U.el('span', 'ep-title', (watched ? '✓ ' : '') + 'E' + (v.episode || '?') + ' ' + (v.title || v.name || ''));
|
|
336
364
|
var rating = U.el('span', 'ep-rating', '');
|
|
365
|
+
line.appendChild(title);
|
|
337
366
|
line.appendChild(rating);
|
|
338
367
|
body.appendChild(line);
|
|
339
368
|
var ov = U.el('span', 'ep-ov', v.overview || '');
|
|
@@ -366,7 +395,7 @@ var Views = (function () {
|
|
|
366
395
|
if (!map || _d.season !== seasonAtFetch) { return; }
|
|
367
396
|
epRefs.forEach(function (r) {
|
|
368
397
|
var rt = map[r.num];
|
|
369
|
-
if (rt) { r.rating.textContent = '
|
|
398
|
+
if (rt) { r.rating.textContent = '★ ' + rt + ' IMDb'; }
|
|
370
399
|
});
|
|
371
400
|
});
|
|
372
401
|
}
|
|
@@ -379,8 +408,9 @@ var Views = (function () {
|
|
|
379
408
|
var ep = map[r.num];
|
|
380
409
|
if (!ep) { return; }
|
|
381
410
|
if (ep.still && !r.thumb.style.backgroundImage) { r.thumb.style.backgroundImage = 'url("' + ep.still + '")'; }
|
|
382
|
-
//
|
|
383
|
-
|
|
411
|
+
// Fill the rating only if OMDb didn't provide one for this episode
|
|
412
|
+
// (OMDb's dataset is missing IMDb ratings for some episodes).
|
|
413
|
+
if (ep.rating && !r.rating.textContent) { r.rating.textContent = '★ ' + (Math.round(ep.rating * 10) / 10) + ' TMDb'; }
|
|
384
414
|
if (ep.overview && !r.ov.textContent) { r.ov.textContent = ep.overview; }
|
|
385
415
|
});
|
|
386
416
|
});
|
|
@@ -555,6 +585,10 @@ var Views = (function () {
|
|
|
555
585
|
var go = U.el('div', 'pill focusable search-go', 'Search');
|
|
556
586
|
go.__onselect = function () { runSearch(input.value); };
|
|
557
587
|
bar.appendChild(go);
|
|
588
|
+
|
|
589
|
+
var back = U.el('div', 'pill focusable search-go', '← Back');
|
|
590
|
+
back.__onselect = function () { App.back(); };
|
|
591
|
+
bar.appendChild(back);
|
|
558
592
|
wrap.appendChild(bar);
|
|
559
593
|
|
|
560
594
|
var results = U.el('div', 'search-results');
|
|
@@ -681,6 +715,23 @@ var Views = (function () {
|
|
|
681
715
|
return row;
|
|
682
716
|
}
|
|
683
717
|
|
|
718
|
+
// Like subCycleRow but OK opens a scrollable list picker.
|
|
719
|
+
function subPickRow(caption, list, key) {
|
|
720
|
+
var row = U.el('div', 'addon-item focusable cfg-row');
|
|
721
|
+
var name = U.el('div', 'a-name');
|
|
722
|
+
function render() { name.textContent = caption + ': ' + labelFor(list, Player.getSubCfg()[key]); }
|
|
723
|
+
render();
|
|
724
|
+
row.appendChild(name);
|
|
725
|
+
row.appendChild(U.el('div', 'a-url', 'Press OK to choose'));
|
|
726
|
+
row.__onselect = function () {
|
|
727
|
+
pickList(caption, list, Player.getSubCfg()[key], function (v) {
|
|
728
|
+
var cfg = Player.getSubCfg(); cfg[key] = v; Player.setSubCfg(cfg);
|
|
729
|
+
render();
|
|
730
|
+
});
|
|
731
|
+
};
|
|
732
|
+
return row;
|
|
733
|
+
}
|
|
734
|
+
|
|
684
735
|
// ================= SETTINGS (sectioned) =================
|
|
685
736
|
var _settingsSection = 'menu';
|
|
686
737
|
|
|
@@ -701,6 +752,7 @@ var Views = (function () {
|
|
|
701
752
|
if (_settingsSection === 'addons') { settingsAddons(wrap); }
|
|
702
753
|
else if (_settingsSection === 'integrations') { settingsIntegrations(wrap); }
|
|
703
754
|
else if (_settingsSection === 'subtitles') { settingsSubtitles(wrap); }
|
|
755
|
+
else if (_settingsSection === 'feeds') { settingsFeeds(wrap); }
|
|
704
756
|
else if (_settingsSection === 'display') { settingsDisplay(wrap); }
|
|
705
757
|
else { settingsMenu(wrap); }
|
|
706
758
|
}
|
|
@@ -713,13 +765,20 @@ var Views = (function () {
|
|
|
713
765
|
return r;
|
|
714
766
|
}
|
|
715
767
|
|
|
768
|
+
function backRow() {
|
|
769
|
+
var r = U.el('div', 'addon-item focusable back-row', '← Back');
|
|
770
|
+
r.__onselect = function () { App.back(); };
|
|
771
|
+
return r;
|
|
772
|
+
}
|
|
773
|
+
|
|
716
774
|
function settingsMenu(wrap) {
|
|
717
775
|
wrap.appendChild(U.el('h2', null, 'Settings'));
|
|
718
776
|
wrap.appendChild(sectionRow('Addons', 'Catalog, stream & subtitle addons', 'addons'));
|
|
719
|
-
wrap.appendChild(sectionRow('Integrations', 'API keys (TMDB, MDBList)', 'integrations'));
|
|
777
|
+
wrap.appendChild(sectionRow('Integrations', 'API keys (OMDb, TMDB, MDBList)', 'integrations'));
|
|
720
778
|
wrap.appendChild(sectionRow('Subtitle Options', 'Default language, size, style', 'subtitles'));
|
|
779
|
+
wrap.appendChild(sectionRow('Home Feeds', 'Order & show/hide the home rows', 'feeds'));
|
|
721
780
|
wrap.appendChild(sectionRow('Display', 'Poster orientation', 'display'));
|
|
722
|
-
wrap.appendChild(
|
|
781
|
+
wrap.appendChild(backRow());
|
|
723
782
|
Focus.setScope(scr('settings'));
|
|
724
783
|
}
|
|
725
784
|
|
|
@@ -772,7 +831,8 @@ var Views = (function () {
|
|
|
772
831
|
wrap.appendChild(addRow);
|
|
773
832
|
|
|
774
833
|
wrap.appendChild(U.el('div', 'hint',
|
|
775
|
-
'Nuvio hosts no content — add Stremio addon manifest URLs for catalogs, streams and subtitles.
|
|
834
|
+
'Nuvio hosts no content — add Stremio addon manifest URLs for catalogs, streams and subtitles.'));
|
|
835
|
+
wrap.appendChild(backRow());
|
|
776
836
|
Focus.setScope(scr('settings'));
|
|
777
837
|
});
|
|
778
838
|
}
|
|
@@ -800,16 +860,17 @@ var Views = (function () {
|
|
|
800
860
|
wrap.appendChild(keyRow('OMDb API key', 'omdb', 'OMDb API key', Stremio.omdbValidate));
|
|
801
861
|
wrap.appendChild(keyRow('TMDB API key (v3)', 'tmdb', 'TMDB v3 API key', Stremio.tmdbValidate));
|
|
802
862
|
wrap.appendChild(keyRow('MDBList API key', 'mdblist', 'MDBList API key', Stremio.mdblistValidate));
|
|
803
|
-
wrap.appendChild(U.el('div', 'hint', 'Keys are stored on this TV only. OMDb gives real IMDb per-episode ratings (used first when set); TMDB adds episode images & ratings.
|
|
863
|
+
wrap.appendChild(U.el('div', 'hint', 'Keys are stored on this TV only. OMDb gives real IMDb per-episode ratings (used first when set); TMDB adds episode images & ratings.'));
|
|
864
|
+
wrap.appendChild(backRow());
|
|
804
865
|
Focus.setScope(scr('settings'));
|
|
805
866
|
}
|
|
806
867
|
|
|
807
868
|
function settingsSubtitles(wrap) {
|
|
808
869
|
wrap.appendChild(U.el('h2', null, 'Subtitle Options'));
|
|
809
|
-
wrap.appendChild(
|
|
870
|
+
wrap.appendChild(subPickRow('Default language', SUB_LANGS, 'lang'));
|
|
810
871
|
wrap.appendChild(subCycleRow('Size', SUB_SIZES, 'size'));
|
|
811
872
|
wrap.appendChild(subCycleRow('Style', SUB_STYLES, 'style'));
|
|
812
|
-
wrap.appendChild(
|
|
873
|
+
wrap.appendChild(backRow());
|
|
813
874
|
Focus.setScope(scr('settings'));
|
|
814
875
|
}
|
|
815
876
|
|
|
@@ -828,10 +889,56 @@ var Views = (function () {
|
|
|
828
889
|
render();
|
|
829
890
|
};
|
|
830
891
|
wrap.appendChild(row);
|
|
831
|
-
wrap.appendChild(U.el('div', 'hint', 'Landscape uses wide artwork when available (falls back to the poster).
|
|
892
|
+
wrap.appendChild(U.el('div', 'hint', 'Landscape uses wide artwork when available (falls back to the poster).'));
|
|
893
|
+
wrap.appendChild(backRow());
|
|
832
894
|
Focus.setScope(scr('settings'));
|
|
833
895
|
}
|
|
834
896
|
|
|
897
|
+
// ----- Home Feeds ordering -----
|
|
898
|
+
var _feedFocus = null;
|
|
899
|
+
function refeed(key, ctl) { _feedFocus = { key: key, ctl: ctl }; markHomeDirty(); clearCatalogCache(); renderSettings(); }
|
|
900
|
+
|
|
901
|
+
function settingsFeeds(wrap) {
|
|
902
|
+
wrap.appendChild(U.el('h2', null, 'Home Feeds'));
|
|
903
|
+
wrap.appendChild(U.el('div', 'hint', '▲/▼ reorder a feed · OK on the name shows/hides it.'));
|
|
904
|
+
Stremio.listCatalogsOrdered().then(function (cats) {
|
|
905
|
+
cats.forEach(function (cat) {
|
|
906
|
+
var key = Stremio.catalogKey(cat);
|
|
907
|
+
var hidden = Stremio.isFeedHidden(key);
|
|
908
|
+
var frow = U.el('div', 'feed-row' + (hidden ? ' feed-off' : ''));
|
|
909
|
+
|
|
910
|
+
var up = U.el('div', 'feed-btn focusable', '▲');
|
|
911
|
+
up.setAttribute('data-fk', key); up.setAttribute('data-ctl', 'up');
|
|
912
|
+
up.__onselect = function () { Stremio.moveFeed(key, -1).then(function () { refeed(key, 'up'); }); };
|
|
913
|
+
|
|
914
|
+
var down = U.el('div', 'feed-btn focusable', '▼');
|
|
915
|
+
down.setAttribute('data-fk', key); down.setAttribute('data-ctl', 'down');
|
|
916
|
+
down.__onselect = function () { Stremio.moveFeed(key, 1).then(function () { refeed(key, 'down'); }); };
|
|
917
|
+
|
|
918
|
+
var name = U.el('div', 'feed-name focusable', cat.name + (hidden ? ' (hidden)' : ''));
|
|
919
|
+
name.setAttribute('data-fk', key); name.setAttribute('data-ctl', 'name');
|
|
920
|
+
name.__onselect = function () { Stremio.toggleFeed(key); refeed(key, 'name'); };
|
|
921
|
+
|
|
922
|
+
frow.appendChild(up);
|
|
923
|
+
frow.appendChild(down);
|
|
924
|
+
frow.appendChild(name);
|
|
925
|
+
wrap.appendChild(frow);
|
|
926
|
+
});
|
|
927
|
+
wrap.appendChild(backRow());
|
|
928
|
+
|
|
929
|
+
// restore focus to the control that triggered the last change
|
|
930
|
+
var pref = null;
|
|
931
|
+
if (_feedFocus) {
|
|
932
|
+
var nodes = wrap.querySelectorAll('.feed-btn, .feed-name');
|
|
933
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
934
|
+
if (nodes[i].getAttribute('data-fk') === _feedFocus.key && nodes[i].getAttribute('data-ctl') === _feedFocus.ctl) { pref = nodes[i]; break; }
|
|
935
|
+
}
|
|
936
|
+
_feedFocus = null;
|
|
937
|
+
}
|
|
938
|
+
Focus.setScope(scr('settings'), pref);
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
|
|
835
942
|
return {
|
|
836
943
|
renderHome: renderHome, renderDetail: renderDetail, renderSettings: renderSettings,
|
|
837
944
|
settingsRoot: settingsRoot, backInSettings: backInSettings,
|