feat(mobile): perform actions (#3012)

* feat(mobile): perform actions

* update
This commit is contained in:
Stephen Zhou 2025-03-10 10:52:02 +08:00 committed by GitHub
parent b5272910e2
commit 3e313cac8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 676 additions and 9 deletions

View File

@ -0,0 +1 @@
ALTER TABLE `entries` ADD `settings` text;

View File

@ -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": {}
}
}

View File

@ -113,6 +113,13 @@
"when": 1741226954359,
"tag": "0015_colorful_warbird",
"breakpoints": true
},
{
"idx": 16,
"version": "6",
"when": 1741572308405,
"tag": "0016_curious_carnage",
"breakpoints": true
}
]
}

View File

@ -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,
},
}

View File

@ -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<string[]>(),
settings: text("settings", { mode: "json" }).$type<ActionSettings>(),
})
export const collectionsTable = sqliteTable("collections", {

View File

@ -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"

View File

@ -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()

View File

@ -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,
}
}

View File

@ -14,4 +14,6 @@ export namespace HonoApiClient {
export type Entry_Inbox_Get = ExtractData<typeof apiClient.entries.inbox.$get>
export type List_List_Get = ExtractData<typeof apiClient.lists.list.$get>[number]
export type Feed_Get = ExtractData<typeof apiClient.feeds.$get>
export type ActionSettings = Exclude<Entry_Post[number]["settings"], undefined>
}

View File

@ -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 (
<EntryContentContext.Provider value={ctxValue}>
<PortalHost>

View File

@ -56,7 +56,7 @@ class SummaryActions {
}
}
async getSummary(entryId: string) {
getSummary(entryId: string) {
const state = get()
const summary = state.data[entryId]