Pull to Refresh: Notifications, Chats, Bookmarks

This commit is contained in:
Alex Gleason 2021-11-04 14:07:20 -05:00
parent f61845b876
commit 65a2a40cb2
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
5 changed files with 48 additions and 20 deletions

View File

@ -9,15 +9,17 @@ export const BOOKMARKED_STATUSES_EXPAND_REQUEST = 'BOOKMARKED_STATUSES_EXPAND_RE
export const BOOKMARKED_STATUSES_EXPAND_SUCCESS = 'BOOKMARKED_STATUSES_EXPAND_SUCCESS'; export const BOOKMARKED_STATUSES_EXPAND_SUCCESS = 'BOOKMARKED_STATUSES_EXPAND_SUCCESS';
export const BOOKMARKED_STATUSES_EXPAND_FAIL = 'BOOKMARKED_STATUSES_EXPAND_FAIL'; export const BOOKMARKED_STATUSES_EXPAND_FAIL = 'BOOKMARKED_STATUSES_EXPAND_FAIL';
const noOp = () => new Promise(f => f());
export function fetchBookmarkedStatuses() { export function fetchBookmarkedStatuses() {
return (dispatch, getState) => { return (dispatch, getState) => {
if (getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) { if (getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) {
return; return dispatch(noOp);
} }
dispatch(fetchBookmarkedStatusesRequest()); dispatch(fetchBookmarkedStatusesRequest());
api(getState).get('/api/v1/bookmarks').then(response => { return api(getState).get('/api/v1/bookmarks').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data)); dispatch(importFetchedStatuses(response.data));
dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null)); dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
@ -53,12 +55,12 @@ export function expandBookmarkedStatuses() {
const url = getState().getIn(['status_lists', 'bookmarks', 'next'], null); const url = getState().getIn(['status_lists', 'bookmarks', 'next'], null);
if (url === null || getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) { if (url === null || getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) {
return; return dispatch(noOp);
} }
dispatch(expandBookmarkedStatusesRequest()); dispatch(expandBookmarkedStatusesRequest());
api(getState).get(url).then(response => { return api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data)); dispatch(importFetchedStatuses(response.data));
dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null)); dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null));

View File

@ -2,7 +2,8 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import Column from '../ui/components/column'; import Column from 'soapbox/components/column';
import SubNavigation from 'soapbox/components/sub_navigation';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import StatusList from '../../components/status_list'; import StatusList from '../../components/status_list';
@ -38,15 +39,22 @@ class Bookmarks extends ImmutablePureComponent {
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
}; };
componentDidMount() { fetchData = () => {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch(fetchBookmarkedStatuses()); return dispatch(fetchBookmarkedStatuses());
}
componentDidMount() {
this.fetchData();
} }
handleLoadMore = debounce(() => { handleLoadMore = debounce(() => {
this.props.dispatch(expandBookmarkedStatuses()); this.props.dispatch(expandBookmarkedStatuses());
}, 300, { leading: true }) }, 300, { leading: true })
handleRefresh = () => {
return this.fetchData();
}
render() { render() {
const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
@ -55,7 +63,8 @@ class Bookmarks extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.bookmarks' defaultMessage="You don't have any bookmarks yet. When you add one, it will show up here." />; const emptyMessage = <FormattedMessage id='empty_column.bookmarks' defaultMessage="You don't have any bookmarks yet. When you add one, it will show up here." />;
return ( return (
<Column heading={intl.formatMessage(messages.heading)} transparent> <Column transparent>
<SubNavigation message={intl.formatMessage(messages.heading)} />
<StatusList <StatusList
trackScroll={!pinned} trackScroll={!pinned}
statusIds={statusIds} statusIds={statusIds}
@ -63,6 +72,7 @@ class Bookmarks extends ImmutablePureComponent {
hasMore={hasMore} hasMore={hasMore}
isLoading={isLoading} isLoading={isLoading}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
onRefresh={this.handleRefresh}
shouldUpdateScroll={shouldUpdateScroll} shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn} bindToDocument={!multiColumn}

View File

@ -3,12 +3,12 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Column from '../../components/column'; import Column from '../../components/column';
import ColumnHeader from '../../components/column_header'; import ColumnHeader from '../../components/column_header';
import { launchChat } from 'soapbox/actions/chats'; import { fetchChats, launchChat } from 'soapbox/actions/chats';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ChatList from './components/chat_list'; import ChatList from './components/chat_list';
import AudioToggle from 'soapbox/features/chats/components/audio_toggle'; import AudioToggle from 'soapbox/features/chats/components/audio_toggle';
import AccountSearch from 'soapbox/components/account_search'; import AccountSearch from 'soapbox/components/account_search';
import Pullable from 'soapbox/components/pullable'; import PullToRefresh from 'soapbox/components/pull_to_refresh';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.chats', defaultMessage: 'Chats' }, title: { id: 'column.chats', defaultMessage: 'Chats' },
@ -36,6 +36,11 @@ class ChatIndex extends React.PureComponent {
this.context.router.history.push(`/chats/${chat.get('id')}`); this.context.router.history.push(`/chats/${chat.get('id')}`);
} }
handleRefresh = () => {
const { dispatch } = this.props;
return dispatch(fetchChats());
}
render() { render() {
const { intl } = this.props; const { intl } = this.props;
@ -55,12 +60,12 @@ class ChatIndex extends React.PureComponent {
onSelected={this.handleSuggestion} onSelected={this.handleSuggestion}
/> />
<Pullable> <PullToRefresh onRefresh={this.handleRefresh}>
<ChatList <ChatList
onClickChat={this.handleClickChat} onClickChat={this.handleClickChat}
emptyMessage={<FormattedMessage id='chat_panels.main_window.empty' defaultMessage="No chats found. To start a chat, visit a user's profile." />} emptyMessage={<FormattedMessage id='chat_panels.main_window.empty' defaultMessage="No chats found. To start a chat, visit a user's profile." />}
/> />
</Pullable> </PullToRefresh>
</Column> </Column>
); );
} }

View File

@ -21,7 +21,6 @@ import TimelineQueueButtonHeader from '../../components/timeline_queue_button_h
import { getSettings } from 'soapbox/actions/settings'; import { getSettings } from 'soapbox/actions/settings';
import PlaceholderNotification from 'soapbox/features/placeholder/components/placeholder_notification'; import PlaceholderNotification from 'soapbox/features/placeholder/components/placeholder_notification';
import SubNavigation from 'soapbox/components/sub_navigation'; import SubNavigation from 'soapbox/components/sub_navigation';
import Pullable from 'soapbox/components/pullable';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.notifications', defaultMessage: 'Notifications' }, title: { id: 'column.notifications', defaultMessage: 'Notifications' },
@ -129,6 +128,11 @@ class Notifications extends React.PureComponent {
this.props.dispatch(dequeueNotifications()); this.props.dispatch(dequeueNotifications());
}; };
handleRefresh = () => {
const { dispatch } = this.props;
return dispatch(expandNotifications());
}
render() { render() {
const { intl, notifications, isLoading, hasMore, showFilterBar, totalQueuedNotificationsCount } = this.props; const { intl, notifications, isLoading, hasMore, showFilterBar, totalQueuedNotificationsCount } = this.props;
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />; const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
@ -173,6 +177,7 @@ class Notifications extends React.PureComponent {
placeholderComponent={PlaceholderNotification} placeholderComponent={PlaceholderNotification}
placeholderCount={20} placeholderCount={20}
onLoadMore={this.handleLoadOlder} onLoadMore={this.handleLoadOlder}
onRefresh={this.handleRefresh}
onScrollToTop={this.handleScrollToTop} onScrollToTop={this.handleScrollToTop}
onScroll={this.handleScroll} onScroll={this.handleScroll}
> >
@ -189,9 +194,7 @@ class Notifications extends React.PureComponent {
count={totalQueuedNotificationsCount} count={totalQueuedNotificationsCount}
message={messages.queue} message={messages.queue}
/> />
<Pullable>
{scrollContainer} {scrollContainer}
</Pullable>
</Column> </Column>
); );
} }

View File

@ -934,11 +934,19 @@
} }
// Make MaterialStatus flush against SubNavigation // Make MaterialStatus flush against SubNavigation
.sub-navigation ~ .slist .item-list > article:first-child .material-status__status, .sub-navigation ~,
.sub-navigation ~ .material-status:not(.material-status + .material-status) .material-status__status { .sub-navigation ~ .ptr > .ptr__children > {
// ScrollableList
.slist .item-list > article:first-child,
// Thread
.material-status:not(.material-status + .material-status) {
// MaterialStatus
.material-status__status {
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
} }
}
}
// Display background for loading indicator // Display background for loading indicator
.column--transparent { .column--transparent {