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

feat(tabbar): show notifications count #360

Merged
merged 17 commits into from Sep 28, 2017

Conversation

lex111
Copy link
Member

@lex111 lex111 commented Sep 25, 2017

So now the lazy loading of tabs works, then probably it makes sense to display the number of notifications in the tabbar.

This is the early version, you still need to deduct the notifications that were read by the repository (you need to determine how many they need to be deducted, and do it by analogy here).

Quick demo:

If there are more than 99 notifications, 99+ will be displayed.

Also I had an idea with auto-updates of the number of notifications? Is it necessary?

We can also think about how to make this component differently, more precisely how to connect redux and react-navigation? I had an idea to make a component of the TabBarIcon type with the badgeText property and pass the count there, but it's not clear how to connect redux then to update the badge text.

Feel free to suggest changes, if you want.

@lex111
Copy link
Member Author

lex111 commented Sep 26, 2017

I made update the badge after mark the repo notifications, and also changed a bit of the icon design:

> 99 < 99

How do you like the idea?

@@ -17,6 +17,7 @@ export const colors = {
darkGreen: '#27ae60',
red: '#ee0701',
darkRed: '#e74c3c',
redDarkest: '#da0000',
Copy link
Member

Choose a reason for hiding this comment

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

darkerRed

Copy link
Member

@machour machour left a comment

Choose a reason for hiding this comment

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

Really nice addition!

src/api/index.js Outdated
count = linkHeader.split('=').pop();
}

return +count;
Copy link
Member

Choose a reason for hiding this comment

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

what do +count do ? Oo

Copy link
Member Author

Choose a reason for hiding this comment

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

This convert the value to a number, similarly does the call to parseInt()

Copy link
Member

Choose a reason for hiding this comment

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

Nice, thanks

src/api/index.js Outdated
const response = await fetch(ENDPOINT, accessTokenParameters(accessToken));

let linkHeader = response.headers.get('Link');
let count = 1;
Copy link
Member

Choose a reason for hiding this comment

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

if the Link header is not present, count should be the count of json objects returned in the response, not 1

Copy link
Member

Choose a reason for hiding this comment

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

See how this is handled in v3.count() I just added: 6ce8489#diff-284817f1b6aad47c0ac6d61bd72f474cR58

Copy link
Member Author

Choose a reason for hiding this comment

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

if the Link header is not present, count should be the count of json objects returned in the response, not 1

The fact is that when the notification in the repository is only one, the Link header will not be, and response.json().length will return 0, which is not correct. Therefore, the default is 1.

Copy link
Member Author

Choose a reason for hiding this comment

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

See how this is handled in v3.count() I just added: 6ce8489#diff-284817f1b6aad47c0ac6d61bd72f474cR58

Can you add a third parameter, such as defaultCount?

Copy link
Member

Choose a reason for hiding this comment

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

@lex111 as I understand, here's how the API respond :

  • if you have 0 notifications, response.status is 404 with a json body that must be ignored
  • if you have notifications :
    • if only 1, no Link header and response.json() is an array of 1 element (so json().length = 1)
    • if more, you parse the Link header to get the number

am I missing something?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that's right, I checked again, and found the error that you have now, and because of which I incorrectly put 1

response.json() is a promise, so it should be like this:

number = response.json().then(data => {
  return data.length;
});

Copy link
Member

Choose a reason for hiding this comment

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

OMG, thanks! didn't know response.json() was a promise, I thought only response was.
I'll update count() to handle the 404, fix json().
Do you still think defaultValue is needed? I fail to see how

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you still think defaultValue is needed? I fail to see how

No, at least for now.

Copy link
Member

Choose a reason for hiding this comment

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

just fixed the count() method and realized that json().length isn't needed at all as we request one element per page -_-

So, if 404 => 0 elements
If no Link header => 1 element
Else, count fetched from Link

I wonder if the 404 status code is sent for all empty request responses ..

Copy link
Member

Choose a reason for hiding this comment

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

(let's chat over gitter if you want)

@lex111
Copy link
Member Author

lex111 commented Sep 26, 2017

I made changes according with the feedback, and now I thought, can show the total number notifications, instead 99+? I did it because I wanted the badge to be always round, and if there is a four-digit number, then it will be oval.

@machour
Copy link
Member

machour commented Sep 26, 2017

I personally think +99 is perfect

@lex111
Copy link
Member Author

lex111 commented Sep 26, 2017

Here is an example of how to make a badge:

image

/>

{notificationsCount
? <View style={styles.badge}>
Copy link
Member

Choose a reason for hiding this comment

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

Hey @lex111, maybe we could use from react-native-elements which is already bundled :
https://react-native-training.github.io/react-native-elements/API/badge/

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not round, I like that currenly it's just round in shape, well okay

Copy link
Member

Choose a reason for hiding this comment

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

Fair enough, if you would rather have a circular badge and think it looks neater I don't mind :)

I think we can do one of two things however:

  1. Could we have a local badge component that contains the style for the badge and expose a background color and text color prop? Feels nice to have that exposed in it's own component. Would prefer if you call it circular-badge or something along those lines to not name-conflict with the react native elements badge component
  2. OR we could use React Native Elements badge here and just apply containerStyle to make it circular.

I'm okay with either option 🙌

Copy link
Member Author

Choose a reason for hiding this comment

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

Strangely, I can not import this component:

import { Icon, Badge } from 'react-native-elements';

console.log(Badge); // undefined

I do not understand what's up, okay, I'll make a local component then.

@@ -86,8 +89,11 @@ class Events extends Component {
}
}

getUserEvents = (user = this.props.user) => {
getUserEvents = () => {
Copy link
Member

Choose a reason for hiding this comment

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

We're passing a user into this function on lines 82 and 88, and this would be therefore ignoring it. Maybe something like this will work?

getUserEvents = ({ user, accessToken } = this.props) => {
    // ...
}

While changing lines 82 and 88 to pass in this.props and nextProps respectively.

Copy link
Member Author

@lex111 lex111 Sep 27, 2017

Choose a reason for hiding this comment

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

I tried to do it, it did not work. In componentWillReceiveProps this will work.

Copy link
Member

Choose a reason for hiding this comment

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

Agree with @andrewda. Line 82 shouldn't matter since it's already defaulting to this.props.user (would prefer if you can remove the argument call in there (line 82) if you can too :)

But if you're removing the arguments entirely from the function declaration in line 92, line 88 doesn't look it would work at all. We're passing in nextProps.user but in here it's not being used at all..

Curious why

getUserEvents = ({ user, accessToken } = this.props) => {
    // ...
}

doesn't work? Destructuring and applying a default should work if I'm not mistaken

Copy link
Member

@housseindjirdeh housseindjirdeh left a comment

Choose a reason for hiding this comment

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

Beautiful stuff @lex111. Can't tell you how much I appreciate you always finding things to be improved in the app. Thank you.

Left a few comments but nothing major 👍

@@ -86,8 +89,11 @@ class Events extends Component {
}
}

getUserEvents = (user = this.props.user) => {
getUserEvents = () => {
Copy link
Member

Choose a reason for hiding this comment

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

Agree with @andrewda. Line 82 shouldn't matter since it's already defaulting to this.props.user (would prefer if you can remove the argument call in there (line 82) if you can too :)

But if you're removing the arguments entirely from the function declaration in line 92, line 88 doesn't look it would work at all. We're passing in nextProps.user but in here it's not being used at all..

Curious why

getUserEvents = ({ user, accessToken } = this.props) => {
    // ...
}

doesn't work? Destructuring and applying a default should work if I'm not mistaken

/>

{notificationsCount
? <View style={styles.badge}>
Copy link
Member

Choose a reason for hiding this comment

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

Fair enough, if you would rather have a circular badge and think it looks neater I don't mind :)

I think we can do one of two things however:

  1. Could we have a local badge component that contains the style for the badge and expose a background color and text color prop? Feels nice to have that exposed in it's own component. Would prefer if you call it circular-badge or something along those lines to not name-conflict with the react native elements badge component
  2. OR we could use React Native Elements badge here and just apply containerStyle to make it circular.

I'm okay with either option 🙌

@@ -163,6 +167,8 @@ class Notifications extends Component {
} = this.props;
const { type } = this.state;

this.props.getNotificationsCount();
Copy link
Member

Choose a reason for hiding this comment

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

Could we have this as part of our destructured object up above:

     const {
       getUnreadNotifications,
       getParticipatingNotifications,
       getAllNotifications,
       getNotificationsCount
      } = this.props;

So we can just call getNotificationsCount() here

@@ -145,6 +148,7 @@ class Notifications extends Component {
this.props.getUnreadNotifications();
Copy link
Member

Choose a reason for hiding this comment

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

Would prefer is we destructure here as well (I know this is slightly out of your scope however so don't worry if you would rather not):

     const {
       getUnreadNotifications,
       getParticipatingNotifications,
       getAllNotifications,
       getNotificationsCount
      } = this.props;

And then Promise.all all the functions instead of calling them one by one

Copy link
Member Author

Choose a reason for hiding this comment

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

Done except promises, how can they help?

Copy link
Member

Choose a reason for hiding this comment

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

No worries, ideally Promise.all is used to make sure a collection of promises are complete before submitting an event after them but since we're not really doing anything after each of these specifically - I think it's okay if we don't include it here :)

@lex111
Copy link
Member Author

lex111 commented Sep 28, 2017

Ready for review.

Copy link
Member

@housseindjirdeh housseindjirdeh left a comment

Choose a reason for hiding this comment

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

Works absolutely lovely mate 👏 Thank you!!!!!

Copy link
Member

@housseindjirdeh housseindjirdeh left a comment

Choose a reason for hiding this comment

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

Just noticed one tiny thing to change, otherwise this baby works wonderfully :)

componentDidMount() {
if (this.props.user.login) {
this.getUserEvents(this.props.user);
componentDidMount({ user } = this.props) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should be including props as an argument to componentDidMount. We never call this method as a function anywhere in the component so there's no need to set an argument and define it's default (seems redundant).

Ideally, we can just destructure within the method itself:

componentDidMount() {
    const { user } = this.props;
   // ....
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, that's right, I fixed it.

Copy link
Member

@housseindjirdeh housseindjirdeh left a comment

Choose a reason for hiding this comment

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

Cheers, thanks so much for being on the ball and responding to change requests so quickly 🙌

@housseindjirdeh housseindjirdeh merged commit ec4610b into master Sep 28, 2017
@lex111 lex111 deleted the notification-tabbar-icon branch September 28, 2017 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants