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

[android] Share to Zulip from other apps [part 2] #4757

Merged
merged 11 commits into from Jul 19, 2021

Conversation

AkashDhiman
Copy link
Member

Fixes: #117
Continuation of work done in #4514

The previous PR thread was getting too long, so as suggested by @gnprice It is now broken into two parts.
To see the screen capture of the demonstration of this feature checkout #4514.
Reviews on that thread by @chrisbobbe: #4514 (review)
Reviews on that thread by @gnprice: #4514 (review)

Note that the specific commit that fixes #117 is present in this PR and not #4514.

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

Thanks @AkashDhiman for these changes!

I've now read through the first 4/7 commits:

db8038d sharing: Make the app not quit after share to zulip concludes.
1a0be67 sharing: Set appropriate filename of shared content.
bd52bce android: Enable receiving 'shares' from other apps.
61c1e45 sharing: Add functionality to share multiple files to zulip android.

and I'll leave it there for this round of review.

That leaves these commits for a future round:

1ddc8ac sharing: Display file name under content preview.
41975a5 sharing: Show preview for non image files.
536f0e3 sharing: Enable users to delete attachments before sending.

Comment on lines 29 to 33
export const navigateToChat = (narrow: Narrow): GenericNavigationAction =>
StackActions.push('chat', { narrow, editMessage: null });

export const replaceWithChat = (narrow: Narrow): GenericNavigationAction =>
StackActions.replace('chat', { narrow });
Copy link
Member

Choose a reason for hiding this comment

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

This should pass editMessage: null, same as the existing navigateToChat does.

The reason is that ChatScreen expects that:

// ChatScreen.js
type Props = $ReadOnly<{|
  navigation: AppNavigationProp<'chat'>,
  route: RouteProp<'chat', {| narrow: Narrow, editMessage: EditMessage | null |}>,
|}>;

Note editMessage is a required property there.

This is exactly the kind of thing that in general the type-checker is supposed to mean you don't have to worry about 🙂 -- if Flow accepts the code, then it's good. All these actions in navActions.js represent an exception to that happy general rule, because react-navigation doesn't make it easy to connect the dots between the argument 'chat' here and the route-params type expected by ChatScreen over there. So whenever adding or changing something in navActions.js, or changing the route-params type on a screen, we have to manually check that the types line up.

I should add a comment in navActions.js saying that, because I see there isn't one; perhaps also something in the style guide or other docs. Also I should try to just solve that problem -- react-navigation doesn't make it easy, but I think it still can be done.

Comment on lines 73 to 75
route: RouteProp<'share-to-pm', {| sharedData: SharedData |}>,

ownUserId: UserId,
dispatch: Dispatch,
Copy link
Member

Choose a reason for hiding this comment

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

nit: keep the blank line -- it visually separates the nav props from the rest.

(In general, where there's an existing blank line it's best to keep it unless you have a reason it shouldn't be there anymore.)

Comment on lines 3 to 5
import { View, Image, ScrollView, Modal, BackHandler } from 'react-native';

import { View, ImageBackground, ScrollView, Modal, FlatList, Text } from 'react-native';
import type { RouteProp } from '../react-navigation';
Copy link
Member

Choose a reason for hiding this comment

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

nit: keep blank line between third-party imports and ours

Also an example of the same general rule as in the previous comment 😉

Comment on lines 74 to 76
ownUserId: UserId,
dispatch: Dispatch,
auth: Auth,
Copy link
Member

Choose a reason for hiding this comment

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

nit: keep auth and ownUserId in the same order here as in the connect call (probably alphabetical).

Also keep dispatch first, as it currently is; that's where we typically put it among props gotten from connect. The dispatch: Dispatch is super boring, and writing it the same way every time helps let the boring thing fade appropriately into the background 🙂

Comment on lines 108 to 134
await handleSend(data, auth, _);
this.finishShare();
try {
await handleSend(data, auth, _);
this.shareSuccess();
} catch (err) {
this.finishShare();
}
};
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, the name finishShare no longer seems appropriate -- this now isn't something we do after successfully sending, to finish off the sharing, but instead is something we do if sharing failed.

Perhaps a name like onSendError?

Copy link
Member

Choose a reason for hiding this comment

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

Oh gosh I see, and this exception case shouldn't even happen, because here's the spot in handleSend where we make the API call:

  try {
    await sendMessage(auth, messageData);
  } catch (err) {
    showToast(_('Failed to send message'));
    logging.error(err);
    return;
  }
  showToast(_('Message sent'));

(Where that sendMessage should be api.sendMessage -- it's an API call. I probably hadn't written this style-guide entry yet when that line was added, and I guess I let it slip through in code review.)

That explains the existing behavior you describe in the commit message:

Previously after content was shared to zulip the app would quit,
regardless of the fact that the content was shared successfully or
failed to share, …

That code is, on its own, doing a fine job of the pattern I described in this style-guide entry I just wrote yesterday:
https://github.com/zulip/zulip-mobile/blob/master/docs/style.md#catch-at-ui
i.e. of catching the exception at the level of UI code, and informing the user accordingly.

But then basically what's happening in this commit is that you want to introduce another way that the UI responds to the difference between success and failure: you want to navigate one way on success, and another way on failure.

Does the new logic in this version actually work as intended? It looks like if the API request fails, the handleSend call will show an error toast but then return, and so this code will proceed to this.shareSuccess() which will navigate to the relevant narrow.

To implement this logic correctly, we probably want a single try/catch. Both this function and its callee handleSend are plausible sites for that -- they're both UI code that's responsible for this interaction. I think probably the cleanest way would be to have handleSend take care of it; this caller can pass in a Narrow object for it to use.

Copy link
Member

Choose a reason for hiding this comment

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

(One reason it's probably cleanest to have handleSend take care of this is that as I was looking at this code, another thing I was thinking is that more of this logic should be pushed into code that's shared between ShareToPm and ShareToStream -- there's a fair amount that looks duplicate. That's mostly pre-existing before this PR, but would be good not to regress on.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Does the new logic in this version actually work as intended? It looks like if the API request fails, the handleSend call will show an error toast but then return, and so this code will proceed to this.shareSuccess() which will navigate to the relevant narrow.

Yeah, it works as you describe, I must have not tested it.

In the new revision I have significantly changed this code, (as part of de-duplicating some of the code b/w ShareToPm and ShareToStream) and so effectively this part of the code now uses only 1 try catch.

params.putString("type", "text")
params.putString("sharedText", sharedText)
}
intent.type?.startsWith("image/") == true -> {
Copy link
Member

Choose a reason for hiding this comment

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

Hmm interesting, so this commit changes the way we detect whether the item should be called an "image" or a "file".

Previously we'd look at the type property of the Intent, which I guess is a MIME type, and see if it looked like image/foo.

Now we instead take each URL, and go ask the content resolver about it with getType, and pass that on.

Can you make that change first, before adding the SEND_MULTIPLE case? I think that would help split up this commit's changes so they can individually be understood.

Do you believe that for the SEND case, the new logic has the exact same behavior? I.e. that intent.type is exactly the same as contentResolver.getType(url). That would be helpful to know if so (and you can mark that commit as NFC to express that), or if not it'd be helpful to know what changes are expected.

Copy link
Member Author

Choose a reason for hiding this comment

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

contentResolver.getType has the potential to return null, so in the new revision I am handling this in kotlin code itself although I am not sure if this is the right way, it returns null only when the url provided is broken, this shouldn't really happen with SEND_INTENT but just in case it does, I give a fallback mimeType for octet-stream.

implementation in master branch deduces types by directly analyzing the send intent, but this won't be possible in case of multiple files, it will just be / in that case, wonder what will happen there if a broken url is provided.

singleContent.putString("type", cr.getType(url))
singleContent.putString("fileName", getFileName(url, cr))
content.pushMap(singleContent)
} else if (urls != null) {
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, what if neither url nor urls is non-null? Or what if both of them are?

Is the point here that url should always be present on SEND, and url should always be present on SEND_MULTIPLE? If so, it'd be best to make that explicit -- instead of conditioning on which of these extras are present on the intent, we can switch on intent.action and then assert that the expected extra is present, throwing an exception like the existing code does.

Comment on lines 130 to 134
val singleContent = Arguments.createMap()
singleContent.putString("url", u.toString())
singleContent.putString("type", cr.getType(u))
singleContent.putString("fileName", getFileName(u, cr))
content.pushMap(singleContent)
Copy link
Member

Choose a reason for hiding this comment

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

This is exactly the same logic as in the url != null case above, right? Let's deduplicate it. Can just make a little helper method that takes url and contentResolver as arguments, and returns singleContent.

src/sharing/ShareToPm.js Outdated Show resolved Hide resolved
Comment on lines 159 to 165
renderItem = ({ item, index, separators }) =>
item.type.startsWith('image') ? (
<Image source={{ uri: item.url }} style={styles.imagePreview} />
) : (
<></>
);

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, it's a shame to have to be making all these changes twice. And it makes it likely that as we keep making changes, things will unintentionally diverge.

Would you try to deduplicate some of this code, in commits that come before the ones where you extend it?

It might be as simple as making a component with a name like SharedContentPreview, which would contain (initially, before your change to handle multiple files) just the <Image … /> line and the conditional on sharedData.type that controls it.

Basically any code you're going to add more logic to, and that's already duplicated, would be good to deduplicate first so you can then add the logic only once. (Ideally it should have been deduplicated in the first place; oh well 🤷 )

Copy link
Member Author

Choose a reason for hiding this comment

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

I've made a ShareWrapper method, that deduplicates this and a lot more. Initially I went with just SharedContentPreview but there was a lots of de-duplication potential in both the screens. I wonder if this can have any downside though 🤔.

@gnprice gnprice added a-Android a-compose/send Compose box, autocomplete, camera/upload, outbox, sending P1 high-priority labels May 21, 2021
@AkashDhiman AkashDhiman force-pushed the dev-share-to-zulip branch 2 times, most recently from e3e06f8 to 695de4f Compare July 14, 2021 00:38
@AkashDhiman
Copy link
Member Author

AkashDhiman commented Jul 14, 2021

@gnprice I've made the revision you asked, and did a bit more.

  • I've done as much of deduplication as possible between ShareToStream and ShareToPm.
  • I've moved 1 commit about displaying file name under content preview, earlier than the previous revision.
  • I've noticed that getFileName and getType have the potential to return null, this will take place only when provided with an invalid url and here I am essentially falling back to displaying unknown.bin file in such a case.
  • Then there is 1 concern that I will make an inline comment for.

Comment on lines -107 to -109
this.setSending();
await handleSend(data, auth, _);
this.finishShare();
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 can result in an unhandled promise rejection right? should this be wrapped in a try catch? I am using this inside handleSend of the ShareWrapper introduced in the current revision as well and haven't wrapped it in try catch just yet, wanted someone's opinion on this.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, thanks for catching that.

In the current revision, it looks like there are two awaits in handleSend. You have a try/catch around the one for api.sendMessage, so I think that one is taken care of. It'd be good to add another try/catch around the await line that does api.uploadFile, too.

src/sharing/ShareWrapper.js Show resolved Hide resolved
Comment on lines 53 to 55
stream: string,
topic: string,
type: 'stream',
Copy link
Member

Choose a reason for hiding this comment

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

nit: when there's a type-tag like this type property, put it first in the list. That's because that's always the property you want to look at first, to determine which other properties will even be present and what they'd mean.

Copy link
Member

Choose a reason for hiding this comment

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

Oh I see, this is based on types that were previously in src/sharing/send.js. Well, reordering these would be a nice tiny cleanup to tack on, even at the end of the branch.

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, made a separate commit together with making a SendTo type.

});

type SendStream = {|
stream: string,
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, yikes, is the existing code here relying on stream names instead of IDs? ... Yep, it is. Well, fixing that is out of scope for this PR. But it would be a good followup while you're in this code.

Copy link
Member Author

Choose a reason for hiding this comment

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

will keep this in mind for followup issues.

Comment on lines 52 to 61
type SendStream = {|
stream: string,
topic: string,
type: 'stream',
|};

type SendPm = {|
selectedRecipients: $ReadOnlyArray<UserId>,
type: 'pm',
|};
Copy link
Member

Choose a reason for hiding this comment

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

I think we don't even need names for these two subtypes -- we can just have a single definition for their union.

Then you can write it real compactly:

type SendTo =
  | {| type: 'stream', stream: string, topic: string |}
  | {| type: 'pm', selectedRecipients: $ReadOnlyArray<UserId> |};

Oh, but this is based on types from send.js. Yeah, for moving those it's definitely best to leave them in a similar format -- there's plenty happening in that commit, so best not to add more changes than necessary. Would be a nice small cleanup later; could squash it with reordering type to go first.


return (
<>
<ScrollView style={styles.wrapper} keyboardShouldPersistTaps="always" nestedScrollEnabled>
Copy link
Member

Choose a reason for hiding this comment

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

This nestedScrollEnabled looks like potentially a change that we care about one way or the other. It wasn't set on the ShareToPm side, and isn't set in the new wrapper. Is this something we do want set, or don't?

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 is necessary. Fixed in the new revision.

Copy link
Member

@gnprice gnprice Jul 15, 2021

Choose a reason for hiding this comment

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

Cool. The new revision sets that prop in the new wrapper.

That does still make this change not NFC, because previously it wasn't set for ShareToPm and now it is. Two ways to fix that:

  • The cleanest is to have a small commit first that adds it on ShareToPm and says why. Then the main commit can be NFC.
  • Alternatively the main commit could drop the "[nfc]", and mention this change in the commit message. That wouldn't normally meet our standards for highly readable commits, but in this case it'd be OK.

I think the cleaner thing is actually pretty quick to do, though, with the right tools. (Much easier than splitting up the bigger changes in this commit into several steps would be.) So I'd encourage you to try it. I'd use git rebase -i, with something like:

$ gitk --all HEAD &  # to see what you're doing
$ git rebase -i
  # insert a 'break' line after this "Deduplicate code" commit
  # save and exit; now you're mid-rebase, and at this commit
$ git reset --hard @~  # reset to before this commit
  # now edit ShareToPm.js to change that one line; save that
$ git commit -am 'wip add nestedScrollEnabled'
$ git restore -s @{2} .  # get to the same state as after this commit
$ git commit -aC @{2}  # borrow commit message, and author name+date, from this commit
$ git rebase --continue

The two references to @{2} there are a way of mentioning the "Deduplicate code" commit -- @{0} is the current HEAD, @{1} is what it was before this (before the git commit), and @{2} is what it was before that (before the git reset). Alternatively, you could look at the gitk --all HEAD or a git log command, and copy-paste the commit ID.

Then after that:

  • refresh in gitk, to check your work;
  • use git rebase -i and just a reword on the new commit, to replace the placeholder message with a proper one.

src/sharing/ShareWrapper.js Outdated Show resolved Hide resolved
src/sharing/ShareWrapper.js Outdated Show resolved Hide resolved
Comment on lines +157 to +150
switch (sendTo.type) {
case 'pm': {
Copy link
Member

Choose a reason for hiding this comment

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

Cool, this is a good way to unpack this object.

Comment on lines 174 to 175
default:
this.onShareTerminated();
Copy link
Member

Choose a reason for hiding this comment

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

This case should be impossible, right? It'd be a bug in the app, in fact a bug within the sharing code, if we were to reach this case.

I think treating it as failure is a fine response in the UI, since that's handy. (It'd also be OK to just throw an exception, if there weren't something more specific to do instead that was handy.) But then the other thing we should do is log an error with logging.error, so that we see it in Sentry.

Separately, the first thing this case should do is call ensureUnreachable. That way Flow will confirm that our types say this case really is impossible.

Here's an example at the end of handleOutboundEvents.js:

    default:
      ensureUnreachable(event);
      logging.error(`WebView event of unknown type: ${event.type}`);
      break;

Comment on lines 377 to 406
export type SharedImage = {|
type: 'image',
sharedImageUrl: string,
fileName: string,
|};

export type SharedFile = {|
type: 'file',
sharedFileUrl: string,
fileName: string,
|};
Copy link
Member

Choose a reason for hiding this comment

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

Thanks!

on a side note this makes me wonder why notification has its own separate types.js (why not include it in the main types?)

Good question. Two reasons:

  • It's generally more helpful to have the code organized primarily by what general subsystem it's part of -- like notifications, or sharing, or the server API -- rather than by what kind of code it is, like is it type definitions. For example when you're working on notifications, you might be touching a bunch of different notifications-related files; but it's less common that you'd be working on "types, in general", and be touching a bunch of different types-related files.
  • Having a single global "types" file tends to create big import cycles. Those can be bad for a variety of reasons; they tend to make the codebase harder for both tools and people to work with. Here's an example we ran into: https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/.23M3745.20Server.20version.20to.20Sentry/near/930319

@gnprice
Copy link
Member

gnprice commented Jul 15, 2021

Partway through reviewing, but dinnertime here 🙂

@AkashDhiman
Copy link
Member Author

@gnprice made the changes, you may see them.

@gnprice
Copy link
Member

gnprice commented Jul 15, 2021

Thanks for the fast revision! Reading now.

One thought I meant to put in the overall review comment for yesterday's review (but then didn't think of when I was submitting the partly-done review):

The second commit here, the one deduplicating the sharing code into the new SharingWrapper, has kind of a lot of things going on. Typically I'd ask for a commit like that to be broken up into smaller pieces, because that makes it easier to read all the changes closely and really verify they're not changing anything, or changing only things we intend to change.

  • For example, one commit could make SharingWrapper and give it a handleSend that still looks like the handleSend on the two old components, and then a later commit does the part where the separate handleSend helper function gets inlined into that.

When I'm doing a refactor myself, I'll generally write it more or less that way in the first place. I find that helps a lot for being pretty sure I'm changing only the things I intend to change -- and especially for doing large or complex refactors. This involves doing git commit -a frequently, at basically every step, often with short placeholder commit messages; and then doing a fair amount of git rebase -i at the end, to reorder the changes, to squash later changes that were fixes to earlier ones, and to fill out the commit messages that I didn't fully write the first time.

  • The frequent git commit -am 'wip add WidgetFrobber' is easy, and often very helpful even just for writing the code. I think I would do that even if I were ultimately going to squash everything into one commit.
  • The git rebase -i takes some effort, though it gets faster with practice. We do this for the sake of people reading the changes.

But... the existing code isn't live code that people are using, so it's not like we have a presumption that it currently works despite being tangly. So it's actually OK if the behavior changes, as long as we think the new behavior is fine. It's not even important (like it usually would be) that we understand what the old behavior was and why it was that way.

So I have some questions where I do notice what look like changes. But in this case I won't ask you to do the work to split the commit up into smaller pieces; and for those changes, it's fine for the answer to be that you tested that aspect of the new behavior and it seems good.

Comment on lines +21 to +23
type SendTo =
| {| type: 'pm', selectedRecipients: $ReadOnlyArray<UserId> |}
| {| type: 'stream', stream: string, topic: string |};
Copy link
Member

Choose a reason for hiding this comment

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

👍

Comment on lines 168 to 170
default:
ensureUnreachable(sendTo.type);
logging.error('Unknown type encountered in `sendTo`.');
}
Copy link
Member

Choose a reason for hiding this comment

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

Cool. (Continues this thread: #4757 (comment) .)

We do actually want the onShareCancelled call too, though. In general, because our types aren't guarantees, when we explicitly get into a case that should be impossible, we want to do something to bail out of the part of the code that's clearly confused. Throwing an exception is the default solution, but here onShareCancelled is handy and would be better.

Comment on lines -159 to -163
<View style={styles.container}>
{sharedData.type === 'image' && (
<Image source={{ uri: sharedData.sharedImageUrl }} style={styles.imagePreview} />
)}
<AnimatedScaleComponent visible={isStreamFocused}>
Copy link
Member

Choose a reason for hiding this comment

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

Cool, sounds good.

As described at #4757 (comment) , for this particular refactor, if this sort of thing seems to work in the new version, then that's good enough. If we find some issue in the layout later, we can fix it then.

@gnprice
Copy link
Member

gnprice commented Jul 15, 2021

OK, finished re-reviewing the parts you just revised! (Including a couple of replies in the threads above.) That covers the first 6 commits:

af4e9a9 translation: Add some english words used in sharing screen.
91ef465 sharing [nfc]: Deduplicate code b/w Pm and Stream sharing screens.
beb81e7 sharing [nfc]: Write type of sendTo more aptly.
1c8a526 sharing [nfc]: Move helper kotlin code to a new file.
f8f2c15 sharing: Make the app not quit after share to zulip concludes.
ba0c1e4 sharing [nfc]: Add doc about synchronising native and js data type.

There are a few small things left to fix; I think we're quite close to merging those.

Next I'll take a look at the next few commits, corresponding to the remaining commits I reviewed the first time and that you revised this week.

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

OK, read the next few. Just a few comments -- see below and replies above.

That covers the next four commits:
5695197 sharing: Set appropriate filename of shared content.
fe9463d sharing: Show preview for non image files.
1d09fd1 sharing: Display file name under content preview.
63a6154 android: Enable receiving 'shares' from other apps.

including actually enabling the feature! 🎉 Excited to be close to merging that.

Then I think probably the easiest workflow is I'll merge this PR with just the changes up to that point, and please post the remaining changes:
889d73c sharing: Change SharedFile.
d022622 sharing: Add functionality to share multiple files to zulip android.
787bd00 sharing: Enable users to delete attachments before sending.

as a followup PR -- basically a PR adding the feature of sharing multiple files. There are a couple of subthreads above that will continue on that PR, but that'll just help keep the GitHub interface more manageable as this thread gets long.

@AkashDhiman
Copy link
Member Author

AkashDhiman commented Jul 19, 2021

@gnprice done resolving, you can check. I will also make the PR for Multiple Share once this gets merged.

I see from this review that I should focus on splitting up my commits some more, and I will keep that in mind. One reason for doing this is that I do not notice this sometimes (which I will try to improve upon) but also that my thought process was that associated code should change together to convey the meaning of why we did that change in the first place; moreover sometimes (less frequently though) it can lead to doing extra changes to accommodate the changes introduced in the prep commit that will be changed in the next commit regardless, which feels unnecessary.

But I understand your point that this increases the chances of us catching side effects we may have from this prep code and it makes for a better code review experience so I will try to do this as much as possible. Thanks!

@gnprice
Copy link
Member

gnprice commented Jul 19, 2021

Thanks for the revision! All looks good, except that it looks like send.js got revived in this one (i.e. it's no longer deleted in the deduplication commit.) I'll fix that and merge.

This was required in case of `ShareToStream` and having it here
will allow us to deduplicate the code more cleanly.
Deduplication done as much as possible, by creating a `ShareWrapper`
component which wraps around the components differing in `ShareToPm`
and `ShareToStream`.

`ShareWrapper` contains the common stylesheet, components and the
logic to send/cancel shared data.

To do this:

* The common components were identified in render method of
both `ShareToPm` and `ShareToStream`, they were moved into the file
`src/sharing/ShareWrapper.js`.
* Associated data was also moved and removed from both the files.
* `handleSend` method was modified to contain the code of
  `handleSend` function present in `src/sharing/send.js`.
* This file was present only for code deduplication purposes and
  hence was no longer relevant, therefore it was deleted.
This removes `SendStream` and `SendPm`, and makes a Union type with
their properties called `SendTo`.

This also involves reordering properties of the same to keep `type`
at the top; it is better this way since it dictates what other
members will be present.
New file created is called `sharing.SharingHelper.kt`, it contains
all helper methods related to handling SEND intent.

Signature of `handleSend` and `maybeHandleIntent` were required to
be changed during migration. They contain an extra parameter
`application: ReactApplication`.

MainActivity is modified accordingly to pass this parameter.
Previously after content was shared to zulip the app would quit,
regardless of the fact that the content was shared successfully or
failed to share, now the behaviour is as follows:
- If user cancels sharing/sharing fails, user is returned back
  to the previous view.
- If app was started from share intent, user returns to home screen
  of zulip.
- If share is successful user is navigated to chat screen where the
  content was shared.
Previously filename was the last element of
sharedFileUrl.split('/') or sharedImageUrl.split('/').

This did not always correspond to the actual filename, sometimes
this element did not contain a file extension making the uploaded
data to be stored as a binary file. This made shared images fail
to render and on downloading the shared content it was downloaded
as a binary file.

This commit uses ContentResolver to find the actual name of file
being shared and uses that to identify the shared file.

`getFileName` introduced in this commit is based on:
- https://developer.android.com/training/secure-file-sharing/retrieve-info.html

the query method used in `getFileName` has the potential to return
null in case of invalid url, we are handling it by setting our own
name "unknown.<extension>" <extension> being the one that was found
by contentResolver.getType, if that fails too then we just set it
to `.bin`.
Previously non image files didn't have any preview in the sharing
screen. This resulted in confusion where a user was not able to
tell if the file was indead attached or not.
This is especially helpful for Non Image files, and will prove
necessary when sharing multiple files.
All concerning bugs preventing the launch of this issue have been
fixed in the previous commits.

This does not include Sharing Multiple Files.

Fixes: zulip#117
@gnprice gnprice merged commit 2f3b667 into zulip:master Jul 19, 2021
@gnprice
Copy link
Member

gnprice commented Jul 19, 2021

my thought process was that associated code should change together to convey the meaning of why we did that change in the first place

I started writing some thoughts in reply to this, and then realized it'd be better as a chat thread 🙂 So I'll do that.

edit: https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/splitting.2Fsquashing.20commits/near/1234248

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a-Android a-compose/send Compose box, autocomplete, camera/upload, outbox, sending a-share-to Sharing photos/etc. into Zulip P1 high-priority
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Handle sharing from other apps (Android)
2 participants