nuvio-tizen 1.8.1 → 1.8.3

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/css/style.css CHANGED
@@ -1,4 +1,7 @@
1
1
  /* Nuvio for Tizen 3.0 — conservative CSS (no CSS grid gap edge cases, flexbox is fine on Chromium 47) */
2
+ /* Sentinel: app.js checks this element's width to confirm the stylesheet applied. */
3
+ #css-sentinel { width: 11px; }
4
+
2
5
  * { box-sizing: border-box; margin: 0; padding: 0; }
3
6
 
4
7
  html, body {
@@ -73,7 +76,8 @@ html, body {
73
76
  /* Filled selection for list rows / buttons */
74
77
  .pill.focusable.focused, .stream.focusable.focused, .ep.focusable.focused { background: #818cf8; color: #0e1117; }
75
78
  /* Keep secondary lines readable on the bright selected background */
76
- .stream.focusable.focused .s-sub, .ep.focusable.focused .ep-sub { color: #17203a; }
79
+ .stream.focusable.focused .s-sub, .ep.focusable.focused .ep-sub,
80
+ .ep.focusable.focused .ep-ov, .ep.focusable.focused .ep-rating { color: #17203a; }
77
81
 
78
82
  .gear.focusable.focused { opacity: 1; color: #818cf8; }
79
83
 
@@ -86,12 +90,23 @@ html, body {
86
90
  .detail-desc { font-size: 22px; line-height: 1.45; color: #cfd3dc; max-height: 200px; overflow: hidden; margin-bottom: 26px; }
87
91
  .btn-row { margin-top: 10px; }
88
92
  .pill { display: inline-block; padding: 14px 30px; margin-right: 16px; border-radius: 30px; background: #1a1f2b; font-size: 22px; border: 4px solid transparent; }
93
+ /* selected season: grey with light text; focus must still win (higher specificity below) */
94
+ .season-bar .pill.season-sel { background: #2a3346; color: #c7cbd4; }
95
+ .season-bar .pill.focusable.focused { background: #818cf8; color: #0e1117; }
89
96
 
90
97
  /* Episode + stream lists */
91
98
  .list-panel { position: absolute; top: 640px; left: 60px; right: 60px; bottom: 40px; overflow: hidden; }
92
99
  .season-bar { margin-bottom: 14px; white-space: nowrap; }
93
100
  .ep, .stream { display: block; padding: 16px 22px; margin-bottom: 10px; background: #161b26; border-radius: 8px; font-size: 22px; border: 4px solid transparent; white-space: normal; }
94
101
  .ep .ep-sub, .stream .s-sub { display: block; font-size: 17px; color: #9aa2b1; margin-top: 4px; }
102
+
103
+ /* episode rows with thumbnail + rating */
104
+ .ep-row { display: flex; align-items: flex-start; padding: 12px 16px; }
105
+ .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
+ .ep-body { flex: 1; min-width: 0; }
107
+ .ep-line { font-size: 22px; }
108
+ .ep-rating { color: #ffd257; font-size: 19px; }
109
+ .ep-ov { display: block; font-size: 17px; color: #9aa2b1; margin-top: 6px; max-height: 44px; overflow: hidden; }
95
110
  .list-scroll { position: absolute; top: 0; left: 0; right: 0; bottom: 0; overflow: hidden; }
96
111
 
97
112
  /* Settings */
package/index.html CHANGED
@@ -4,9 +4,11 @@
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.1">
7
+ <link rel="stylesheet" href="css/style.css?v=1.8.3">
8
8
  </head>
9
9
  <body>
10
+ <!-- CSS-applied sentinel (kept off-screen); app.js waits for its width before rendering -->
11
+ <div id="css-sentinel" style="position:absolute;left:-9999px;top:0;height:1px;overflow:hidden;"></div>
10
12
  <!-- AVPlay video plane (Tizen). Sits behind the UI; shown only during playback. -->
11
13
  <object type="application/avplayer" id="av-player"></object>
12
14
  <!-- HTML5 fallback for desktop/dev testing when webapis.avplay is absent -->
@@ -45,13 +47,13 @@
45
47
 
46
48
  <!-- Load order matters: polyfills -> util -> data -> input -> player -> views -> app -->
47
49
  <!-- ?v= busts the TV webview cache on every release (keep in sync with package.json) -->
48
- <script src="js/polyfills.js?v=1.8.1"></script>
49
- <script src="js/util.js?v=1.8.1"></script>
50
- <script src="js/stremio.js?v=1.8.1"></script>
51
- <script src="js/keys.js?v=1.8.1"></script>
52
- <script src="js/focus.js?v=1.8.1"></script>
53
- <script src="js/player.js?v=1.8.1"></script>
54
- <script src="js/views.js?v=1.8.1"></script>
55
- <script src="js/app.js?v=1.8.1"></script>
50
+ <script src="js/polyfills.js?v=1.8.3"></script>
51
+ <script src="js/util.js?v=1.8.3"></script>
52
+ <script src="js/stremio.js?v=1.8.3"></script>
53
+ <script src="js/keys.js?v=1.8.3"></script>
54
+ <script src="js/focus.js?v=1.8.3"></script>
55
+ <script src="js/player.js?v=1.8.3"></script>
56
+ <script src="js/views.js?v=1.8.3"></script>
57
+ <script src="js/app.js?v=1.8.3"></script>
56
58
  </body>
57
59
  </html>
package/js/app.js CHANGED
@@ -49,7 +49,12 @@ var App = (function () {
49
49
  var f = _stack.pop();
50
50
  if (!f) { tryExitApp(); return; }
51
51
  showScreen(f.screen);
52
- Focus.setScope(screenEl(f.screen), f.focus);
52
+ // Refresh home when returning if Continue Watching / poster / addons changed.
53
+ if (f.screen === 'home' && Views.homeDirty()) {
54
+ Views.renderHome();
55
+ } else {
56
+ Focus.setScope(screenEl(f.screen), f.focus);
57
+ }
53
58
  }
54
59
 
55
60
  function tryExitApp() {
@@ -86,7 +91,7 @@ var App = (function () {
86
91
  }
87
92
  }
88
93
 
89
- function start() {
94
+ function actualStart() {
90
95
  Player.init();
91
96
  Keys.init();
92
97
  Keys.setHandler(onKey);
@@ -94,6 +99,30 @@ var App = (function () {
94
99
  Views.renderHome();
95
100
  }
96
101
 
102
+ // On a cold launch the stylesheet fetch can race/fail, leaving the app unstyled.
103
+ // Confirm the CSS applied (sentinel width) before rendering; re-request it if not.
104
+ function start() {
105
+ function styled() {
106
+ var s = document.getElementById('css-sentinel');
107
+ return s && s.offsetWidth === 11;
108
+ }
109
+ function reloadCss() {
110
+ var l = document.createElement('link');
111
+ l.rel = 'stylesheet';
112
+ l.href = 'css/style.css?rl=' + Date.now();
113
+ document.head.appendChild(l);
114
+ }
115
+ if (styled()) { actualStart(); return; }
116
+ var tries = 0;
117
+ reloadCss();
118
+ var iv = setInterval(function () {
119
+ tries++;
120
+ if (styled()) { clearInterval(iv); actualStart(); return; }
121
+ if (tries % 8 === 0) { reloadCss(); } // re-attempt every ~2s
122
+ if (tries > 60) { clearInterval(iv); actualStart(); } // ~15s: render anyway
123
+ }, 250);
124
+ }
125
+
97
126
  return {
98
127
  start: start, back: back,
99
128
  openDetail: openDetail, openSettings: openSettings, openSearch: openSearch,
package/js/stremio.js CHANGED
@@ -251,6 +251,56 @@ var Stremio = (function () {
251
251
  });
252
252
  }
253
253
 
254
+ // ---- TMDB / MDBList (uses keys from Integrations) ----
255
+ var TMDB_IMG = 'https://image.tmdb.org/t/p/';
256
+ var _tmdbIdCache = {};
257
+ var _tmdbSeasonCache = {};
258
+
259
+ function tmdbKey() { return getIntegrations().tmdb; }
260
+ function tmdbImg(path, size) { return path ? (TMDB_IMG + (size || 'w300') + path) : ''; }
261
+
262
+ function tmdbValidate(key) {
263
+ if (!key) { return Promise.resolve(false); }
264
+ return U.getJSON('https://api.themoviedb.org/3/configuration?api_key=' + encodeURIComponent(key), 8000)
265
+ .then(function () { return true; }, function () { return false; });
266
+ }
267
+ function mdblistValidate(key) {
268
+ if (!key) { return Promise.resolve(false); }
269
+ return U.getJSON('https://api.mdblist.com/user?apikey=' + encodeURIComponent(key), 8000)
270
+ .then(function (r) { return !!(r && !r.error); }, function () { return false; });
271
+ }
272
+
273
+ // imdb id (tt..) -> TMDB tv id, cached.
274
+ function tmdbFindTvId(imdbId) {
275
+ var key = tmdbKey();
276
+ if (!key || !imdbId) { return Promise.resolve(null); }
277
+ if (_tmdbIdCache[imdbId] !== undefined) { return Promise.resolve(_tmdbIdCache[imdbId]); }
278
+ return U.getJSON('https://api.themoviedb.org/3/find/' + encodeURIComponent(imdbId) +
279
+ '?api_key=' + encodeURIComponent(key) + '&external_source=imdb_id', 9000)
280
+ .then(function (r) {
281
+ var id = (r && r.tv_results && r.tv_results[0]) ? r.tv_results[0].id : null;
282
+ _tmdbIdCache[imdbId] = id; return id;
283
+ }, function () { _tmdbIdCache[imdbId] = null; return null; });
284
+ }
285
+
286
+ // One request per season → { episodeNumber: {still, rating, overview, name} }.
287
+ function tmdbSeasonEpisodes(tvId, season) {
288
+ var key = tmdbKey();
289
+ if (!key || !tvId) { return Promise.resolve(null); }
290
+ var ck = tvId + ':' + season;
291
+ if (_tmdbSeasonCache[ck] !== undefined) { return Promise.resolve(_tmdbSeasonCache[ck]); }
292
+ return U.getJSON('https://api.themoviedb.org/3/tv/' + tvId + '/season/' + season +
293
+ '?api_key=' + encodeURIComponent(key), 12000)
294
+ .then(function (r) {
295
+ var map = {};
296
+ ((r && r.episodes) || []).forEach(function (ep) {
297
+ map[ep.episode_number] = { still: tmdbImg(ep.still_path, 'w300'),
298
+ rating: ep.vote_average, overview: ep.overview, name: ep.name };
299
+ });
300
+ _tmdbSeasonCache[ck] = map; return map;
301
+ }, function () { _tmdbSeasonCache[ck] = null; return null; });
302
+ }
303
+
254
304
  // ---- Skip segments (TheIntroDB) ----
255
305
  // GET https://api.theintrodb.org/v3/media?imdb_id=tt..[&season=&episode=][&duration_ms=]
256
306
  // Returns { intro, recap, credits, preview } arrays of {start,end} in seconds (null = open).
@@ -366,6 +416,8 @@ var Stremio = (function () {
366
416
  getAddonUrls: getAddonUrls, addAddon: addAddon, removeAddon: removeAddon, resetAddons: resetAddons,
367
417
  getIntegrations: getIntegrations, setIntegration: setIntegration, getPrefs: getPrefs, setPref: setPref,
368
418
  getIntroSegments: getIntroSegments,
419
+ tmdbValidate: tmdbValidate, mdblistValidate: mdblistValidate,
420
+ tmdbFindTvId: tmdbFindTvId, tmdbSeasonEpisodes: tmdbSeasonEpisodes, tmdbImg: tmdbImg,
369
421
  loadAddons: loadAddons, listCatalogs: listCatalogs, getCatalog: getCatalog,
370
422
  getMeta: getMeta, getStreams: getStreams, getSubtitles: getSubtitles,
371
423
  getSearchCatalogs: getSearchCatalogs, search: search,
package/js/views.js CHANGED
@@ -102,72 +102,94 @@ var Views = (function () {
102
102
  }
103
103
 
104
104
  // ================= HOME =================
105
+ var _catalogCache = null; // [{name,type,metas}] — avoids refetch on re-render
106
+ var _homeDirty = false; // set when Continue Watching / poster / addons changed
107
+
108
+ function markHomeDirty() { _homeDirty = true; }
109
+ function homeDirty() { return _homeDirty; }
110
+ function clearCatalogCache() { _catalogCache = null; _homeDirty = true; }
111
+
112
+ function focusHome(s) {
113
+ var firstCard = s.querySelector('.rows .card.focusable');
114
+ Focus.setScope(s, firstCard || undefined);
115
+ }
116
+
117
+ function continueCard(e) {
118
+ var c = posterCard({ id: e.id.split(':')[0], type: e.type, name: e.name, poster: e.poster }, e.type);
119
+ // For a series, keep the full episode id so detail opens on the right season/episode.
120
+ c.__onselect = function () {
121
+ App.openDetail(e.type, e.id.split(':')[0],
122
+ { name: e.name, poster: e.poster, type: e.type },
123
+ e.type === 'series' ? e.id : null);
124
+ };
125
+ c.__onlongpress = function () {
126
+ confirm('Remove “' + (e.name || 'this') + '” from Continue Watching?', 'Remove', function () {
127
+ Player.removeProgress(e.type, e.id);
128
+ U.toast('Removed from Continue Watching');
129
+ renderHome();
130
+ });
131
+ };
132
+ if (e.duration) {
133
+ var p = U.el('div', 'prog');
134
+ p.style.width = Math.min(100, (e.position / e.duration * 100)) + '%';
135
+ c.appendChild(p);
136
+ }
137
+ return c;
138
+ }
139
+
105
140
  function renderHome() {
141
+ _homeDirty = false;
106
142
  var s = scr('home');
107
143
  U.clear(s);
108
144
  s.appendChild(header(true, true));
109
145
  var rows = U.el('div', 'rows');
110
146
  s.appendChild(rows);
111
147
 
112
- // Continue watching
148
+ // Continue watching (always fresh — it's local)
113
149
  var cont = Player.continueList();
114
150
  if (cont.length) {
115
- rows.appendChild(buildRow('Continue Watching · hold OK to remove', cont.map(function (e) {
116
- var c = posterCard({ id: e.id.split(':')[0], type: e.type, name: e.name, poster: e.poster }, e.type);
117
- // For a series, keep the full episode id so detail opens on the right
118
- // season and focuses the exact episode being resumed.
119
- c.__onselect = function () {
120
- App.openDetail(e.type, e.id.split(':')[0],
121
- { name: e.name, poster: e.poster, type: e.type },
122
- e.type === 'series' ? e.id : null);
123
- };
124
- // Long-press (hold OK) removes the item from Continue Watching.
125
- c.__onlongpress = function () {
126
- confirm('Remove “' + (e.name || 'this') + '” from Continue Watching?', 'Remove', function () {
127
- Player.removeProgress(e.type, e.id);
128
- U.toast('Removed from Continue Watching');
129
- renderHome();
130
- });
131
- };
132
- if (e.duration) {
133
- var p = U.el('div', 'prog');
134
- p.style.width = Math.min(100, (e.position / e.duration * 100)) + '%';
135
- c.appendChild(p);
136
- }
137
- return c;
138
- })));
151
+ rows.appendChild(buildRow('Continue Watching · hold OK to remove', cont.map(continueCard)));
152
+ }
153
+
154
+ // Catalog rows: use cache for an instant re-render; fetch only when cold.
155
+ if (_catalogCache) {
156
+ _catalogCache.forEach(function (c) {
157
+ var row = buildRow(c.name, c.metas.map(function (m) { return posterCard(m, c.type); }));
158
+ row.setAttribute('data-filled', '1');
159
+ rows.appendChild(row);
160
+ });
161
+ focusHome(s);
162
+ return;
139
163
  }
140
164
 
141
165
  U.spinner(true);
142
166
  Stremio.listCatalogs().then(function (cats) {
143
167
  var pick = cats.slice(0, 10);
144
- // Render placeholder rows in order, fill as data arrives.
145
- var rowNodes = pick.map(function (cat) {
146
- var placeholder = buildRow(cat.name, []);
147
- rows.appendChild(placeholder);
148
- return placeholder;
149
- });
150
- Focus.setScope(s);
168
+ var rowNodes = pick.map(function (cat) { var ph = buildRow(cat.name, []); rows.appendChild(ph); return ph; });
169
+ focusHome(s);
151
170
  U.spinner(false);
152
171
 
172
+ var cache = pick.map(function (cat) { return { name: cat.name, type: cat.type, metas: [] }; });
153
173
  var tasks = pick.map(function (cat, i) {
154
174
  return function () {
155
175
  return Stremio.getCatalog(cat).then(function (metas) {
156
- fillRow(rowNodes[i], metas.slice(0, 24), cat.type);
176
+ var slice = metas.slice(0, 24);
177
+ cache[i].metas = slice;
178
+ fillRow(rowNodes[i], slice, cat.type);
157
179
  });
158
180
  };
159
181
  });
160
182
  U.settleAll(tasks, 3).then(function () {
161
- // Remove empty rows, refresh focus scope.
162
183
  for (var i = rowNodes.length - 1; i >= 0; i--) {
163
184
  if (!rowNodes[i].getAttribute('data-filled')) { rows.removeChild(rowNodes[i]); }
164
185
  }
165
- if (!Focus.current()) { Focus.setScope(s); }
186
+ _catalogCache = cache.filter(function (c) { return c.metas && c.metas.length; });
187
+ if (!Focus.current()) { focusHome(s); }
166
188
  });
167
189
  }, function (err) {
168
190
  U.spinner(false);
169
191
  U.toast('Failed to load addons. Check network / Settings.');
170
- Focus.setScope(s);
192
+ focusHome(s);
171
193
  });
172
194
  }
173
195
 
@@ -287,8 +309,7 @@ var Views = (function () {
287
309
  var panel = U.el('div', 'list-panel');
288
310
  var bar = U.el('div', 'season-bar');
289
311
  ss.forEach(function (sn) {
290
- var p = U.el('div', 'pill focusable', 'Season ' + sn);
291
- if (sn === _d.season) { p.style.background = '#2a3346'; }
312
+ var p = U.el('div', 'pill focusable' + (sn === _d.season ? ' season-sel' : ''), 'Season ' + sn);
292
313
  p.__onselect = function () { _d.season = sn; drawDetail(); };
293
314
  bar.appendChild(p);
294
315
  });
@@ -300,26 +321,58 @@ var Views = (function () {
300
321
  listScroll.style.height = '340px';
301
322
  var eps = meta.videos.filter(function (v) { return (v.season === undefined ? 1 : v.season) === _d.season; });
302
323
  eps.sort(function (a, b) { return (a.episode || 0) - (b.episode || 0); });
324
+ var epRefs = [];
303
325
  eps.forEach(function (v) {
304
326
  var watched = Player.isWatched('series', v.id);
305
- var e = U.el('div', 'ep focusable' + (watched ? ' watched' : ''));
306
- e.appendChild(document.createTextNode((watched ? '✓ ' : '') + 'E' + (v.episode || '?') + ' ' + (v.title || v.name || '')));
307
- if (v.overview) { e.appendChild(U.el('span', 'ep-sub', v.overview)); }
327
+ var e = U.el('div', 'ep ep-row focusable' + (watched ? ' watched' : ''));
328
+
329
+ var thumb = U.el('div', 'ep-thumb');
330
+ if (v.thumbnail) { thumb.style.backgroundImage = 'url("' + v.thumbnail + '")'; }
331
+ e.appendChild(thumb);
332
+
333
+ var body = U.el('div', 'ep-body');
334
+ var line = U.el('div', 'ep-line');
335
+ line.appendChild(document.createTextNode((watched ? '✓ ' : '') + 'E' + (v.episode || '?') + ' ' + (v.title || v.name || '')));
336
+ var rating = U.el('span', 'ep-rating', '');
337
+ line.appendChild(rating);
338
+ body.appendChild(line);
339
+ var ov = U.el('span', 'ep-ov', v.overview || '');
340
+ body.appendChild(ov);
341
+ e.appendChild(body);
342
+
308
343
  e.__onselect = function () {
309
344
  openStreams(v.id, _d.meta.name + ' S' + _d.season + 'E' + (v.episode || ''));
310
345
  };
311
- // Long-press toggles watched/unwatched.
312
346
  e.__onlongpress = function () {
313
347
  var now = !Player.isWatched('series', v.id);
314
348
  Player.setWatched('series', v.id, now);
315
349
  U.toast(now ? 'Marked watched' : 'Marked unwatched');
316
- _d.target = v.id; // keep focus on this episode after re-render
350
+ _d.target = v.id;
317
351
  drawDetail();
318
352
  };
319
353
  if (_d.target && v.id === _d.target) { _d._epFocus = e; }
320
354
  listScroll.appendChild(e);
355
+ epRefs.push({ num: v.episode, thumb: thumb, rating: rating, ov: ov });
321
356
  });
322
357
  panel.appendChild(listScroll);
358
+
359
+ // Enrich with TMDB episode images + ratings (one request per season) if a key is set.
360
+ if (Stremio.getIntegrations().tmdb) {
361
+ var seasonAtFetch = _d.season;
362
+ Stremio.tmdbFindTvId(_d.id).then(function (tvId) {
363
+ if (!tvId || _d.season !== seasonAtFetch) { return; }
364
+ return Stremio.tmdbSeasonEpisodes(tvId, seasonAtFetch).then(function (map) {
365
+ if (!map || _d.season !== seasonAtFetch) { return; }
366
+ epRefs.forEach(function (r) {
367
+ var ep = map[r.num];
368
+ if (!ep) { return; }
369
+ if (ep.still && !r.thumb.style.backgroundImage) { r.thumb.style.backgroundImage = 'url("' + ep.still + '")'; }
370
+ if (ep.rating) { r.rating.textContent = ' ★ ' + (Math.round(ep.rating * 10) / 10); }
371
+ if (ep.overview && !r.ov.textContent) { r.ov.textContent = ep.overview; }
372
+ });
373
+ });
374
+ });
375
+ }
323
376
  return panel;
324
377
  }
325
378
 
@@ -420,6 +473,7 @@ var Views = (function () {
420
473
  }
421
474
 
422
475
  function startPlayback(stream, videoId, label) {
476
+ markHomeDirty(); // progress/watched will change → refresh Continue Watching on return
423
477
  var ctx = { url: stream.url, type: _d.type, id: videoId, name: label,
424
478
  poster: _d.meta.poster, streams: playableSources() };
425
479
  if (_d.type === 'series' && _d.meta.videos) {
@@ -480,8 +534,9 @@ var Views = (function () {
480
534
  input.className = 'focusable';
481
535
  input.placeholder = 'Search movies & series…';
482
536
  input.__onselect = function () { input.focus(); };
537
+ // Enter (or the Search button) runs the search. No onchange — it fires on blur
538
+ // when moving to a result and would re-run the search and steal focus.
483
539
  input.onkeydown = function (ev) { if (ev.keyCode === 13) { if (ev.stopPropagation) { ev.stopPropagation(); } runSearch(input.value); } };
484
- input.onchange = function () { runSearch(input.value); };
485
540
  bar.appendChild(input);
486
541
 
487
542
  var go = U.el('div', 'pill focusable search-go', 'Search');
@@ -554,6 +609,18 @@ var Views = (function () {
554
609
  confirmBtn.__onselect = doConfirm;
555
610
  cancelBtn.__onselect = function () { App.back(); };
556
611
  row.appendChild(confirmBtn);
612
+ if (cfg.validator) {
613
+ var valBtn = U.el('div', 'pill focusable', 'Validate');
614
+ valBtn.__onselect = function () {
615
+ var v = ('' + input.value).trim();
616
+ if (!v) { U.toast('Enter a key first'); return; }
617
+ U.toast('Validating…', 4000);
618
+ cfg.validator(v).then(function (ok) {
619
+ U.toast(ok ? '✓ Key is valid' : '✗ Key rejected (or unreachable)', 3500);
620
+ });
621
+ };
622
+ row.appendChild(valBtn);
623
+ }
557
624
  row.appendChild(cancelBtn);
558
625
  wrap.appendChild(row);
559
626
 
@@ -656,6 +723,7 @@ var Views = (function () {
656
723
  item.__onselect = function () {
657
724
  confirm('Remove "' + name + '"?', 'Remove', function () {
658
725
  Stremio.removeAddon(url);
726
+ clearCatalogCache();
659
727
  U.toast('Removed');
660
728
  setTimeout(renderSettings, 300);
661
729
  });
@@ -669,6 +737,7 @@ var Views = (function () {
669
737
  reset.__onselect = function () {
670
738
  confirm('Reset to default addons? This removes all addons you’ve added.', 'Reset', function () {
671
739
  Stremio.resetAddons();
740
+ clearCatalogCache();
672
741
  U.toast('Addons reset to default');
673
742
  setTimeout(renderSettings, 300);
674
743
  });
@@ -684,7 +753,7 @@ var Views = (function () {
684
753
  hint: 'Type a Stremio addon manifest URL, then Confirm. RETURN cancels — nothing is saved unless you confirm.',
685
754
  value: 'https://', placeholder: 'https://addon.example/manifest.json',
686
755
  validate: function (v) { return (!v || v === 'https://' || v.indexOf('://') === -1 || v.length < 12) ? 'Enter a full manifest URL' : null; },
687
- onConfirm: function (v) { Stremio.addAddon(v); U.toast('Addon added'); App.back(); renderSettings(); }
756
+ onConfirm: function (v) { Stremio.addAddon(v); clearCatalogCache(); U.toast('Addon added'); App.back(); renderSettings(); }
688
757
  });
689
758
  };
690
759
  wrap.appendChild(addRow);
@@ -697,7 +766,7 @@ var Views = (function () {
697
766
 
698
767
  function maskKey(v) { v = '' + v; return v.length <= 4 ? '••••' : ('••••' + v.slice(-4)); }
699
768
 
700
- function keyRow(title, key, placeholder) {
769
+ function keyRow(title, key, placeholder, validator) {
701
770
  var val = Stremio.getIntegrations()[key] || '';
702
771
  var r = U.el('div', 'addon-item focusable');
703
772
  r.appendChild(U.el('div', 'a-name', title));
@@ -705,8 +774,8 @@ var Views = (function () {
705
774
  r.__onselect = function () {
706
775
  App.openEntry({
707
776
  title: title,
708
- hint: 'Paste your key, then Confirm. Leave blank and Confirm to clear it.',
709
- value: val, placeholder: placeholder,
777
+ hint: 'Paste your key, then Confirm. Use Validate to test it. Leave blank and Confirm to clear.',
778
+ value: val, placeholder: placeholder, validator: validator,
710
779
  onConfirm: function (v) { Stremio.setIntegration(key, v); U.toast(v ? 'Saved' : 'Cleared'); App.back(); renderSettings(); }
711
780
  });
712
781
  };
@@ -715,9 +784,9 @@ var Views = (function () {
715
784
 
716
785
  function settingsIntegrations(wrap) {
717
786
  wrap.appendChild(U.el('h2', null, 'Integrations'));
718
- wrap.appendChild(keyRow('TMDB API key (v3)', 'tmdb', 'TMDB v3 API key'));
719
- wrap.appendChild(keyRow('MDBList API key', 'mdblist', 'MDBList API key'));
720
- wrap.appendChild(U.el('div', 'hint', 'Keys are stored on this TV only. RETURN goes back.'));
787
+ wrap.appendChild(keyRow('TMDB API key (v3)', 'tmdb', 'TMDB v3 API key', Stremio.tmdbValidate));
788
+ wrap.appendChild(keyRow('MDBList API key', 'mdblist', 'MDBList API key', Stremio.mdblistValidate));
789
+ wrap.appendChild(U.el('div', 'hint', 'Keys are stored on this TV only. TMDB enables episode images & ratings. RETURN goes back.'));
721
790
  Focus.setScope(scr('settings'));
722
791
  }
723
792
 
@@ -741,6 +810,7 @@ var Views = (function () {
741
810
  row.appendChild(U.el('div', 'a-url', 'Press OK to change'));
742
811
  row.__onselect = function () {
743
812
  Stremio.setPref('posterShape', nextVal(POSTER_SHAPES, Stremio.getPrefs().posterShape));
813
+ markHomeDirty(); // home reflects the new shape on return
744
814
  render();
745
815
  };
746
816
  wrap.appendChild(row);
@@ -752,6 +822,7 @@ var Views = (function () {
752
822
  renderHome: renderHome, renderDetail: renderDetail, renderSettings: renderSettings,
753
823
  settingsRoot: settingsRoot, backInSettings: backInSettings,
754
824
  renderSearch: renderSearch, renderEntry: renderEntry, backInDetail: backInDetail,
825
+ homeDirty: homeDirty,
755
826
  isModalOpen: function () { return _modalOpen; }, closeModal: closeModal
756
827
  };
757
828
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuvio-tizen",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "Nuvio-style Stremio-addon streaming client for Tizen 3.0+ Samsung TVs (TizenBrew app module).",
5
5
  "packageType": "app",
6
6
  "appName": "Nuvio",