Skip to content
This repository has been archived by the owner on Mar 12, 2020. It is now read-only.

Commit

Permalink
Add Support for Renaming Threads (#1084)
Browse files Browse the repository at this point in the history
* Refactored some group code.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Added rename-group to features.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Added button to group screen that goes to RenameGroup screen.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Styled rename group container.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Successfully makes API call to rename group.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Navigate to thread after renaming it.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Convert RenameGroup compoonent into a modal.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Refresh thread after changing its name.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Close modal upon completion.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Fixed linting issues and missing imports.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Fixed linting issues.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Switched to using yarn.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Fixed some review issues.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Fixed renaming threads workflow, styling.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Fixed disabled opacity.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Use constants for fontFamily styling.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Added types to styles, moved them into container where they are used.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Fixed consecutive blank lines.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Added test for rename-group reducer.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Fixed bug in success test.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>

* Fleshed out tests, fixed snapshot.

Signed-off-by: Thomas Hobohm <public@thomashobohm.com>
  • Loading branch information
undercase committed May 24, 2019
1 parent cac6cd6 commit 964a388
Show file tree
Hide file tree
Showing 16 changed files with 371 additions and 13 deletions.
185 changes: 185 additions & 0 deletions App/Containers/RenameGroupModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
Container to rename a group.
URL: "RenameGroup"
Parameters:
- threadId: the ID of the group to be renamed
When the user submits a new name or presses back, they are taken back to the
group screen.
*/

import React from 'react'
import { Dispatch } from 'redux'
import { connect } from 'react-redux'

import {
View,
Text,
TouchableOpacity,
ViewStyle,
TextStyle
} from 'react-native'
import Modal from 'react-native-modal'
import { color, spacing, size, fontSize, fontFamily } from '../styles'

import { TextileHeaderButtons, Item as TextileHeaderButtonsItem } from '../Components/HeaderButtons'
import Button from '../Components/LargeButton'
import Input from '../SB/components/Input'

import { RootState, RootAction } from '../Redux/Types'
import { groupActions } from '../features/group'

const container: ViewStyle = {
flex: 1,
backgroundColor: color.grey_6,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'stretch',
padding: spacing._024
}

const inputStyle: TextStyle = {
height: size._064,
fontSize: fontSize._20,
color: color.grey_1
}

const buttonContainer: ViewStyle = {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}

const buttons: ViewStyle = {
marginTop: spacing._048,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center'
}

const cancelButtonText: TextStyle = {
color: color.grey_2,
fontSize: fontSize._20,
fontFamily: fontFamily.regular,
textAlign: 'center'
}

const confirmButtonText: TextStyle = {
color: color.action_2,
fontSize: fontSize._20,
fontFamily: fontFamily.regular,
textAlign: 'center'
}

interface StateProps {
renaming: boolean
}

interface DispatchProps {
rename: (newName: string) => void
}

interface ModalProps {
isVisible: boolean
threadId: string
groupName: string
cancel: () => void
complete: () => void
}

interface State {
newName: string,
startedRename: boolean
}

type Props = StateProps & DispatchProps & ModalProps

class RenameGroupModal extends React.Component<Props, State> {

constructor(props: Props) {
super(props)
this.state = {
newName: props.groupName,
startedRename: false
}
}

componentDidUpdate() {
if (this.state.startedRename && !this.props.renaming) {
this.setState({
startedRename: false
})
this.props.complete()
}
}

render() {
const groupName = this.props.groupName
const disabled = this.state.newName === '' || this.props.renaming
return (
<Modal
isVisible={this.props.isVisible}
animationIn={'fadeInUp'}
animationOut={'fadeOutDown'}
avoidKeyboard={true}
backdropOpacity={0}
style={{margin: 0, padding: 0}}
>
<View style={container}>
<Input
style={inputStyle}
value={this.state.newName}
label={this.state.newName === '' ? 'Change the group name' : ''}
onChangeText={this.handleNewText}
/>
<View style={buttons}>
<TouchableOpacity
style={buttonContainer}
onPress={this.props.cancel}
>
<Text style={cancelButtonText}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
style={[buttonContainer, disabled ? { opacity: 0.2 } : {}]}
disabled={disabled}
onPress={this.rename}
>
<Text style={confirmButtonText}>Rename</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
)
}

handleNewText = (text: string) => {
this.setState({
newName: text
})
}

rename = () => {
this.props.rename(this.state.newName)
this.setState({
startedRename: true
})
}
}

const mapStateToProps = (state: RootState, ownProps: ModalProps): StateProps => {
const threadId = ownProps.threadId
const renaming = Object.keys(state.group.renameGroup).indexOf(threadId) > -1
return {
renaming
}
}

const mapDispatchToProps = (dispatch: Dispatch<RootAction>, ownProps: ModalProps): DispatchProps => {
const threadId = ownProps.threadId
return {
rename: (newName: string) => { dispatch(groupActions.renameGroup.renameGroup.request({ threadId, name: newName })) }
}
}

export default connect(mapStateToProps, mapDispatchToProps)(RenameGroupModal)
13 changes: 13 additions & 0 deletions App/Redux/PhotoViewingRedux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ const actions = {
refreshThreadError: createAction('REFRESH_THREAD_ERROR', (resolve) => {
return (threadId: string, error: any) => resolve({ threadId, error })
}),
updateThreadName: createAction('UPDATE_THREAD_NAME', (resolve) => {
return (threadId: string, name: string) => resolve({ threadId, name })
}),
viewWalletPhoto: createAction('VIEW_WALLET_PHOTO', (resolve) => {
return (photoId: string) => resolve({ photoId })
}),
Expand Down Expand Up @@ -207,6 +210,16 @@ export function reducer(state: PhotoViewingState = initialState, action: PhotoVi
const threads = { ...state.threads, [threadId]: { ...threadData, querying: false, error: threadError } }
return { ...state, threads }
}
case getType(actions.updateThreadName): {
const { threadId, name } = action.payload
const threadData = state.threads[threadId]
if (!threadData) {
// We should always have threadData before renaming a thread, but just make sure.
return state
}
const threads = { ...state.threads, [threadId]: { ...threadData, name } }
return { ...state, threads }
}
case getType(actions.viewWalletPhoto): {
const { photoId } = action.payload
const defaultThreadData = Object.keys(state.threads)
Expand Down
3 changes: 2 additions & 1 deletion App/features/group/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { feedActions as feed } from './feed'
import { addMessageActions as addMessage } from './add-message'
import { addPhotoActions as addPhoto } from './add-photo'
import { renameGroupActions as renameGroup } from './rename-group'

export { feed, addMessage, addPhoto }
export { feed, addMessage, addPhoto, renameGroup }
15 changes: 14 additions & 1 deletion App/features/group/add-message/actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { createAsyncAction } from 'typesafe-actions'

export interface AddMessagePayload {
id: string
groupId: string
}

export interface AddMessageRequestPayload extends AddMessagePayload {
body: string
}

export interface AddMessageFailurePayload extends AddMessagePayload {
error: any
}

export const addMessage = createAsyncAction(
'group/add-message/ADD_MESSAGE_REQUEST',
'group/add-message/ADD_MESSAGE_SUCCESS',
'group/add-message/ADD_MESSAGE_FAILURE'
)<{ id: string, groupId: string, body: string }, { id: string, groupId: string }, { id: string, groupId: string, error: any }>()
)<AddMessageRequestPayload, AddMessagePayload, AddMessageFailurePayload>()
2 changes: 1 addition & 1 deletion App/features/group/add-message/sagas.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ActionType, getType } from 'typesafe-actions'
import { call, put, takeEvery, all } from 'redux-saga/effects'
import Textile from '@textile/react-native-sdk'
import { addMessage} from './actions'
import { addMessage } from './actions'

export function * handleAddMessageRequest(action: ActionType<typeof addMessage.request>) {
const { id, groupId, body } = action.payload
Expand Down
1 change: 0 additions & 1 deletion App/features/group/add-photo/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { combineReducers } from 'redux'
import { ActionType, getType } from 'typesafe-actions'

Expand Down
5 changes: 4 additions & 1 deletion App/features/group/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ import { ActionType } from 'typesafe-actions'
import { feedReducer, FeedState } from './feed'
import { addMessageReducer, AddMessageState } from './add-message'
import { addPhotoReducer, ProcessingImagesState } from './add-photo'
import { renameGroupReducer, RenameGroupState } from './rename-group'

import * as actions from './actions'

export interface GroupState {
feed: FeedState
addMessage: AddMessageState
addPhoto: ProcessingImagesState
renameGroup: RenameGroupState
}

export type GroupAction = ActionType<typeof actions>

export default combineReducers<GroupState>({
feed: feedReducer,
addMessage: addMessageReducer,
addPhoto: addPhotoReducer
addPhoto: addPhotoReducer,
renameGroup: renameGroupReducer
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renaming groups initial state should match snapshot 1`] = `Object {}`;
7 changes: 7 additions & 0 deletions App/features/group/rename-group/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createAsyncAction } from 'typesafe-actions'

export const renameGroup = createAsyncAction(
'group/rename-group/RENAME_GROUP_REQUEST',
'group/rename-group/RENAME_GROUP_SUCCESS',
'group/rename-group/RENAME_GROUP_FAILURE'
)<{ threadId: string, name: string }, { threadId: string }, { threadId: string, error: any }>()
5 changes: 5 additions & 0 deletions App/features/group/rename-group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as renameGroupActions from './actions'
import renameGroupReducer, { RenameGroupState, RenameGroupAction } from './reducer'
import renameGroupSaga from './sagas'

export { renameGroupActions, renameGroupReducer, RenameGroupState, RenameGroupAction, renameGroupSaga }
Empty file.
31 changes: 31 additions & 0 deletions App/features/group/rename-group/reducer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
renameGroupReducer as reducer,
renameGroupActions as actions
} from './'

const threadId1 = 'id1'
const threadId2 = 'id2'
const name1 = 'name1'
const name2 = 'name2'
const error = 'error'
const initialState = reducer(undefined, {} as any)

describe('renaming groups', () => {
describe('initial state', () => {
it('should match snapshot', () => {
expect(initialState).toMatchSnapshot()
})
})
describe('request to rename group', () => {
it('should manage async renaming the group', () => {
const state0 = reducer(initialState, actions.renameGroup.request({ threadId: threadId1, name: name1 }))
expect(state0[threadId1]).toBeDefined()
const state1 = reducer(state0, actions.renameGroup.success({ threadId: threadId1 }))
expect(state1[threadId1]).toBeUndefined()
const state2 = reducer(state1, actions.renameGroup.request({ threadId: threadId2, name: name2 }))
expect(state2[threadId2]).toBeDefined()
const state3 = reducer(state2, actions.renameGroup.failure({ threadId: threadId2, error }))
expect(state3[threadId2].error).toEqual(error)
})
})
})
39 changes: 39 additions & 0 deletions App/features/group/rename-group/reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { combineReducers } from 'redux'
import { ActionType, getType } from 'typesafe-actions'

import * as actions from './actions'

export interface RenameGroupState {
readonly [threadId: string]: {
readonly error?: string
}
}

export type RenameGroupAction = ActionType<typeof actions>

export default (state: RenameGroupState = {}, action: RenameGroupAction) => {
switch (action.type) {
case getType(actions.renameGroup.request): {
return {
...state,
[action.payload.threadId]: {}
}
}
case getType(actions.renameGroup.success): {
const { [action.payload.threadId]: renamed, ...renameGroup } = state
return renameGroup
}
case getType(actions.renameGroup.failure): {
const { threadId, error } = action.payload
const errorMessage = error.message as string || error as string || 'unknown'
return {
...state,
[threadId]: {
error: errorMessage
}
}
}
default:
return state
}
}
22 changes: 22 additions & 0 deletions App/features/group/rename-group/sagas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ActionType, getType } from 'typesafe-actions'
import { call, put, takeEvery, all } from 'redux-saga/effects'
import Textile from '@textile/react-native-sdk'
import { renameGroup } from './actions'
import PhotoViewingActions from '../../../Redux/PhotoViewingRedux'

export function *handleRenameGroupRequest(action: ActionType<typeof renameGroup.request>) {
const { threadId, name } = action.payload
try {
yield call(Textile.threads.rename, threadId, name)
yield put(PhotoViewingActions.updateThreadName(threadId, name))
yield put(renameGroup.success({ threadId }))
} catch (error) {
yield put(renameGroup.failure({ threadId, error }))
}
}

export default function *() {
yield all([
takeEvery(getType(renameGroup.request), handleRenameGroupRequest)
])
}
Loading

0 comments on commit 964a388

Please sign in to comment.