Feat: tailing skeletons (#1064)
* tailing loading covers * fix type issues * fixed compatibility issues * show skeleton even if row complete * cleanup * replaced !! with Boolean() * Update type names Co-authored-by: nyagami <hoangquan05112002@gmail.com> * updated type occurances * fixed key issue * deleted unused vars and comments --------- Co-authored-by: nyagami <hoangquan05112002@gmail.com>
This commit is contained in:
@@ -6,10 +6,11 @@ import { coverPlaceholderColor } from '../theme/colors';
|
||||
|
||||
import color from 'color';
|
||||
import { ThemeColors } from '@theme/types';
|
||||
import { NovelInfo } from '@database/types';
|
||||
import { NovelItem } from '@plugins/types';
|
||||
import { LibraryNovelInfo } from '@database/types';
|
||||
|
||||
interface ListViewProps {
|
||||
item: NovelInfo;
|
||||
item: NovelItem | LibraryNovelInfo;
|
||||
downloadBadge?: React.ReactNode;
|
||||
unreadBadge?: React.ReactNode;
|
||||
inLibraryBadge?: React.ReactNode;
|
||||
|
||||
+115
-70
@@ -14,12 +14,13 @@ import ListView from './ListView';
|
||||
import { useDeviceOrientation } from '@hooks';
|
||||
import { coverPlaceholderColor } from '../theme/colors';
|
||||
import { DisplayModes } from '@screens/library/constants/constants';
|
||||
import { LibraryNovelInfo, NovelInfo } from '@database/types';
|
||||
import { LibraryNovelInfo } from '@database/types';
|
||||
import { NovelItem } from '@plugins/types';
|
||||
import { ThemeColors } from '@theme/types';
|
||||
import { useLibrarySettings } from '@hooks/persisted';
|
||||
import { getUserAgent } from '@hooks/persisted/useUserAgent';
|
||||
import { getString } from '@strings/translations';
|
||||
import SourceScreenSkeletonLoading from '@screens/browse/loadingAnimation/SourceScreenSkeletonLoading';
|
||||
|
||||
interface UnreadBadgeProps {
|
||||
chaptersDownloaded: number;
|
||||
@@ -35,23 +36,37 @@ interface DownloadBadgeProps {
|
||||
theme: ThemeColors;
|
||||
}
|
||||
|
||||
function NovelCover<TNovel extends NovelItem | NovelInfo | LibraryNovelInfo>({
|
||||
item,
|
||||
onPress,
|
||||
libraryStatus,
|
||||
theme,
|
||||
isSelected,
|
||||
onLongPress,
|
||||
selectedNovelIds,
|
||||
}: {
|
||||
type CoverItemLibrary =
|
||||
| LibraryNovelInfo & {
|
||||
completeRow?: number;
|
||||
};
|
||||
|
||||
type CoverItemPlugin =
|
||||
| NovelItem & {
|
||||
completeRow?: number;
|
||||
};
|
||||
|
||||
interface INovelCover<TNovel> {
|
||||
item: TNovel;
|
||||
onPress: () => void;
|
||||
libraryStatus: boolean;
|
||||
theme: ThemeColors;
|
||||
isSelected: boolean;
|
||||
addSkeletonLoading: boolean;
|
||||
onLongPress: (item: TNovel) => void;
|
||||
selectedNovelIds: number[];
|
||||
}) {
|
||||
}
|
||||
|
||||
function NovelCover<TNovel extends CoverItemLibrary | CoverItemPlugin>({
|
||||
item,
|
||||
onPress,
|
||||
libraryStatus,
|
||||
theme,
|
||||
isSelected,
|
||||
addSkeletonLoading,
|
||||
onLongPress,
|
||||
selectedNovelIds,
|
||||
}: INovelCover<TNovel>) {
|
||||
const {
|
||||
displayMode = DisplayModes.Comfortable,
|
||||
showDownloadBadges = true,
|
||||
@@ -70,25 +85,35 @@ function NovelCover<TNovel extends NovelItem | NovelInfo | LibraryNovelInfo>({
|
||||
|
||||
const coverHeight = useMemo(
|
||||
() => (window.width / numColumns) * (4 / 3),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[numColumns],
|
||||
);
|
||||
|
||||
const selectNovel = () => onLongPress(item);
|
||||
|
||||
const uri = item.cover;
|
||||
if (item.completeRow) {
|
||||
if (!addSkeletonLoading) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<SourceScreenSkeletonLoading
|
||||
theme={theme}
|
||||
completeRow={item.completeRow}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return displayMode !== DisplayModes.List ? (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
flex: 1 / numColumns,
|
||||
borderRadius: 6,
|
||||
overflow: 'hidden',
|
||||
margin: 2,
|
||||
},
|
||||
styles.standardNovelCover,
|
||||
isSelected && {
|
||||
backgroundColor: theme.primary,
|
||||
opacity: 0.8,
|
||||
},
|
||||
backgroundColor: theme.primary,
|
||||
} &&
|
||||
styles.selectedNovelCover,
|
||||
]}
|
||||
>
|
||||
<Pressable
|
||||
@@ -101,34 +126,36 @@ function NovelCover<TNovel extends NovelItem | NovelInfo | LibraryNovelInfo>({
|
||||
}
|
||||
onLongPress={selectNovel}
|
||||
>
|
||||
<View style={styles.badgeContainer}>
|
||||
{libraryStatus && <InLibraryBadge theme={theme} />}
|
||||
{showDownloadBadges && item.chaptersDownloaded > 0 ? (
|
||||
<DownloadBadge
|
||||
showUnreadBadges={showUnreadBadges}
|
||||
chaptersDownloaded={item.chaptersDownloaded}
|
||||
chaptersUnread={item.chaptersUnread}
|
||||
theme={theme}
|
||||
/>
|
||||
) : null}
|
||||
{showUnreadBadges && item.chaptersUnread > 0 ? (
|
||||
<UnreadBadge
|
||||
theme={theme}
|
||||
chaptersDownloaded={item.chaptersDownloaded}
|
||||
chaptersUnread={item.chaptersUnread}
|
||||
showDownloadBadges={showDownloadBadges}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
{item.id ? (
|
||||
<View style={styles.badgeContainer}>
|
||||
{libraryStatus && <InLibraryBadge theme={theme} />}
|
||||
{showDownloadBadges && item.chaptersDownloaded > 0 ? (
|
||||
<DownloadBadge
|
||||
showUnreadBadges={showUnreadBadges}
|
||||
chaptersDownloaded={item.chaptersDownloaded}
|
||||
chaptersUnread={item.chaptersUnread}
|
||||
theme={theme}
|
||||
/>
|
||||
) : null}
|
||||
{showUnreadBadges && item.chaptersUnread > 0 ? (
|
||||
<UnreadBadge
|
||||
theme={theme}
|
||||
chaptersDownloaded={item.chaptersDownloaded}
|
||||
chaptersUnread={item.chaptersUnread}
|
||||
showDownloadBadges={showDownloadBadges}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
<Image
|
||||
source={{ uri, headers: { 'User-Agent': getUserAgent() } }}
|
||||
style={[
|
||||
{
|
||||
height: coverHeight,
|
||||
borderRadius: 4,
|
||||
backgroundColor: coverPlaceholderColor,
|
||||
},
|
||||
libraryStatus && { opacity: 0.5 },
|
||||
styles.standardBorderRadius,
|
||||
libraryStatus && styles.opacityPoint5,
|
||||
]}
|
||||
/>
|
||||
<View style={styles.compactTitleContainer}>
|
||||
@@ -145,7 +172,7 @@ function NovelCover<TNovel extends NovelItem | NovelInfo | LibraryNovelInfo>({
|
||||
<ListView
|
||||
item={item}
|
||||
downloadBadge={
|
||||
showDownloadBadges && item.chaptersDownloaded ? (
|
||||
showDownloadBadges && item.id && item.chaptersDownloaded ? (
|
||||
<DownloadBadge
|
||||
theme={theme}
|
||||
showUnreadBadges={showUnreadBadges}
|
||||
@@ -155,7 +182,7 @@ function NovelCover<TNovel extends NovelItem | NovelInfo | LibraryNovelInfo>({
|
||||
) : null
|
||||
}
|
||||
unreadBadge={
|
||||
showUnreadBadges && item.chaptersUnread ? (
|
||||
showUnreadBadges && item.id && item.chaptersUnread ? (
|
||||
<UnreadBadge
|
||||
theme={theme}
|
||||
chaptersDownloaded={item.chaptersDownloaded}
|
||||
@@ -188,9 +215,9 @@ const ComfortableTitle = ({
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
styles.padding4,
|
||||
{
|
||||
color: theme.onSurface,
|
||||
padding: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -204,18 +231,7 @@ const CompactTitle = ({ novelName }: { novelName: string }) => (
|
||||
colors={['transparent', 'rgba(0,0,0,0.7)']}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color: 'rgba(255,255,255,1)',
|
||||
textShadowColor: 'rgba(0, 0, 0, 0.75)',
|
||||
textShadowOffset: { width: -1, height: 1 },
|
||||
textShadowRadius: 10,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text numberOfLines={2} style={[styles.title, styles.compactTitle]}>
|
||||
{novelName}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
@@ -229,15 +245,27 @@ const InLibraryBadge = ({ theme }: { theme: ThemeColors }) => (
|
||||
{
|
||||
backgroundColor: theme.primary,
|
||||
color: theme.onPrimary,
|
||||
borderRadius: 4,
|
||||
},
|
||||
styles.standardBorderRadius,
|
||||
]}
|
||||
>
|
||||
{getString('novelScreen.inLibaray')}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const UnreadBadge = ({
|
||||
interface BadgeProps {
|
||||
chaptersDownloaded: number;
|
||||
chaptersUnread: number;
|
||||
theme: ThemeColors;
|
||||
}
|
||||
interface UnreadBadgeProps extends BadgeProps {
|
||||
showDownloadBadges: boolean;
|
||||
}
|
||||
interface DownloadBadgeProps extends BadgeProps {
|
||||
showUnreadBadges: boolean;
|
||||
}
|
||||
|
||||
const UnreadBadge: React.FC<UnreadBadgeProps> = ({
|
||||
chaptersDownloaded,
|
||||
chaptersUnread,
|
||||
showDownloadBadges,
|
||||
@@ -246,13 +274,8 @@ const UnreadBadge = ({
|
||||
<Text
|
||||
style={[
|
||||
styles.unreadBadge,
|
||||
!chaptersDownloaded && {
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
!showDownloadBadges && {
|
||||
borderRadius: 4,
|
||||
},
|
||||
!chaptersDownloaded && styles.LeftBorderRadius,
|
||||
!showDownloadBadges && styles.standardBorderRadius,
|
||||
{
|
||||
backgroundColor: theme.primary,
|
||||
color: theme.onPrimary,
|
||||
@@ -263,7 +286,7 @@ const UnreadBadge = ({
|
||||
</Text>
|
||||
);
|
||||
|
||||
const DownloadBadge = ({
|
||||
const DownloadBadge: React.FC<DownloadBadgeProps> = ({
|
||||
chaptersDownloaded,
|
||||
showUnreadBadges,
|
||||
chaptersUnread,
|
||||
@@ -272,13 +295,8 @@ const DownloadBadge = ({
|
||||
<Text
|
||||
style={[
|
||||
styles.downloadBadge,
|
||||
!chaptersUnread && {
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
!showUnreadBadges && {
|
||||
borderRadius: 4,
|
||||
},
|
||||
!chaptersUnread && styles.RightBorderRadius,
|
||||
!showUnreadBadges && styles.standardBorderRadius,
|
||||
{
|
||||
backgroundColor: theme.tertiary,
|
||||
color: theme.onTertiary,
|
||||
@@ -290,6 +308,19 @@ const DownloadBadge = ({
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
LeftBorderRadius: {
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
RightBorderRadius: {
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
standardBorderRadius: {
|
||||
borderRadius: 4,
|
||||
},
|
||||
opacityPoint5: { opacity: 0.5 },
|
||||
padding4: { padding: 4 },
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
borderRadius: 4,
|
||||
@@ -353,4 +384,18 @@ const styles = StyleSheet.create({
|
||||
left: 10,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
standardNovelCover: {
|
||||
borderRadius: 6,
|
||||
overflow: 'hidden',
|
||||
margin: 2,
|
||||
},
|
||||
selectedNovelCover: {
|
||||
opacity: 0.8,
|
||||
},
|
||||
compactTitle: {
|
||||
color: 'rgba(255,255,255,1)',
|
||||
textShadowColor: 'rgba(0, 0, 0, 0.75)',
|
||||
textShadowOffset: { width: -1, height: 1 },
|
||||
textShadowRadius: 10,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,19 +8,24 @@ import {
|
||||
ListRenderItem,
|
||||
} from 'react-native';
|
||||
import { NovelItem } from '@plugins/types';
|
||||
import { LibraryNovelInfo, NovelInfo } from '../database/types';
|
||||
import { LibraryNovelInfo } from '../database/types';
|
||||
import { useDeviceOrientation } from '@hooks';
|
||||
|
||||
export type NovelListRenderItem = ListRenderItem<
|
||||
LibraryNovelInfo | NovelInfo | NovelItem
|
||||
>;
|
||||
export type NovelListRenderItem = ListRenderItem<LibraryNovelInfo | NovelItem>;
|
||||
|
||||
const NovelList: React.FC<
|
||||
FlatListProps<LibraryNovelInfo | NovelInfo | NovelItem>
|
||||
> = props => {
|
||||
type listDataItem =
|
||||
| (LibraryNovelInfo | NovelItem) & {
|
||||
completeRow?: number;
|
||||
};
|
||||
|
||||
interface NovelListProps extends FlatListProps<LibraryNovelInfo | NovelItem> {
|
||||
inSource?: boolean;
|
||||
data: Array<listDataItem>;
|
||||
}
|
||||
|
||||
const NovelList: React.FC<NovelListProps> = props => {
|
||||
const { displayMode = DisplayModes.Comfortable, novelsPerRow = 3 } =
|
||||
useLibrarySettings();
|
||||
|
||||
const orientation = useDeviceOrientation();
|
||||
|
||||
const isListView = displayMode === DisplayModes.List;
|
||||
@@ -37,6 +42,30 @@ const NovelList: React.FC<
|
||||
}
|
||||
}, [isListView, orientation, novelsPerRow]);
|
||||
|
||||
var extendedNovelList: Array<listDataItem> = props?.data;
|
||||
if (props.data?.length && props.inSource) {
|
||||
let remainder = numColumns - (props.data?.length % numColumns);
|
||||
let extension: Array<listDataItem> = [];
|
||||
if (remainder !== 0 && remainder !== numColumns) {
|
||||
for (let i = 0; i < remainder; i++) {
|
||||
extension.push({
|
||||
cover: '',
|
||||
name: '',
|
||||
path: 'loading-' + remainder,
|
||||
completeRow: 1,
|
||||
} as listDataItem);
|
||||
}
|
||||
}
|
||||
extension.push({
|
||||
cover: '',
|
||||
name: '',
|
||||
path: 'loading-' + remainder,
|
||||
completeRow: 2,
|
||||
} as listDataItem);
|
||||
|
||||
extendedNovelList = [...props.data, ...extension];
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={[
|
||||
@@ -47,6 +76,7 @@ const NovelList: React.FC<
|
||||
key={numColumns}
|
||||
keyExtractor={(item, index) => index + '_' + item.path}
|
||||
{...props}
|
||||
data={extendedNovelList}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FilterToValues, Filters } from './filterTypes';
|
||||
|
||||
export interface NovelItem {
|
||||
id: undefined;
|
||||
name: string;
|
||||
path: string;
|
||||
cover?: string;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { getString } from '@strings/translations';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { useLibraryNovels } from '@screens/library/hooks/useLibrary';
|
||||
import { switchNovelToLibrary } from '@database/queries/NovelQueries';
|
||||
import { NovelInfo } from '@database/types';
|
||||
import { LibraryNovelInfo, NovelInfo } from '@database/types';
|
||||
import SourceScreenSkeletonLoading from '@screens/browse/loadingAnimation/SourceScreenSkeletonLoading';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { BrowseSourceScreenProps } from '@navigators/types';
|
||||
@@ -46,7 +46,6 @@ const BrowseSourceScreen = ({ route, navigation }: BrowseSourceScreenProps) => {
|
||||
clearSearchResults,
|
||||
searchError,
|
||||
} = useSearchSource(pluginId);
|
||||
|
||||
const novelList = searchResults.length > 0 ? searchResults : novels;
|
||||
const errorMessage = error || searchError;
|
||||
|
||||
@@ -76,7 +75,7 @@ const BrowseSourceScreen = ({ route, navigation }: BrowseSourceScreenProps) => {
|
||||
);
|
||||
|
||||
const navigateToNovel = useCallback(
|
||||
(item: NovelItem) =>
|
||||
(item: NovelItem | LibraryNovelInfo) =>
|
||||
navigation.navigate('Novel', {
|
||||
...item,
|
||||
pluginId: pluginId,
|
||||
@@ -121,6 +120,7 @@ const BrowseSourceScreen = ({ route, navigation }: BrowseSourceScreenProps) => {
|
||||
) : (
|
||||
<NovelList
|
||||
data={novelList}
|
||||
inSource
|
||||
renderItem={({ item }) => {
|
||||
const inLibrary = novelInLibrary(item.path);
|
||||
|
||||
@@ -131,6 +131,10 @@ const BrowseSourceScreen = ({ route, navigation }: BrowseSourceScreenProps) => {
|
||||
libraryStatus={inLibrary}
|
||||
onPress={() => navigateToNovel(item)}
|
||||
isSelected={false}
|
||||
addSkeletonLoading={
|
||||
(hasNextPage && !searchText) ||
|
||||
(hasNextSearchPage && Boolean(searchText))
|
||||
}
|
||||
onLongPress={async () => {
|
||||
await switchNovelToLibrary(item.path, pluginId);
|
||||
setLibrary(prevValues => {
|
||||
@@ -164,15 +168,9 @@ const BrowseSourceScreen = ({ route, navigation }: BrowseSourceScreenProps) => {
|
||||
fetchNextPage();
|
||||
}
|
||||
}}
|
||||
ListFooterComponent={
|
||||
(hasNextPage && !searchText) ||
|
||||
(hasNextSearchPage && searchText) ? (
|
||||
<SourceScreenSkeletonLoading theme={theme} />
|
||||
) : undefined
|
||||
}
|
||||
onEndReachedThreshold={1.5}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!showLatestNovels && filterValues && !searchText ? (
|
||||
<>
|
||||
<FAB
|
||||
|
||||
@@ -100,7 +100,7 @@ const createStyleSheet = (pictureHeight: number, pictureWidth: number) => {
|
||||
borderRadius: 4,
|
||||
},
|
||||
listLoadingContainer: {
|
||||
marginHorizontal: 12,
|
||||
marginHorizontal: 8,
|
||||
marginVertical: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -9,9 +9,13 @@ import { useDeviceOrientation } from '@hooks';
|
||||
|
||||
interface Props {
|
||||
theme: ThemeColors;
|
||||
completeRow?: number;
|
||||
}
|
||||
|
||||
const SourceScreenSkeletonLoading: React.FC<Props> = ({ theme }) => {
|
||||
const SourceScreenSkeletonLoading: React.FC<Props> = ({
|
||||
theme,
|
||||
completeRow,
|
||||
}) => {
|
||||
const [highlightColor, backgroundColor] = getLoadingColors(theme);
|
||||
|
||||
const { displayMode = DisplayModes.Comfortable, novelsPerRow = 3 } =
|
||||
@@ -31,23 +35,24 @@ const SourceScreenSkeletonLoading: React.FC<Props> = ({ theme }) => {
|
||||
let height = (window.width / numColumns) * (4 / 3);
|
||||
let width = (window.width - 12 - 9.6 * numColumns) / numColumns;
|
||||
return [height, width];
|
||||
}, [numColumns]);
|
||||
}, [numColumns, window.width]);
|
||||
|
||||
const renderLoadingNovel = (item: number, index: number) => {
|
||||
const renderLoadingNovel = (item: number) => {
|
||||
let randomNumber = Math.random();
|
||||
randomNumber < 0.1 ? (randomNumber = 0) : null;
|
||||
return (
|
||||
<LoadingNovel
|
||||
key={index}
|
||||
backgroundColor={backgroundColor}
|
||||
highlightColor={highlightColor}
|
||||
pictureHeight={pictureHeight}
|
||||
pictureWidth={pictureWidth}
|
||||
displayMode={displayMode}
|
||||
/>
|
||||
<View key={'sourceLoading' + item} style={{ flex: 1 / numColumns }}>
|
||||
<LoadingNovel
|
||||
backgroundColor={backgroundColor}
|
||||
highlightColor={highlightColor}
|
||||
pictureHeight={pictureHeight}
|
||||
pictureWidth={pictureWidth}
|
||||
displayMode={displayMode}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
const renderLoading = (item: number, index: number) => {
|
||||
const renderLoading = (item: number) => {
|
||||
const offset = Math.pow(10, item);
|
||||
const items: Array<number> = [1 * offset];
|
||||
if (displayMode !== DisplayModes.List) {
|
||||
@@ -56,12 +61,16 @@ const SourceScreenSkeletonLoading: React.FC<Props> = ({ theme }) => {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<View key={index} style={styles.row}>
|
||||
<View key={'sourceSkeletonRow' + item} style={styles.row}>
|
||||
{items.map(renderLoadingNovel)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
let items: Array<number> = [];
|
||||
if (completeRow === 1) {
|
||||
return renderLoadingNovel(completeRow);
|
||||
}
|
||||
|
||||
if (displayMode === DisplayModes.List) {
|
||||
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
} else {
|
||||
@@ -69,6 +78,7 @@ const SourceScreenSkeletonLoading: React.FC<Props> = ({ theme }) => {
|
||||
items.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
return <View style={styles.container}>{items.map(renderLoading)}</View>;
|
||||
};
|
||||
|
||||
@@ -76,15 +86,23 @@ const createStyleSheet = () => {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
marginHorizontal: 4,
|
||||
marginHorizontal: 2,
|
||||
marginBottom: 8,
|
||||
marginTop: 2,
|
||||
overflow: 'visible',
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 1,
|
||||
},
|
||||
completeRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
paddingHorizontal: 1,
|
||||
marginBottom: 8,
|
||||
position: 'relative',
|
||||
right: 0,
|
||||
opacity: 0.8,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user