release(mobile): Release v0.2.2
release(mobile): Release v0.2.2
This commit is contained in:
commit
7c56fc0410
|
|
@ -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
|
||||
|
|
@ -1,26 +1,11 @@
|
|||
# What's New in vNEXT_VERSION
|
||||
|
||||
## ⚠️ Important
|
||||
|
||||
We’ve 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 that’s 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, we’ve cranked up the performance so it runs like a dream. Get ready to be wowed! 😎✨
|
||||
|
||||
## No longer broken
|
||||
|
||||
🎉 Ta-da! We’ve squashed a bunch of pesky bugs you awesome folks in the community pointed out—high fives all around! 🙌 But if something’s still acting wonky, don’t be shy—holler at us with an issue report, pretty please! 😜 Let’s keep the good vibes rolling! 🚀
|
||||
## Thanks
|
||||
|
||||
Special thanks to volunteer contributors @ for their valuable contributions
|
||||
|
|
|
|||
|
|
@ -5,3 +5,7 @@
|
|||
## Improvements
|
||||
|
||||
## No longer broken
|
||||
|
||||
## Thanks
|
||||
|
||||
Special thanks to volunteer contributors @ for their valuable contributions
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@follow/mobile",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"main": "src/main.tsx",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 } : {})}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
|
|
|
|||
Loading…
Reference in New Issue