release(mobile): Release v0.2.2

release(mobile): Release v0.2.2
This commit is contained in:
DIYgod 2025-06-29 10:29:40 +08:00 committed by GitHub
commit 7c56fc0410
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 71 additions and 55 deletions

View File

@ -0,0 +1,24 @@
# What's New in v0.2.2
## Shiny New Things
- Copy AI summaries with one tap
- Use video duration as an Action condition
## Improvements
- Top indicator now fades smoothly when switching views (fa78bad)
- Gradually rolling out an experimental unified local database for mobile and desktop (#3897 #3902)
- Improved image preview
- Refined Android dropdown-menu styling
- Fixed star/unstar status not syncing across devices (fbd0b3)
## No Longer Broken
- Fixed login failures caused by corrupted cache in some cases (2bbe512)
- Fixed inability to follow sources in preview mode (2c04919)
- Fixed profile-picture upload not working
## Thanks
Special thanks to volunteer contributors @kovsu @huanfe1 @cscnk52 @Olexandr88 @0-o0 @kingsword09 @ericyzhu for their valuable contributions

View File

@ -1,26 +1,11 @@
# What's New in vNEXT_VERSION
## ⚠️ Important
Weve made some updates to our `user` database to help keep things running smoothly and securely. Some fields—like your name, email, profile image link, handle, bio, and website—now have maximum character limits:
- Name: up to 64 characters
- Email: up to 64 characters
- Profile image link: up to 256 characters
- Handle: up to 36 characters
- Bio: up to 256 characters
- Website: up to 256 characters
If you previously entered information thats longer than these limits, it will be automatically shortened to fit. Other fields, like your email verification status, two-factor authentication, and social links, are not affected by these changes.
## Shiny new things
- 🎉 Say hello to a snazzy new look for your personal profile! We've sprinkled in some cool social attribute settings to spice things up. But hold onto your hats—this is just the appetizer for our grand social vision! 🚀 Stay tuned for more! 😎
## Improvements
- 🌟 Buckle up, folks! Our AI summarization just got a dazzling new animation makeover—smoother, flashier, and ready to impress! Plus, weve cranked up the performance so it runs like a dream. Get ready to be wowed! 😎✨
## No longer broken
🎉 Ta-da! Weve squashed a bunch of pesky bugs you awesome folks in the community pointed out—high fives all around! 🙌 But if somethings still acting wonky, dont be shy—holler at us with an issue report, pretty please! 😜 Lets keep the good vibes rolling! 🚀
## Thanks
Special thanks to volunteer contributors @ for their valuable contributions

View File

@ -5,3 +5,7 @@
## Improvements
## No longer broken
## Thanks
Special thanks to volunteer contributors @ for their valuable contributions

View File

@ -32,7 +32,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>0.2.1</string>
<string>0.2.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -52,7 +52,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>109</string>
<string>110</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>

View File

@ -1,6 +1,6 @@
{
"name": "@follow/mobile",
"version": "0.2.1",
"version": "0.2.2",
"private": true,
"main": "src/main.tsx",
"scripts": {

View File

@ -1,4 +1,5 @@
import { useTypeScriptHappyCallback } from "@follow/hooks"
import { cn } from "@follow/utils"
import { useSetAtom, useStore } from "jotai"
import type { PropsWithChildren } from "react"
import { use, useImperativeHandle, useLayoutEffect, useRef, useState } from "react"
@ -134,7 +135,7 @@ export const SafeNavigationScrollView = ({
{...props}
>
<View style={{ height: headerHeight - (withTopInset ? insets.top : 0) }} />
<View style={contentViewStyle} className={contentViewClassName}>
<View style={contentViewStyle} className={cn("flex-1", contentViewClassName)}>
{children}
</View>
{ScrollViewBottom}

View File

@ -29,6 +29,7 @@ interface GroupedInsetListCardProps {
}
interface BaseCellClassNames {
className?: string
leftClassName?: string
rightClassName?: string
}
@ -146,11 +147,11 @@ export const GroupedInsetListNavigationLink: FC<
disabled?: boolean
postfix?: React.ReactNode
} & BaseCellClassNames
> = ({ label, icon, onPress, disabled, leftClassName, rightClassName, postfix }) => {
> = ({ label, icon, onPress, disabled, className, leftClassName, rightClassName, postfix }) => {
const rightIconColor = useColor("tertiaryLabel")
return (
<Pressable onPress={onPress} disabled={disabled}>
<Pressable onPress={onPress} disabled={disabled} className={className}>
{({ pressed }) => (
<GroupedInsetListBaseCell
className={cn(pressed ? "bg-system-fill" : undefined, disabled && "opacity-40")}
@ -197,10 +198,10 @@ export const GroupedInsetListCell: FC<
icon?: SFSymbol
onPress?: () => void
} & BaseCellClassNames
> = ({ label, description, children, leftClassName, rightClassName, icon, onPress }) => {
> = ({ label, description, children, className, leftClassName, rightClassName, icon, onPress }) => {
return (
<GroupedInsetListBaseCell
className="bg-secondary-system-grouped-background flex-1"
className={cn("bg-secondary-system-grouped-background flex flex-1", className)}
as={onPress ? TouchableOpacity : undefined}
{...(onPress ? { onPress } : {})}
>

View File

@ -12,7 +12,7 @@ import { proxyEnv } from "./proxy-env"
export const apiFetch = ofetch.create({
retry: false,
credentials: "omit",
baseURL: proxyEnv.API_URL,
onRequest: async (ctx) => {
const { options, request } = ctx

View File

@ -1,16 +1,12 @@
import { useViewWithSubscription } from "@follow/store/subscription/hooks"
import { useUnreadByView } from "@follow/store/unread/hooks"
import { cn } from "@follow/utils"
import * as React from "react"
import { useEffect } from "react"
import { useTranslation } from "react-i18next"
import type { StyleProp, ViewStyle } from "react-native"
import { ScrollView, Text, useWindowDimensions, View } from "react-native"
import Animated, {
interpolate,
interpolateColor,
useAnimatedStyle,
useSharedValue,
} from "react-native-reanimated"
import Animated, { interpolate, interpolateColor, useAnimatedStyle } from "react-native-reanimated"
import { ReAnimatedPressable } from "@/src/components/common/AnimatedComponents"
import { TIMELINE_VIEW_SELECTOR_HEIGHT } from "@/src/constants/ui"
@ -28,7 +24,7 @@ import { TimelineViewSelectorContextMenu } from "./TimelineViewSelectorContextMe
const ACTIVE_WIDTH = 180
const INACTIVE_WIDTH = 48
const ACTIVE_TEXT_WIDTH = 85
const ACTIVE_TEXT_WIDTH = 95
export function TimelineViewSelector() {
const activeViews = useViewWithSubscription()
@ -69,15 +65,16 @@ function ItemWrapper({
index,
activeColor,
children,
isActive,
onPress,
style,
className,
}: {
children: React.ReactNode
index: number
isActive: boolean
activeColor: string
onPress: () => void
className?: string
style?: Exclude<StyleProp<ViewStyle>, number>
}) {
const { width: windowWidth } = useWindowDimensions()
@ -88,13 +85,14 @@ function ItemWrapper({
windowWidth - (INACTIVE_WIDTH + 12) * (activeViews.length - 1) - 8 * 2,
ACTIVE_WIDTH,
)
const textWidth = useSharedValue(0)
const bgColor = useColor("gray5")
return (
<ReAnimatedPressable
className="relative flex h-12 flex-row items-center justify-center gap-2 overflow-hidden rounded-[1.2rem] pl-2"
className={cn(
"relative flex h-12 flex-row items-center justify-center gap-2 overflow-hidden rounded-[1.2rem] pl-2",
className,
)}
onPress={onPress}
style={useAnimatedStyle(() => ({
backgroundColor: interpolateColor(
@ -105,22 +103,13 @@ function ItemWrapper({
width: interpolate(
dragProgress.get(),
[index - 1, index, index + 1],
[INACTIVE_WIDTH, Math.max(activeWidth, textWidth.value + INACTIVE_WIDTH), INACTIVE_WIDTH],
[INACTIVE_WIDTH, Math.max(activeWidth, INACTIVE_WIDTH), INACTIVE_WIDTH],
"clamp",
),
...style,
}))}
>
<View
className="flex-row items-center gap-2"
onLayout={({ nativeEvent }) => {
if (isActive) {
textWidth.set(nativeEvent.layout.width)
}
}}
>
{children}
</View>
{children}
</ReAnimatedPressable>
)
}
@ -192,7 +181,7 @@ function ViewItem({
</View>
<Animated.View
className="flex flex-row items-center gap-2 overflow-hidden"
className="flex flex-row items-center justify-center gap-2"
style={useAnimatedStyle(() => ({
width: interpolate(
dragProgress.get(),
@ -202,21 +191,28 @@ function ViewItem({
),
}))}
>
<Text key={view.name} className="text-sm font-semibold text-white" numberOfLines={1}>
<Text
key={view.name}
className="text-sm font-semibold text-white"
numberOfLines={1}
ellipsizeMode="clip"
>
{t(view.name)}
</Text>
<UnreadCount
max={99}
unread={unreadCount}
dotClassName="size-1.5 rounded-full bg-white"
textClassName="text-white font-bold"
textClassName="text-white font-bold flex-1"
/>
</Animated.View>
{/* Unread indicator for inactive items */}
<Animated.View
className="absolute -top-0.5 left-5 size-2 rounded-full border"
className="absolute size-2 rounded-full border"
style={useAnimatedStyle(() => ({
left: 30,
top: 10,
backgroundColor: textColor,
borderColor,
display: unreadCount ? "flex" : "none",

View File

@ -22,7 +22,12 @@ export function UnreadCount({
if (!unread) return null
return showUnreadCount ? (
<Text className={cn("text-tertiary-label text-xs", className, textClassName)} {...rest}>
<Text
numberOfLines={1}
ellipsizeMode="clip"
className={cn("text-tertiary-label text-xs", className, textClassName)}
{...rest}
>
{unread > max ? `${max}+` : unread}
</Text>
) : (

View File

@ -606,7 +606,7 @@ class EntrySyncServices {
cookie: options.cookie,
}
: undefined,
credentials: options?.cookie ? undefined : "include",
credentials: options?.cookie ? "omit" : "include",
body: JSON.stringify({
ids: nextIds,
}),