Merge pull request #1234 from Materialious/update/1.12.3

Update/1.12.3
This commit is contained in:
Ward
2025-12-08 10:18:23 +00:00
committed by GitHub
18 changed files with 174 additions and 40 deletions
+1
View File
@@ -0,0 +1 @@
words = ["favourite"]
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 174
versionName "1.12.2"
versionCode 175
versionName "1.12.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -71,7 +71,11 @@
<release version="1.12.2" date="2025-12-08">
<release version="1.12.3" date="2025-12-08">
<url>https://github.com/Materialious/Materialious/releases/tag/1.12.3</url>
</release>
<release version="1.12.2" date="2025-12-08">
<url>https://github.com/Materialious/Materialious/releases/tag/1.12.2</url>
</release>
<release version="1.12.1" date="2025-12-08">
+12 -2
View File
@@ -1,16 +1,17 @@
{
"name": "Materialious",
"version": "1.12.0",
"version": "1.12.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.12.0",
"version": "1.12.3",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
"bgutils-js": "^3.2.0",
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.9/capacitor-nodejs.tgz",
"chokidar": "~5.0.0",
"electron-is-dev": "~2.0.0",
"electron-serve": "~3.0.0",
@@ -1885,6 +1886,15 @@
"node": ">= 0.4"
}
},
"node_modules/capacitor-nodejs": {
"version": "1.0.0-beta.9",
"resolved": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.9/capacitor-nodejs.tgz",
"integrity": "sha512-4EU/QBolfuyHiMtwd1nxZ4wsK243tGE4VttKvVBG+x1yQuK9EIlEf3lbRjFXfjrJMTZNCqaP6glmdfj9Zo7KVg==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=7.0.0"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.12.2",
"version": "1.12.3",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
@@ -24,6 +24,7 @@
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
"bgutils-js": "^3.2.0",
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.9/capacitor-nodejs.tgz",
"chokidar": "~5.0.0",
"electron-is-dev": "~2.0.0",
"electron-serve": "~3.0.0",
@@ -43,4 +44,4 @@
"capacitor",
"electron"
]
}
}
@@ -1,4 +1,6 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const CapacitorNodejs = require('../../../node_modules/capacitor-nodejs/electron/dist/plugin.js');
module.exports = {
CapacitorNodejs,
}
+9 -2
View File
@@ -1,12 +1,12 @@
{
"name": "materialious",
"version": "1.12.0",
"version": "1.12.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "materialious",
"version": "1.12.0",
"version": "1.12.3",
"hasInstallScript": true,
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
@@ -25,6 +25,7 @@
"bgutils-js": "^3.2.0",
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.9/capacitor-nodejs.tgz",
"dayjs": "^1.11.19",
"dexie": "^4.2.1",
"fuse.js": "^7.0.0",
"googlevideo": "^4.0.4",
"he": "^1.2.0",
@@ -5669,6 +5670,12 @@
"integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==",
"license": "MIT"
},
"node_modules/dexie": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/dexie/-/dexie-4.2.1.tgz",
"integrity": "sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==",
"license": "Apache-2.0"
},
"node_modules/dezalgo": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.12.2",
"version": "1.12.3",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -57,6 +57,7 @@
"bgutils-js": "^3.2.0",
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.9/capacitor-nodejs.tgz",
"dayjs": "^1.11.19",
"dexie": "^4.2.1",
"fuse.js": "^7.0.0",
"googlevideo": "^4.0.4",
"he": "^1.2.0",
@@ -73,4 +74,4 @@
"svelte-infinite-loading": "^1.4.0",
"youtubei.js": "^16.0.1"
}
}
}
+6
View File
@@ -23,6 +23,9 @@ export interface VideoBase {
lengthSeconds: number;
viewCountText: string;
viewCount?: number;
// Means it was given preference in rankings due to
// a users settings
promotedBy?: 'favourited';
}
export interface ResolvedUrl {
@@ -205,6 +208,9 @@ export interface PlaylistPageVideo extends Omit<PlaylistVideo, 'type'> {
indexId: string;
authorId: string;
viewCount: number;
// Means it was given preference in rankings due to
// a users settings
promotedBy?: 'favourited';
}
export interface ChannelContentVideos {
@@ -229,6 +229,10 @@
<p>{video.author}</p>
{/if}
{#if video.promotedBy === 'favourited'}
<i>star</i>
{/if}
{#if !('publishedText' in video) && 'viewCountText' in video}
{video.viewCountText ?? cleanNumber(video.viewCount ?? 0)}
@@ -6,6 +6,8 @@
import { truncate } from '$lib/misc';
import { authStore, interfaceLowBandwidthMode, isAndroidTvStore } from '$lib/store';
import { _ } from '$lib/i18n';
import { localDb } from '$lib/dexie';
import { onMount } from 'svelte';
let {
video,
@@ -13,6 +15,16 @@
hideSubscribe = false
}: { video: VideoPlay; subscribed?: boolean; hideSubscribe?: boolean } = $props();
let favoritedChannel = $state(false);
const favoriteChannelItem = localDb.favouriteChannels.filter((item) => {
return item.channelId === video.authorId;
});
onMount(async () => {
favoritedChannel = (await favoriteChannelItem.count()) > 0;
});
async function toggleSubscribed() {
if (subscribed) {
await deleteUnsubscribe(video.authorId);
@@ -22,6 +34,16 @@
subscribed = !subscribed;
}
async function toggleFavourited() {
if (favoritedChannel) {
await favoriteChannelItem.delete();
favoritedChannel = false;
} else {
await localDb.favouriteChannels.add({ channelId: video.authorId, created: new Date() });
favoritedChannel = true;
}
}
</script>
<nav>
@@ -56,6 +78,18 @@
{$_('unsubscribe')}
{/if}
</button>
{#if window.indexedDB}
<button
class:inverse-surface={!favoritedChannel}
class:border={favoritedChannel}
onclick={toggleFavourited}
>
<i>star</i>
<div class="tooltip">
{favoritedChannel ? $_('unfavouriteChannel') : $_('favouriteChannel')}
</div>
</button>
{/if}
{:else}
<button class="inverse-surface" disabled>
{$_('subscribe')}
+20
View File
@@ -0,0 +1,20 @@
import type { Table } from 'dexie';
import Dexie from 'dexie';
export interface FavouriteChannels {
channelId: string;
created: Date;
}
export class MaterialiousDb extends Dexie {
favouriteChannels!: Table<FavouriteChannels>;
constructor() {
super('materialious');
this.version(1).stores({
favouriteChannels: 'channelId, created'
});
}
}
export const localDb = new MaterialiousDb();
+3 -1
View File
@@ -200,5 +200,7 @@
"titleOnly": "Title only"
}
},
"subscribe": "Subscribe"
"subscribe": "Subscribe",
"favouriteChannel": "Favourite channel",
"unfavouriteChannel": "Unfavourite channel"
}
@@ -4,6 +4,8 @@
import { feedCacheStore } from '$lib/store';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
import ItemsList from '$lib/components/ItemsList.svelte';
import { resolve } from '$app/paths';
import { _ } from '$lib/i18n';
let currentPage = 1;
let videos: (VideoBase | Video | PlaylistPageVideo)[] = $state($feedCacheStore.subscription);
@@ -21,6 +23,13 @@
}
</script>
<nav class="right-align">
<a class="button outline" href={resolve('/subscriptions/manage', {})}>
{$_('subscriptions.manageSubscriptions')}
</a>
</nav>
<div class="space"></div>
<ItemsList items={videos ?? []} />
<InfiniteLoading on:infinite={loadMore} />
@@ -1,10 +1,39 @@
import { getFeed } from '$lib/api/index';
import type { PlaylistPageVideo, Video, VideoBase } from '$lib/api/model';
import { localDb } from '$lib/dexie';
import { excludeDuplicateFeeds } from '$lib/misc';
import { feedCacheStore } from '$lib/store';
import { error } from '@sveltejs/kit';
import { get } from 'svelte/store';
type supportedVideos = (VideoBase | Video | PlaylistPageVideo)[];
async function sortVideosByFavourites(videos: supportedVideos): Promise<supportedVideos> {
if (!window.indexedDB) return videos;
const favouritedChannels = (await localDb.favouriteChannels.toArray()).map(
(channel) => channel.channelId
);
if (favouritedChannels.length === 0) {
return videos;
}
const regularVideos: supportedVideos = [];
const favouriteVideos: supportedVideos = [];
videos.forEach((video) => {
if (favouritedChannels.includes(video.authorId)) {
video.promotedBy = 'favourited';
favouriteVideos.push(video);
} else {
regularVideos.push(video);
}
});
return [...favouriteVideos, ...regularVideos];
}
export async function load() {
let videos = get(feedCacheStore).subscription;
@@ -16,12 +45,16 @@ export async function load() {
error(500, errorMessage);
}
videos = [...feeds.notifications, ...feeds.videos];
videos = await sortVideosByFavourites([...feeds.notifications, ...feeds.videos]);
feedCacheStore.set({ ...get(feedCacheStore), subscription: videos });
} else {
await getFeed(100, 1).then((feeds) => {
const newVideos = [...feeds.notifications, ...feeds.videos, ...videos];
await getFeed(100, 1).then(async (feeds) => {
const newVideos = await sortVideosByFavourites([
...feeds.notifications,
...feeds.videos,
...videos
]);
feedCacheStore.set({
...get(feedCacheStore),
subscription: excludeDuplicateFeeds(videos, newVideos) as (
@@ -34,31 +34,31 @@
}
</script>
<div class="space"></div>
<div class="padding">
<div class="max field suffix prefix small no-margin surface-variant">
<i class="front">search</i><input
bind:value={search}
oninput={searchSubs}
type="text"
placeholder={$_('searchPlaceholder')}
/>
</div>
<div class="max field suffix prefix small no-margin surface-variant">
<i class="front">search</i><input
bind:value={search}
oninput={searchSubs}
type="text"
placeholder={$_('searchPlaceholder')}
/>
{#each subscriptions as sub (sub.authorId)}
<article>
<nav>
<a href={resolve(`/channel/[authorId]`, { authorId: sub.authorId })}
><h6>
{sub.author}
</h6></a
>
<div class="max"></div>
<button onclick={async () => unsubscribe(sub.authorId)} class="border">Unsubscribe</button>
</nav>
</article>
{/each}
</div>
{#each subscriptions as sub (sub.authorId)}
<article>
<nav>
<a href={resolve(`/channel/[authorId]`, { authorId: sub.authorId })}
><h6>
{sub.author}
</h6></a
>
<div class="max"></div>
<button onclick={async () => unsubscribe(sub.authorId)} class="border">Unsubscribe</button>
</nav>
</article>
{/each}
<style>
@media screen and (max-width: 590px) {
nav {
@@ -251,7 +251,9 @@
playerCurrentTime = playerElement.currentTime;
});
}
}
onMount(async () => {
if (data.video.premiereTimestamp) {
premiereTime = humanFriendlyTimestamp(data.video.premiereTimestamp);
premiereUpdateInterval = setInterval(async () => {
@@ -265,9 +267,7 @@
}
}, 60000);
}
}
onMount(async () => {
// Required due to needing the playerElement
if ($playerState?.playerElement) {
await load($playerState);
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.12.2"
LATEST_VERSION = "1.12.3"
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")