diff --git a/apps/mobile/drizzle/0016_curious_carnage.sql b/apps/mobile/drizzle/0016_curious_carnage.sql new file mode 100644 index 000000000..4d2b92c97 --- /dev/null +++ b/apps/mobile/drizzle/0016_curious_carnage.sql @@ -0,0 +1 @@ +ALTER TABLE `entries` ADD `settings` text; \ No newline at end of file diff --git a/apps/mobile/drizzle/meta/0016_snapshot.json b/apps/mobile/drizzle/meta/0016_snapshot.json new file mode 100644 index 000000000..e8a191420 --- /dev/null +++ b/apps/mobile/drizzle/meta/0016_snapshot.json @@ -0,0 +1,629 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "44da86ee-5ce1-49e9-8f77-992f3fc23a16", + "prevId": "1c7389f2-ca2c-47be-bcd1-7b7dece9f457", + "tables": { + "collections": { + "name": "collections", + "columns": { + "feed_id": { + "name": "feed_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entry_id": { + "name": "entry_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "view": { + "name": "view", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "entries": { + "name": "entries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source_content": { + "name": "source_content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "guid": { + "name": "guid", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author_url": { + "name": "author_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author_avatar": { + "name": "author_avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inserted_at": { + "name": "inserted_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "media": { + "name": "media", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "categories": { + "name": "categories", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attachments": { + "name": "attachments", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "extra": { + "name": "extra", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "feed_id": { + "name": "feed_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inbox_handle": { + "name": "inbox_handle", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read": { + "name": "read", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sources": { + "name": "sources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feeds": { + "name": "feeds", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_at": { + "name": "error_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "site_url": { + "name": "site_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "images": { + "name": "images", + "columns": { + "url": { + "name": "url", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "colors": { + "name": "colors", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inboxes": { + "name": "inboxes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "lists": { + "name": "lists", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "feed_ids": { + "name": "feed_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "view": { + "name": "view", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fee": { + "name": "fee", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "subscriptions": { + "name": "subscriptions", + "columns": { + "feed_id": { + "name": "feed_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "list_id": { + "name": "list_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inbox_id": { + "name": "inbox_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "view": { + "name": "view", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "summaries": { + "name": "summaries", + "columns": { + "entry_id": { + "name": "entry_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "unq": { + "name": "unq", + "columns": ["entry_id", "language"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "unread": { + "name": "unread", + "columns": { + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_me": { + "name": "is_me", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/apps/mobile/drizzle/meta/_journal.json b/apps/mobile/drizzle/meta/_journal.json index f55998afd..fe747ed95 100644 --- a/apps/mobile/drizzle/meta/_journal.json +++ b/apps/mobile/drizzle/meta/_journal.json @@ -113,6 +113,13 @@ "when": 1741226954359, "tag": "0015_colorful_warbird", "breakpoints": true + }, + { + "idx": 16, + "version": "6", + "when": 1741572308405, + "tag": "0016_curious_carnage", + "breakpoints": true } ] } diff --git a/apps/mobile/drizzle/migrations.js b/apps/mobile/drizzle/migrations.js index 54a6f0442..2041ab2f0 100644 --- a/apps/mobile/drizzle/migrations.js +++ b/apps/mobile/drizzle/migrations.js @@ -16,6 +16,7 @@ import m0012 from "./0012_magenta_thing.sql" import m0013 from "./0013_chunky_stephen_strange.sql" import m0014 from "./0014_chemical_shocker.sql" import m0015 from "./0015_colorful_warbird.sql" +import m0016 from "./0016_curious_carnage.sql" import journal from "./meta/_journal.json" export default { @@ -37,5 +38,6 @@ export default { m0013, m0014, m0015, + m0016, }, } diff --git a/apps/mobile/src/database/schemas/index.ts b/apps/mobile/src/database/schemas/index.ts index 666deae27..2dd40847b 100644 --- a/apps/mobile/src/database/schemas/index.ts +++ b/apps/mobile/src/database/schemas/index.ts @@ -2,7 +2,13 @@ import type { FeedViewType } from "@follow/constants" import { sql } from "drizzle-orm" import { integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core" -import type { AttachmentsModel, ExtraModel, ImageColorsResult, MediaModel } from "./types" +import type { + ActionSettings, + AttachmentsModel, + ExtraModel, + ImageColorsResult, + MediaModel, +} from "./types" export const feedsTable = sqliteTable("feeds", { id: text("id").primaryKey(), @@ -85,6 +91,7 @@ export const entriesTable = sqliteTable("entries", { inboxHandle: text("inbox_handle"), read: integer("read", { mode: "boolean" }), sources: text("sources", { mode: "json" }).$type(), + settings: text("settings", { mode: "json" }).$type(), }) export const collectionsTable = sqliteTable("collections", { diff --git a/apps/mobile/src/database/schemas/types.ts b/apps/mobile/src/database/schemas/types.ts index cdc231696..690404746 100644 --- a/apps/mobile/src/database/schemas/types.ts +++ b/apps/mobile/src/database/schemas/types.ts @@ -1,3 +1,5 @@ +import type { HonoApiClient } from "@/src/morph/types" + import type { collectionsTable, entriesTable, @@ -31,6 +33,8 @@ export type SummarySchema = typeof summariesTable.$inferSelect export type ImageSchema = typeof imagesTable.$inferInsert +export type ActionSettings = HonoApiClient.ActionSettings + export type MediaModel = { url: string type: "photo" | "video" diff --git a/apps/mobile/src/modules/entry-content/EntryContentHeaderRightActions.tsx b/apps/mobile/src/modules/entry-content/EntryContentHeaderRightActions.tsx index 9cb87983c..c89bf5e72 100644 --- a/apps/mobile/src/modules/entry-content/EntryContentHeaderRightActions.tsx +++ b/apps/mobile/src/modules/entry-content/EntryContentHeaderRightActions.tsx @@ -93,7 +93,7 @@ const HeaderRightActionsImpl = ({ if (!entry) return const getCachedOrGenerateSummary = async () => { - const hasSummary = await summaryActions.getSummary(entryId) + const hasSummary = summaryActions.getSummary(entryId) if (hasSummary) return const hideGlowEffect = showIntelligenceGlowEffect() diff --git a/apps/mobile/src/morph/hono.ts b/apps/mobile/src/morph/hono.ts index 1622cd90e..e403cb0a1 100644 --- a/apps/mobile/src/morph/hono.ts +++ b/apps/mobile/src/morph/hono.ts @@ -128,6 +128,7 @@ class Morph { inboxHandle: item.feeds.type === "inbox" ? item.feeds.id : null, read: item.read, sources: "from" in item ? (item.from ?? null) : null, + settings: item.settings ?? null, }) } return entries @@ -179,10 +180,10 @@ class Morph { : null, language: data.entries.language, feedId: data.feeds.id, - // TODO: handle inboxHandle - inboxHandle: "", + inboxHandle: data.feeds.type === "inbox" ? data.feeds.id : null, read: false, sources: null, + settings: null, } } diff --git a/apps/mobile/src/morph/types.ts b/apps/mobile/src/morph/types.ts index b172a2d8b..e5c41e70b 100644 --- a/apps/mobile/src/morph/types.ts +++ b/apps/mobile/src/morph/types.ts @@ -14,4 +14,6 @@ export namespace HonoApiClient { export type Entry_Inbox_Get = ExtractData export type List_List_Get = ExtractData[number] export type Feed_Get = ExtractData + + export type ActionSettings = Exclude } diff --git a/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx b/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx index c761321c7..7f3a7d708 100644 --- a/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx +++ b/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx @@ -1,7 +1,7 @@ import { FeedViewType } from "@follow/constants" import { useLocalSearchParams } from "expo-router" import { atom, useAtomValue } from "jotai" -import { useMemo } from "react" +import { useEffect, useMemo } from "react" import { Pressable, Text, View } from "react-native" import Animated, { FadeIn, FadeOut } from "react-native-reanimated" import { useSafeAreaInsets } from "react-native-safe-area-context" @@ -14,8 +14,10 @@ import { openLink } from "@/src/lib/native" import { EntryContentContext, useEntryContentContext } from "@/src/modules/entry-content/ctx" import { EntryAISummary } from "@/src/modules/entry-content/EntryAISummary" import { useEntry, usePrefetchEntryContent } from "@/src/store/entry/hooks" +import { entrySyncServices } from "@/src/store/entry/store" import type { EntryModel } from "@/src/store/entry/types" import { useFeed } from "@/src/store/feed/hooks" +import { summarySyncService } from "@/src/store/summary/store" import { useAutoMarkAsRead } from "@/src/store/unread/hooks" import { EntrySocialTitle, EntryTitle } from "../../../../modules/entry-content/EntryTitle" @@ -34,12 +36,24 @@ export default function EntryDetailPage() { const insets = useSafeAreaInsets() const ctxValue = useMemo( () => ({ - showAISummaryAtom: atom(false), - showSourceAtom: atom(false), + showAISummaryAtom: atom(entry?.settings?.summary || false), + showSourceAtom: atom(entry?.settings?.readability || false), }), - [], + [entry?.settings?.readability, entry?.settings?.summary], ) + useEffect(() => { + if (entry?.settings?.readability) { + entrySyncServices.fetchEntrySourceContent(entryId) + } + }, [entry?.settings?.readability, entryId]) + + useEffect(() => { + if (entry?.settings?.summary) { + summarySyncService.generateSummary(entryId) + } + }, [entry?.settings?.summary, entryId]) + return ( diff --git a/apps/mobile/src/store/summary/store.ts b/apps/mobile/src/store/summary/store.ts index b8fad965f..5db003452 100644 --- a/apps/mobile/src/store/summary/store.ts +++ b/apps/mobile/src/store/summary/store.ts @@ -56,7 +56,7 @@ class SummaryActions { } } - async getSummary(entryId: string) { + getSummary(entryId: string) { const state = get() const summary = state.data[entryId]