Skip to content

Commit

Permalink
Merge branch 'dev' into feat/mobile-masonry-grid
Browse files Browse the repository at this point in the history
  • Loading branch information
hyoban authored Jan 22, 2025
2 parents 40c2fbe + 6fae018 commit cb34669
Show file tree
Hide file tree
Showing 31 changed files with 741 additions and 202 deletions.
6 changes: 6 additions & 0 deletions apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
favicon: iconPath,
},
plugins: [
[
"expo-document-picker",
{
iCloudContainerEnvironment: "Production",
},
],
"expo-localization",
[
"expo-router",
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"expo-clipboard": "~7.0.0",
"expo-constants": "~17.0.4",
"expo-dev-client": "^5.0.9",
"expo-document-picker": "~13.0.2",
"expo-file-system": "~18.0.6",
"expo-font": "~13.0.1",
"expo-haptics": "~14.0.0",
Expand Down Expand Up @@ -88,6 +89,7 @@
"react-native-uikit-colors": "0.1.1",
"react-native-web": "~0.19.13",
"react-native-webview": "13.12.5",
"shiki": "1.24.1",
"swiftui-react-native": "6.3.3",
"tailwindcss": "3.4.16",
"usehooks-ts": "3.1.0",
Expand Down
18 changes: 18 additions & 0 deletions apps/mobile/src/atoms/settings/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { DataSettings } from "@/src/interfaces/settings/data"

import { createSettingAtom } from "./internal/helper"

export const createDefaultSettings = (): DataSettings => ({
sendAnonymousData: true,
})

export const {
useSettingKey: useDataSettingKey,
useSettingSelector: useDataSettingSelector,
useSettingKeys: useDataSettingKeys,
setSetting: setDataSetting,
clearSettings: clearDataSettings,
initializeDefaultSettings: initializeDefaultDataSettings,
getSettings: getDataSettings,
useSettingValue: useDataSettingValue,
} = createSettingAtom("data", createDefaultSettings)
36 changes: 36 additions & 0 deletions apps/mobile/src/atoms/settings/ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { UISettings } from "@/src/interfaces/settings/ui"

import { createSettingAtom } from "./internal/helper"

export const createDefaultSettings = (): UISettings => ({
// Subscription

hideExtraBadge: false,

subscriptionShowUnreadCount: true,
thumbnailRatio: "square",

// Content
readerRenderInlineStyle: false,
codeHighlightThemeLight: "github-light",
codeHighlightThemeDark: "github-dark",
guessCodeLanguage: true,
hideRecentReader: false,
customCSS: "",

// View

pictureViewFilterNoImage: false,
})

export const {
useSettingKey: useUISettingKey,
useSettingSelector: useUISettingSelector,
useSettingKeys: useUISettingKeys,
setSetting: setUISetting,
clearSettings: clearUISettings,
initializeDefaultSettings: initializeDefaultUISettings,
getSettings: getUISettings,
useSettingValue: useUISettingValue,
settingAtom: __uiSettingAtom,
} = createSettingAtom("ui", createDefaultSettings)
1 change: 1 addition & 0 deletions apps/mobile/src/components/ui/dropdown/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function DropdownMenu<T>({
: options.map((option) => ({
title: option.label,
selected: option.value === currentValue,
disabled: option.value === currentValue,
}))
}
onPress={(e) => {
Expand Down
50 changes: 40 additions & 10 deletions apps/mobile/src/components/ui/grouped/GroupedList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,23 @@ export const GroupedInsetListNavigationLink: FC<{
label: string
icon?: React.ReactNode
onPress: () => void
}> = ({ label, icon, onPress }) => {
const tertiaryLabelColor = useColor("tertiaryLabel")
disabled?: boolean
}> = ({ label, icon, onPress, disabled }) => {
const rightIconColor = useColor("tertiaryLabel")

return (
<Pressable onPress={onPress}>
<Pressable onPress={onPress} disabled={disabled}>
{({ pressed }) => (
<GroupedInsetListBaseCell className={pressed ? "bg-system-fill" : undefined}>
<View className={"flex-1 flex-row items-center"}>
<GroupedInsetListBaseCell
className={cn(pressed ? "bg-system-fill" : undefined, disabled && "opacity-40")}
>
<View className={"flex-1 flex-row items-center justify-between"}>
<View className="flex-row items-center">
{icon}
<Text className="text-[16px]">{label}</Text>
<Text className={"text-label text-[16px]"}>{label}</Text>
</View>
<View className="-mr-2 ml-auto">
<RightCuteReIcon height={18} width={18} color={tertiaryLabelColor} />
<View className="-mr-2 ml-4">
<RightCuteReIcon height={18} width={18} color={rightIconColor} />
</View>
</View>
</GroupedInsetListBaseCell>
Expand Down Expand Up @@ -117,7 +120,7 @@ export const GroupedInsetListNavigationLinkIcon: FC<
export const GroupedInsetListCell: FC<{
label: string
description?: string
children: React.ReactNode
children?: React.ReactNode
}> = ({ label, description, children }) => {
return (
<GroupedInsetListBaseCell className="flex-1">
Expand All @@ -126,7 +129,34 @@ export const GroupedInsetListCell: FC<{
{!!description && <Text className="text-secondary-label text-sm">{description}</Text>}
</View>

<View className="ml-4 shrink-0">{children}</View>
<View className="mb-auto ml-4 shrink-0">{children}</View>
</GroupedInsetListBaseCell>
)
}

export const GroupedInsetListActionCell: FC<{
label: string
description?: string
onPress: () => void
disabled?: boolean
}> = ({ label, description, onPress, disabled }) => {
const rightIconColor = useColor("tertiaryLabel")
return (
<Pressable onPress={onPress} disabled={disabled}>
{({ pressed }) => (
<GroupedInsetListBaseCell
className={cn(pressed ? "bg-system-fill" : undefined, disabled && "opacity-40")}
>
<View className="flex-1">
<Text>{label}</Text>
{!!description && <Text className="text-secondary-label text-sm">{description}</Text>}
</View>

<View className="-mr-2 ml-4">
<RightCuteReIcon height={18} width={18} color={rightIconColor} />
</View>
</GroupedInsetListBaseCell>
)}
</Pressable>
)
}
26 changes: 26 additions & 0 deletions apps/mobile/src/icons/exit_cute_fi.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from "react"
import Svg, { Path } from "react-native-svg"

interface ExitCuteFiIconProps {
width?: number
height?: number
color?: string
}

export const ExitCuteFiIcon = ({
width = 24,
height = 24,
color = "#10161F",
}: ExitCuteFiIconProps) => {
return (
<Svg width={width} height={height} fill="none" viewBox="0 0 24 24">
<Path fill="#fff" fillOpacity={0.01} d="M24 0v24H0V0z" />
<Path
stroke={color}
strokeLinecap="round"
strokeWidth={3}
d="M12.5 12H20m-2.5-2.828a8.616 8.616 0 0 1 2.478 2.307.89.89 0 0 1 0 1.042A8.631 8.631 0 0 1 17.5 14.83M12.5 3.5c-3.44.002-5.21.053-6.328 1.171C5 5.843 5 7.73 5 11.5v1c0 3.771 0 5.657 1.172 6.828C7.29 20.447 9.06 20.498 12.5 20.5"
/>
</Svg>
)
}
3 changes: 3 additions & 0 deletions apps/mobile/src/interfaces/settings/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface DataSettings {
sendAnonymousData: boolean
}
17 changes: 17 additions & 0 deletions apps/mobile/src/interfaces/settings/ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface UISettings {
subscriptionShowUnreadCount: boolean
hideExtraBadge: boolean
thumbnailRatio: "square" | "original"

// Content
readerRenderInlineStyle: boolean
codeHighlightThemeLight: string
codeHighlightThemeDark: string
guessCodeLanguage: boolean
hideRecentReader: boolean
customCSS: string

// view

pictureViewFilterNoImage: boolean
}
25 changes: 23 additions & 2 deletions apps/mobile/src/lib/api-fetch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-console */
import type { AppType } from "@follow/shared"
import { router } from "expo-router"
import { ofetch } from "ofetch"
import { FetchError, ofetch } from "ofetch"

import { getCookie } from "./auth"
import { getApiUrl } from "./env"
Expand All @@ -12,11 +12,16 @@ export const apiFetch = ofetch.create({
retry: false,

baseURL: getApiUrl(),
onRequest: async ({ options, request }) => {
onRequest: async (ctx) => {
const { options, request } = ctx
if (__DEV__) {
// Logger
console.log(`---> ${options.method} ${request as string}`)
}

// add cookie
options.headers = options.headers || new Headers()
options.headers.set("cookie", getCookie())
},
onRequestError: ({ error, request, options }) => {
if (__DEV__) {
Expand Down Expand Up @@ -54,3 +59,19 @@ export const apiClient = hc<AppType>(getApiUrl(), {
}
},
})

export const getBizFetchErrorMessage = (error: unknown) => {
if (error instanceof FetchError && error.response) {
try {
const data = JSON.parse(error.response._data)

if (data.message && data.code) {
// TODO i18n handle by code
return data.message
}
} catch {
return ""
}
}
return ""
}
44 changes: 20 additions & 24 deletions apps/mobile/src/modules/feed-drawer/collection-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { cn } from "@follow/utils"
import {
Image,
ScrollView,
StyleSheet,
TouchableOpacity,
useWindowDimensions,
View,
} from "react-native"
import { Image, ScrollView, StyleSheet, TouchableOpacity, View } from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"

import { FallbackIcon } from "@/src/components/ui/icon/fallback-icon"
import { Logo } from "@/src/components/ui/logo"
import type { ViewDefinition } from "@/src/constants/views"
import { views } from "@/src/constants/views"
import { useList } from "@/src/store/list/hooks"
Expand All @@ -18,23 +12,26 @@ import { useAllListSubscription } from "@/src/store/subscription/hooks"
import { selectCollection, useSelectedCollection } from "./atoms"

export const CollectionPanel = () => {
const winDim = useWindowDimensions()
const lists = useAllListSubscription()

const insets = useSafeAreaInsets()
return (
<View
className="bg-tertiary-system-background"
style={{ width: Math.max(50, winDim.width * 0.15) }}
className="bg-quaternary-system-fill dark:bg-tertiary-system-background"
style={{ width: 65 }}
>
<ScrollView
contentContainerClassName="flex py-3 gap-3"
contentContainerStyle={{ paddingTop: insets.top, paddingBottom: insets.bottom }}
contentContainerClassName="flex gap-4 px-3.5"
contentContainerStyle={{ paddingTop: insets.top + 10, paddingBottom: insets.bottom }}
>
<View className="flex-1 items-center">
<Logo width={37} height={37} color="#222" />
</View>
<View style={styles.hairline} className="bg-opaque-separator mx-1" />
{views.map((viewDef) => (
<ViewButton key={viewDef.name} viewDef={viewDef} />
))}
<View style={styles.hairline} className="bg-opaque-separator mx-4" />
<View style={styles.hairline} className="bg-opaque-separator mx-1" />
{lists.map((listId) => (
<ListButton key={listId} listId={listId} />
))}
Expand All @@ -56,7 +53,7 @@ const ViewButton = ({ viewDef }: { viewDef: ViewDefinition }) => {
return (
<TouchableOpacity
className={cn(
"mx-3 flex aspect-square items-center justify-center rounded-lg p-3",
"flex aspect-square items-center justify-center rounded-full p-3",
isActive ? "bg-secondary-system-fill" : "bg-system-background",
)}
onPress={() =>
Expand All @@ -65,8 +62,9 @@ const ViewButton = ({ viewDef }: { viewDef: ViewDefinition }) => {
viewId: viewDef.view,
})
}
style={{ backgroundColor: viewDef.activeColor }}
>
<viewDef.icon key={viewDef.name} color={viewDef.activeColor} />
<viewDef.icon key={viewDef.name} color={"#fff"} />
</TouchableOpacity>
)
}
Expand All @@ -80,7 +78,7 @@ const ListButton = ({ listId }: { listId: string }) => {
return (
<TouchableOpacity
className={cn(
"mx-3 flex aspect-square items-center justify-center rounded-lg p-3",
"flex aspect-square items-center justify-center overflow-hidden rounded-full p-3",
isActive ? "bg-system-fill" : "bg-system-background",
)}
onPress={() =>
Expand All @@ -90,13 +88,11 @@ const ListButton = ({ listId }: { listId: string }) => {
})
}
>
<View className="overflow-hidden rounded">
{list.image ? (
<Image source={{ uri: list.image, width: 24, height: 24 }} resizeMode="cover" />
) : (
<FallbackIcon title={list.title} size={24} />
)}
</View>
{list.image ? (
<Image source={{ uri: list.image, width: 41, height: 41 }} resizeMode="cover" />
) : (
<FallbackIcon title={list.title} size={41} />
)}
</TouchableOpacity>
)
}
2 changes: 1 addition & 1 deletion apps/mobile/src/modules/feed-drawer/drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const FeedDrawer = ({ children }: PropsWithChildren) => {
return (
<Drawer
open={isDrawerOpen}
drawerStyle={{ width: Math.min(400, winDim.width * 0.85) }}
drawerStyle={{ width: winDim.width }}
onOpen={openDrawer}
onClose={closeDrawer}
renderDrawerContent={renderDrawerContent}
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/modules/login/email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export function EmailLogin() {
<ReAnimatedPressable
disabled={submitMutation.isPending || !formState.isValid}
onPress={login}
className="mt-8 h-10 flex-row items-center justify-center rounded-lg"
className="mt-8 h-10 flex-row items-center justify-center rounded-3xl"
style={buttonStyle}
>
{submitMutation.isPending ? (
Expand Down
Loading

0 comments on commit cb34669

Please sign in to comment.