Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding scenarios and adding screen #19

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/App/Config/DebugConfig.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default {
useReactotron: __DEV__,
useFixtures: false,
useStorybook: false
useFixtures: true,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quando a gente sobe algum código pra develop ou pra master, tentamos sempre subir com fixtures desativadas. Isso evita eventuais erros na hora de gerar versões de produção.

useStorybook: false,
}
19 changes: 19 additions & 0 deletions app/App/Features/ToDo/Components/AddToDo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import {Text, View} from 'react-native'

import type { ToDoAdd } from '../Entities/index'

import Images from '../../../Themes/Images'

type Props = {
item : ToDoAdd,
onToDos(values: ToDoAdd):void

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Para tipagem de funções, você pode definir assim:


type Props = {
    item: ToDoAdd,
    onToDos: (values: ToDoAdd) => void
}
  • Tente definir nomes com verbos para callbacks. Por exemplo, em vez de onToDo, definir onAddToDo ou onAddItem. Como você escolheu o termo item no prop acima, era legal padronizar.

}

const AddToDO = ({ item, onToDos}: Props,ref) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

De onde veio esse ref? 🤔

return (
<View>

</View>
)
}
1 change: 1 addition & 0 deletions app/App/Features/ToDo/Components/ToDo.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Props = {
toggleToDo: () => mixed
}


const ToDo = ({ text, onPressText, toggled, toggleToDo }: Props) => {
return (
<View style={styles.container}>
Expand Down
143 changes: 114 additions & 29 deletions app/App/Features/ToDo/Containers/ToDoScreen.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
// @flow
import React, { useCallback, useState, useEffect } from 'react'
import { View, Text, ImageBackground, Image, TouchableOpacity, FlatList } from 'react-native'
import { View, Text, ImageBackground, Image, TouchableOpacity, FlatList, ActivityIndicator, Modal, Picker } from 'react-native'
import { useDispatch, useSelector } from 'react-redux'

import ToDo from '../Components/ToDo'
import TogglableText from '../Components/TogglableText'

import { actions as ToDosUIActions } from '../Redux/Ui'
import ToDoEntitySelectors from '../Selectors/Entity'
import ToDoUISelections from '../Selectors/Ui'
import ToDoUISelections, { fetching } from '../Selectors/Ui'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O termo padrão é selector. Se você usar selections, talvez isso confundirá o próximo desenvolvedor a manter essa tela.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Além disso, o ideal nesse caso seria utilizar ToDoUISelectors.fetching já que você importou o default do arquivo de seletores de Ui, daí não seria necessário importar o fetching separadamente e deixaria claro de onde esse seletor vem.

Nada impede você de importar os seletores um por um (no caso de fetching), apenas deixo como sugestão quando fazer isso, renomear para o import para isFetchingSomethingSelector dessa forma:

import { fetching as isFetchingSomethingSelector } from '../.../......'


import styles from './ToDoScreen.style'
import { Images } from '../../../Themes'

import type { StackNavigationProp } from '@react-navigation/stack'

import moment from 'moment'
import moment from 'moment' //data-hora
import { TextInput } from 'react-native-gesture-handler'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Por que usar o TextInput do react-native-gesture-handler e não o do react-native? 🤔 A não ser que ele faça alguma coisa especial, é mais seguro usar os componentes 'nativos' ao react-native.

import colors from '../../../Themes/Colors'
import { values } from 'lodash'



type Props = {
navigation: StackNavigationProp
}


const ToDoScreen = ({ navigation }: Props) => {
// Redux Actions
const dispatch = useDispatch()
const getToDos = useCallback(() => dispatch(ToDosUIActions.request()))

// State
const [selectedFilterIndex, setFilterIndex] = useState(0)
const [selectedFilterIndex, setFilterIndex] = useState(0)
const [add, setAdd] = useState(false)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Com propriedades booleanas, é recomendado seguir o padrão de adicionar um is ou are no início da frase. Por exemplo:

const [isAdding, setIsAdding] = useState(false)
const [isOpen, setIsOpen] = useState(false) 

ou se você preferir (com tipagem do Typescipt):

const [isAdding, setAddingStatus] = useState<boolean>(false)
const [isOpen, setOpenStatus] = useState<boolean>(false) 

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E seria interessante também deixar mais claro o que está sendo adicionado, aberto ou o que a data representa, por exemplo:

const [isAddingSomething, setAddingSomething] = useState<boolean>(false)
const [isSomethingOpen, setIsSomethingOpen] = useState<boolean>(false)
const [reminderDate, setReminderDate] = useState<Date>(new Date())

const [open, setOpen] = useState(false)
const [date, setDate] = useState(new Date())

// Selectors
const sortedToDos = useSelector(ToDoEntitySelectors.sortedToDos)
Expand All @@ -41,36 +50,49 @@ const ToDoScreen = ({ navigation }: Props) => {

// Consts
const filterList = ['All', 'Today', 'This week', 'This month']
console.tron.logImportant({sortedToDos,fetching,error})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numa PR normal, comentários ou console.logs no código geralmente fazem a tarefa voltar, pra você retirar.


return (
<ImageBackground source={Images.appBackground} style={styles.background}>
<HeaderContainer onPressSearch={() => {}} />
<View style={styles.tasksContainer}>
<FilterListContainer
filterList={filterList}
selectedFilter={selectedFilterIndex}
selectedFilter={selectedFilterIndex} //--
onPressFilter={setFilterIndex}
/>
{!fetching && !error && !!sortedToDos && (
<FlatList
style={{ marginLeft: 12 }}
data={sortedToDos}
keyExtractor={(item, index) => `${item.id}-${index}-${item.title}`}
renderItem={({ item }) => (
<ToDo onPressText={() => {}} toggleToDo={() => {}} text={item.title} toggled={item.isDone} />
)}
/>
)}
<ListContainer

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gostei dessa decisão de extrair o componente de lista. Isso limpa o código dessa tela, e deixa o propósito do componente bem evidente.

sortedToDos = {sortedToDos}
fetching = {fetching}
error = {error}
/>
</View>
<FloatingButton onPress={() => {}} />
<FloatingButton onPress={() => setOpen(true)} />
<Modal transparent={true} visible ={open} animationType="slide">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do mesmo jeito que extraiu o ListContainer, podia ter extraído do AddToDoModal também. Tive que decifrar o que esse trecho de código faz pelo placeholder de "Novo Lembrete", mas com um nome do tipo AddToDoModal, teria entendido logo de cara.

<View style={styles.container}>
<View style={styles.flexModalContainer}>
<CloseButton onPress = {() => setOpen(false)}/>
<TextInput style={styles.textInput} placeholder='Novo Lembrete' placeholderTextColor ={colors.c600}/>
<AddDate/>
<PriorityList/>
<FloatingButtonAdd onPress = {() => setAdd(true)}/>
</View>
</View>
</Modal>
</ImageBackground>
)
}

const FloatingButton = ({ onPress }) => (
<TouchableOpacity onPress={onPress} style={styles.floatingButton}>
<Image source={Images.add['36px']} />
</TouchableOpacity>
const HeaderContainer = ({ onPressSearch }) => (
<View style={styles.headerContainer}>
<View>
<Text style={styles.displayDateName}>Today</Text>
<Text style={styles.date}>{moment().format('dddd, DD MMMM')}</Text>
</View>
<TouchableOpacity activeOpacity={0.7} onPress={onPressSearch} style={styles.searchContainer}>
<Image source={Images.search['24px']} />
</TouchableOpacity>
</View>
)

const FilterListContainer = ({ filterList, selectedFilter, onPressFilter }) => (
Expand All @@ -89,15 +111,78 @@ const FilterListContainer = ({ filterList, selectedFilter, onPressFilter }) => (
</View>
)

const HeaderContainer = ({ onPressSearch }) => (
<View style={styles.headerContainer}>
<View>
<Text style={styles.displayDateName}>Today</Text>
<Text style={styles.date}>{moment().format('dddd, DD MMMM')}</Text>
</View>
<TouchableOpacity activeOpacity={0.7} onPress={onPressSearch} style={styles.searchContainer}>
<Image source={Images.search['24px']} />
</TouchableOpacity>
const ListContainer = ({sortedToDos,fetching,error}) => (
<>
{!!fetching &&
<View style = {styles.fetchingCircle}>
<ActivityIndicator size="large" color = "#000"/>
</View>
}
{Object.entries(sortedToDos).length == 0 ? <EmptyContainer/> :
<>
<FlatList
style={{ marginLeft: 12 }}
data={sortedToDos}
keyExtractor={(item, index) => `${item.id}-${index}-${item.title}`}
renderItem={({ item }) => (
<ToDo onPressText={() => {}} toggleToDo={() => {console.warn(item.isDone)}} text={item.title} toggled={item.isDone} />
)}
/>
</>
}
</>
)

const EmptyContainer = () => (
<View style={styles.emptyContainer}>
<Image source={Images.sol['36px']} />
<Text style = {styles.displayEmptyName}>Tudo Limpo!</Text>
<Text style = {styles.displayEmptyText}>Adicione um novo lembrete tocando no '+'.</Text>
</View>
)

const FloatingButton = ({ onPress }) => (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Esses componentes dentro desse arquivo são bem pequenos, mas ainda assim, merecem seu próprio arquivo, seu próprio arquivo de estilos, seu próprio registro no Storybook, etc. Inclusive, são esses componentes pequenos que são super reutilizáveis e não podem estar perdidos dentro de um arquivo de tela.

<TouchableOpacity onPress={onPress} style={styles.floatingButton}>
<Image source={Images.add['36px']} />
</TouchableOpacity>
)

const FloatingButtonAdd = ({ onPress }) => (
<TouchableOpacity style ={styles.floatingButtonAdd} onPressText={onPress}>
<Text style={styles.textAdd}>Adicionar</Text>
</TouchableOpacity>
)

const CloseButton = ({ onPress }) => (
<TouchableOpacity onPress={onPress}>
<Image source={Images.close['24px']} />
</TouchableOpacity>
)

const AddDate = ({}) => (
<View>
<TouchableOpacity onPress={() => {}} style={styles.addDateTouch}>
<Image style={styles.addDateImage} source={Images.bell['24px']} />
<Text style = {styles.addDateText}>Lembrar-me</Text>
</TouchableOpacity>
</View>
)

const PriorityList = () =>{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um erro bastante comum é guardar informações de estado de dados dentro do componente e não da tela. Por exemplo, a tela de "Novo Lembrete" precisa da prioridade, mas esse informação á perdida aqui dentro desse componente. Para isso, a gente geralmente usa o padrão controlled-component, onde a tela controla o componente. Ficaria algo assim:

const ToDoScreen = () => {
  const [selectedPriority, onSelectPriority] = useState('Selecionar')
  return (.... 
     <PriorityList 
        selectedPriority={selectedPriority} 
        onSelectPriority={onSelectPriority} 
     /> 
  ...)
}

const PriorityList = ({ selectedPriority, onSelectPriority }) => (...)

const [priorityList, setPriorityList] = useState('Selecionar')
return (
<View style ={styles.container}>
<Image style={styles.addDateImage} source ={Images.flag['24px']}/>
<Text style={styles.textPicker}>Prioridade</Text>
<Picker style = {styles.picker} priorityList = {priorityList} onValueChange = {(value, index) => setPriorityList(index)}>
<Picker.Item label='Selecionar' value='0'/>
<Picker.Item label='Baixa' value='1'/>
<Picker.Item label='Media' value='2'/>
<Picker.Item label='Alta' value='3'/>
</Picker>
</View>
)
}


export default ToDoScreen
104 changes: 104 additions & 0 deletions app/App/Features/ToDo/Containers/ToDoScreen.style.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,110 @@ const styles = StyleSheet.create({
backgroundColor: Colors.a220,
justifyContent: 'center',
alignItems: 'center'
},
fetchingCircle: {
flex:1,
justifyContent: "center",
flexDirection:"row",
padding: 10,
},
emptyContainer: {
flex:1,
alignItems: "center",
justifyContent: "center",
},
displayEmptyName: {
color: '#4A4A4D',
fontSize: 21,
fontWeight: 'bold'
},
displayEmptyText: {
color: '#4A4A4D',
fontSize: 16
},
floatingButtonAdd: {
backgroundColor:Colors.a220,
alignSelf: 'center',
width: 200,
height: 60,
borderRadius:15,
justifyContent: 'center',
alignItems:'center',
position: 'relative',
bottom: 20
},
textAdd: {
fontSize:18,
alignItems: 'center',
color:'#fff'
},
textInput:{
justifyContent:'center',
fontSize: 30,
alignItems: 'center',
paddingLeft: 30,
borderStyle: 'solid',
borderBottomWidth: 2,
borderBottomColor: '#E3E4E6',
fontWeight: 'bold'
},
floatingButtonNull: {
backgroundColor:'#fff',
alignSelf: 'center',
width: 200,
height: 50,
borderRadius:15,
justifyContent: 'center',
alignItems:'center',
position:'absolute',
bottom: 20,
},
container: {
flex: 1
},
flexModalContainer: {
flex:1,
marginTop: 130,
padding: 40,
borderTopLeftRadius: 32,
backgroundColor: '#fff'

},
addDateTouch: {
padding: 22,
alignItems: 'center',
paddingLeft: 30,
borderStyle: 'solid',
borderBottomColor: '#E3E4E6',
borderBottomWidth: 2,
justifyContent: 'center'
},
addDateImage: {
padding: 8,
position:'absolute',
top: 12,
left: 0
},
addDateText: {
position:'absolute',
color: Colors.c400,
fontSize:18,
left: 30
},
picker: {
color: Colors.c400,
position: 'absolute',
width: 200,
left: 230
},
textPicker:{
padding: 10,
color: Colors.c400,
fontSize: 18,
borderStyle: 'solid',
borderBottomColor: '#E3E4E6',
paddingLeft:30,
borderEndWidth:2
}
})

Expand Down
6 changes: 6 additions & 0 deletions app/App/Features/ToDo/Entities/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ export type ToDoType = {
reminder: string,
priority: string
}

export type ToDoAdd = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um ToDo é um item, não uma ação, então não precisa do "Add" no final.

  1. Existe um ToDo que não tem título? Até consigo imaginar não ter lembrete ou prioridade, mas título é importante.
  2. Geralmente não colocamos o | null direto na entidade, pq aí, em todo local que ele é usado no app, ele poderá ser nulo, e isso não é o que queremos. Em lugares específicos, e gente pode adicionar o | null mas não na definição da entidade.

title?: string,
reminder?: string,
priority?: String,
} | null
4 changes: 4 additions & 0 deletions app/App/Features/ToDo/Fixtures/getToDosSuccess.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

[
/*
{
"id": 1,
"title": "Take over the galaxy",
Expand Down Expand Up @@ -71,4 +73,6 @@
"reminder": "2019-11-16T16:13:27.523Z",
"priority": "Top"
}
*/
]

3 changes: 2 additions & 1 deletion app/App/Features/ToDo/Redux/Entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const toDoEntitySlice = createSlice({
addToDos: (state: State, action: PayloadAction) => {
const toDoList = action.payload
return toDoList
}
},

}
})

Expand Down
Binary file added app/App/Images/sol_84px.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/App/Images/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/App/Images/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion app/App/Navigation/AppNavigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react'
import { createStackNavigator } from '@react-navigation/stack'
import ToDoScreen from '../Features/ToDo/Containers/ToDoScreen'

const Stack = createStackNavigator()
const Stack = createStackNavigator() //uma tela em cima da outra

function AppNavigation () {
return (
Expand Down
Loading