The Wayback Machine - https://web.archive.org/web/20200928163533/https://github.com/wordpress-mobile/WordPress-iOS/pull/6521
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

Support publicize on posting #6521

Merged

Conversation

@elibud
Copy link
Contributor

elibud commented Jan 31, 2017

Fixes #2698

To test:

1 - Configure at least one Publicize Connection for a Blog
2 - Create a new Post
3 - Navigate to the Post Settings
4 - Edit the Publicize Message
5 - Enable/Disable some connections for the Post
6 - Publish the Post and check that it's shared to the selected Publicize Connections with the correct message

When sharing is not supported or there are no Connections setup the Sharing section is completely hidden.
If the Post has been published the Sharing section and its items are disabled.

Needs review: @jleandroperez @kurzee @koke
CC: @sendhil

Some background to understand the implementation

How the API handles enabling/disabling Publicize Connections:

Fetching disabled connections
Disabled publicize connections are returned for every post in the metadata section:

...
"metadata": [ 
    {
        "id": "270",
        "key": "_wpas_skip_16508268",
        "value": "1"
    }
],
....

The previous excerpt shows that the connection with connectinoKeyringID = 16508268 is disabled for that post.

Updating disabled connections

This is done in the same way as with any other metadata entry.

POST data to disable a connection

...
"metadata": [ 
    {
        "key": "_wpas_skip_16508268",
        "value": "1",
        "operation": "add"
    }
],
....

POST data to enable a connection

...
"metadata": [ 
    {
        "id": "270",
        "key": "_wpas_skip_16508268",
        "value": "0",
        "operation": "update"
    }
],
....

Please note that in order to re-enable a connection it is necessary to issue an update operation with the previously retrieved id and "0" as the value. This is why it is not enough to just store the disabled connection keyringIDs.
Calypso currently issues a delete operation to accomplish this and it does not work (Automattic/wp-calypso#11099).

@kurzee
Copy link
Contributor

kurzee commented Feb 1, 2017

@elibud this has some conflicts now after merging in #6523

@kurzee
Copy link
Contributor

kurzee commented Feb 1, 2017

@elibud one quick note on this, for future PRs, (this one can stay as is). It's best to try and divide up commits into any kind of logical separation between change-sets. So a couple of examples for this one, it could have been a commit for model additions, CoreData upgrade, service additions, and then UI support, followed by bug fixes.

elibud added 4 commits Feb 1, 2017
…rt-publicize-on-posting

# Conflicts:
#	WordPress/WordPressTest/PostTests.swift
Renamed the methods that return a cell to be consistent among themselves and to use different cell identifiers
@elibud elibud changed the title Support publicize on posting [Not ready for review yet] Feb 2, 2017
@elibud
Copy link
Contributor Author

elibud commented Feb 2, 2017

Needs review: @jleandroperez @koke

@kurzee
Copy link
Contributor

kurzee commented Feb 2, 2017

@elibud looks like more conflicts from the Excerpt merge.

@@ -766,32 +766,40 @@ - (UITableViewCell *)configureFeaturedImageCellForIndexPath:(NSIndexPath *)index
- (UITableViewCell *)configureShareCellForIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
BOOL canEditSharing = ![self.apost.status isEqualToString:PostStatusPublish];

This comment has been minimized.

@kurzee

kurzee Feb 2, 2017 Contributor

For this, we actually use these statuses as more of what status the post will have versus what it currently has. For example, since this change, creating a new post and going to post options renders the Publicize sections disabled since the post is actually unpublished, but carries the publish status. (confusing I know)

# Conflicts:
#	WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m
#	WordPress/Classes/ViewRelated/Post/PostSettingsViewController_Internal.h
#	WordPress/WordPressTest/PostTests.swift
func enablePublicizeConnectionWithKeyringID(_ keyringID: NSNumber) {
if let _ = disabledPublicizeConnections?[keyringID] {
if let _ = disabledPublicizeConnections?[keyringID]?["id"] {
disabledPublicizeConnections?[keyringID]!["value"] = "0"

This comment has been minimized.

@kurzee

kurzee Feb 2, 2017 Contributor

Any reason not to set a if let connection = and carry it down through the blocks?

This comment has been minimized.

@elibud

elibud Feb 2, 2017 Author Contributor

As it's a swift dictionary it's really a struct, so I have to make the assignments in the dictionary itself. However it can be prettier than this, fixed already.

}

func disablePublicizeConnectionWithKeyringID(_ keyringID: NSNumber) {
if let _ = disabledPublicizeConnections?[keyringID] {

This comment has been minimized.

@kurzee

kurzee Feb 2, 2017 Contributor

Same with this one, let's rewrite to carry on let = var declarations.

This comment has been minimized.

@elibud

elibud Feb 2, 2017 Author Contributor

Same as the one above.

@kurzee
Copy link
Contributor

kurzee commented Feb 2, 2017

Since we're adding a new CoreData model here, go ahead and note the additions and changes in /MIGRATIONS.md.

- (NSInteger)numberOfRowsForShareSection
{
if (self.apost.blog.supportsPublicize && self.publicizeConnections.count > 0) {
return self.publicizeConnections.count + 1;

This comment has been minimized.

@kurzee

kurzee Feb 2, 2017 Contributor

Maybe add a small comment here for what the +1 is for.

This comment has been minimized.

@elibud

elibud Feb 2, 2017 Author Contributor

Added.

NSSortDescriptor *sortExternalNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"externalName"
ascending:YES
selector:@selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortServiceDescriptor, sortExternalNameDescriptor, nil];

This comment has been minimized.

@jleandroperez

jleandroperez Feb 2, 2017 Contributor

Nipticky! (no need to change anything). This feels more swifty!

NSArray *sortDescriptors = @[sortServiceDescriptor, sortExternalNameDescriptor];

This comment has been minimized.

@elibud

elibud Feb 2, 2017 Author Contributor

Force of habit, I like it better that way, I will change it.

This comment has been minimized.

@jleandroperez

jleandroperez Feb 6, 2017 Contributor

Thank you! =D

@@ -4,13 +4,16 @@ import CoreData
extension Post {

@NSManaged var commentCount: NSNumber?
@NSManaged var disabledPublicizeConnections: Dictionary<NSNumber, Dictionary<String, String>>?

This comment has been minimized.

@jleandroperez

jleandroperez Feb 2, 2017 Contributor

Out of curiosity, does [NSNumber: [String: String]] breaks with Core Data?

This comment has been minimized.

@elibud

elibud Feb 2, 2017 Author Contributor

Nope it doesn't and it looks better, I appreciate the style suggestions since I'm not so proficient in Swift as I am in Objective-C

This comment has been minimized.

@jleandroperez

jleandroperez Feb 6, 2017 Contributor

None of us is proficient in Swift... since it's mutating all of the time! Thanks for updating this one!

return true
} else {
return false
}

This comment has been minimized.

@jleandroperez

jleandroperez Feb 2, 2017 Contributor

Unless i'm missing something, you can replace this whole method with one line:

return disabledPublicizeConnections?[keyringID]?["value"] == "1"
if disabledPublicizeConnections == nil {
disabledPublicizeConnections = [NSNumber: [String: String]]()
}
disabledPublicizeConnections?[keyringID] = ["value": "1"]

This comment has been minimized.

@jleandroperez

jleandroperez Feb 2, 2017 Contributor

Can we (maybe) move "value" and "1" / else, to an actual constant?

This comment has been minimized.

@jleandroperez

jleandroperez Feb 2, 2017 Contributor

(Common practise looks something like this):

class X {
   struct Constants {
       static let publicizeValyeKey = "value"
       // ...
   }
}

This comment has been minimized.

@elibud

elibud Feb 2, 2017 Author Contributor

Will do, thanks for the suggestion and example.


if (!NSDictionary(dictionary: disabledPublicizeConnections ?? [:])
.isEqual(to: originalPost.disabledPublicizeConnections ?? [:])) {
return true

This comment has been minimized.

@jleandroperez

jleandroperez Feb 2, 2017 Contributor

Can't Swift dictionaries be compared? (i mean, can we avoid the NSDictionary conversion?)

This comment has been minimized.

@elibud

elibud Feb 3, 2017 Author Contributor

They can as long as their keys and values are equatable, but Dictionary is not equatable so in this case we can't avoid the NSDictionary conversion.

This comment has been minimized.

@koke

koke Feb 3, 2017 Member

Apparently, not nested dictionaries :(
You can compare [String: String], but not [NSNumber: [String: String]].

When you look at the operator it makes sense:

extension Dictionary where Key : Equatable, Value : Equatable {

    public static func ==(lhs: [Key : Value], rhs: [Key : Value]) -> Bool

    public static func !=(lhs: [Key : Value], rhs: [Key : Value]) -> Bool
}

So you can == on [String: String] because both Key and Value are Equatable, but that doesn't make [String: String] Equatable itself. So [NSNumber: [String: String]] can't be compared because it's Value is not Equatable (even though it has a == operator).

What this means is we'd need conditional conformances, which has been accepted, but not landed into Swift yet (Swift 4, maybe?).

This comment has been minimized.

@koke

koke Feb 3, 2017 Member

That said, we could skip the default values :)

if let current = (disabledPublicizeConnections as NSDictionary?),
    let original = originalPost.disabledPublicizeConnections,
    !current.isEqual(to: original) {
    return true
}

This comment has been minimized.

@elibud

elibud Feb 6, 2017 Author Contributor

Wouldn't that prevent detecting changes when disabledPublicizeConnections = nil ?

This comment has been minimized.

@koke

koke Feb 6, 2017 Member

Yep, you're right, I got confused with all the optionals.

for (NSNumber *keyringConnectionId in post.disabledPublicizeConnections.allKeys) {
NSMutableDictionary *disabledConnectionsDictionary = [NSMutableDictionary dictionaryWithCapacity: 3];
disabledConnectionsDictionary[@"key"] = [NSString stringWithFormat:@"_wpas_skip_%@", keyringConnectionId];
[disabledConnectionsDictionary addEntriesFromDictionary:post.disabledPublicizeConnections[keyringConnectionId]];

This comment has been minimized.

@jleandroperez

jleandroperez Feb 2, 2017 Contributor

(Maybe) split this line in two, to ease out readability?

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if ([self tableView:tableView numberOfRowsInSection:section] == 0) {
return 0.01f;

This comment has been minimized.

@jleandroperez

jleandroperez Feb 2, 2017 Contributor

Please, replace 0.01f with CGFLOAT_MIN

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
if ([self tableView:tableView numberOfRowsInSection:section] == 0) {
return 0.01f;

This comment has been minimized.

@jleandroperez

jleandroperez Feb 2, 2017 Contributor

Same as above: CGFLOAT_MIN

if (self.apost.blog.supportsPublicize && self.publicizeConnections.count > 0) {
// One row per publicize connection plus an extra row for the publicze message
return self.publicizeConnections.count + 1;
} else {

This comment has been minimized.

@jleandroperez

jleandroperez Feb 2, 2017 Contributor

No need for else statement, since the return in line 597 will prevent this branch from running

{
static NSString *wpTableViewCellIdentifier = @"wpTableViewCellIdentifier";
WPTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:wpTableViewCellIdentifier];
static NSString *wpTableViewDisclosureCellIdentifier = @"wpTableViewDisclosureCellIdentifier";

This comment has been minimized.

@jleandroperez

jleandroperez Feb 2, 2017 Contributor

Perhaps capitalize the constant?

@@ -4,13 +4,16 @@ import CoreData
extension Post {

@NSManaged var commentCount: NSNumber?
@NSManaged var disabledPublicizeConnections: [NSNumber: [String: String]]?

This comment has been minimized.

@koke

koke Feb 3, 2017 Member

I'm wondering if we can make this non-optional. That would simplify all the optional handling a bit.

This comment has been minimized.

@koke

koke Feb 3, 2017 Member

Bonus: if we could have a simple Connection type instead of [String: String] and get rid of Constants.publicize*, although dealing with Core Data I'm not sure if that'd complicate things even more 😕

This comment has been minimized.

@elibud

elibud Feb 6, 2017 Author Contributor

@koke yes we can make it non-optinal and the optional handling code does improve a lot.
I'm happy to make the change if it's ok with everyone @kurzee @jleandroperez
BTW do you guys have a policy for when an attribute should be non-optional in CoreData? I know by default attributes are crated as optional so maybe that's why most of them are currently this way.

This comment has been minimized.

@koke

koke Feb 6, 2017 Member

I keep forgetting about all the Core Data subtleties 🤦‍♂️
We could make this non-optional in the data model, and non-optional in the code, but @NSManaged makes it kind of a lie. Since you don't init managed objects manually, core data's "optional" is only enforced on validate/save. So while you can be certain that an object fetched from storage will have a non-optional attribute set, you can't have the same guarantee about a new unsaved object, making it less safe. So I think it's probably best to leave it as optional? 🤷‍♂️

This comment has been minimized.

@elibud

elibud Feb 6, 2017 Author Contributor

I absolutely agree, leaving it as optional will be the safest option here.

return disabledPublicizeConnections?[keyringID]?[Constants.publicizeValueKey] == Constants.publicizeDisabledValue
}

func enablePublicizeConnectionWithKeyringID(_ keyringID: NSNumber) {

This comment has been minimized.

@koke

koke Feb 3, 2017 Member

My take on this would be (considering disabledPublicizeConnections non-optional):

func enablePublicizeConnectionWithKeyringID(_ keyringID: NSNumber) {
    guard var connection = disabledPublicizeConnections[keyringID] else {
        return
    }

    guard connection[Constants.publicizeIdKey] != nil else {
        disabledPublicizeConnections.removeValue(forKey: keyringID)
        return
    }

    connection[Constants.publicizeValueKey] = Constants.publicizeEnabledValue
    disabledPublicizeConnections[keyringID] = connection
}

It's less compact, but easier to read IMO :)

selector:@selector(caseInsensitiveCompare:)];
NSSortDescriptor *sortExternalNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"externalName"
ascending:YES
selector:@selector(caseInsensitiveCompare:)];

This comment has been minimized.

@koke

koke Feb 3, 2017 Member

I know the other sorted* methods do the same, but we should be using localizedCaseInsensitiveCompare: for any visible strings. I didn't know better when I wrote that :D

This comment has been minimized.

@elibud

elibud Feb 6, 2017 Author Contributor

We should use localizedCaseInsensitiveCompare: only for the service name, right? We don't want to modify the external account name which is what the user is used to expect as a visible name. Even though the chances of an external name being in the localized strings file there's no point in using localizedCaseInsensitiveCompare: for non localizable strings.

This comment has been minimized.

@koke

koke Feb 6, 2017 Member

Good point. I don't think we're translating service names, so it makes sense to only localize account names 👍

// MARK: - Sharing
func canEditPublicizeSettings() -> Bool {
return !self.hasRemote() || self.status != PostStatusPublish

This comment has been minimized.

@koke

koke Feb 3, 2017 Member

This doesn't cover the case when you edit a draft, tap publish, but the post fails. When you edit the post again and try to publish, this will be false as it hasRemote and the status is publish. I'm not sure we can work around it, since there's no way to know the status on the server, but I wanted to document that case.

@@ -204,6 +242,15 @@ class Post: AbstractPost {

return true
}

if publicizeMessage ?? "" != originalPost.publicizeMessage ?? "" {

This comment has been minimized.

@koke

koke Feb 3, 2017 Member

I think you can drop the ?? "" from both sides.

This comment has been minimized.

@elibud

elibud Feb 6, 2017 Author Contributor

We want the default values there to avoid detecting changes for nil vs the empty string.


if (!NSDictionary(dictionary: disabledPublicizeConnections ?? [:])
.isEqual(to: originalPost.disabledPublicizeConnections ?? [:])) {
return true

This comment has been minimized.

@koke

koke Feb 3, 2017 Member

Apparently, not nested dictionaries :(
You can compare [String: String], but not [NSNumber: [String: String]].

When you look at the operator it makes sense:

extension Dictionary where Key : Equatable, Value : Equatable {

    public static func ==(lhs: [Key : Value], rhs: [Key : Value]) -> Bool

    public static func !=(lhs: [Key : Value], rhs: [Key : Value]) -> Bool
}

So you can == on [String: String] because both Key and Value are Equatable, but that doesn't make [String: String] Equatable itself. So [NSNumber: [String: String]] can't be compared because it's Value is not Equatable (even though it has a == operator).

What this means is we'd need conditional conformances, which has been accepted, but not landed into Swift yet (Swift 4, maybe?).


if (!NSDictionary(dictionary: disabledPublicizeConnections ?? [:])
.isEqual(to: originalPost.disabledPublicizeConnections ?? [:])) {
return true

This comment has been minimized.

@koke

koke Feb 3, 2017 Member

That said, we could skip the default values :)

if let current = (disabledPublicizeConnections as NSDictionary?),
    let original = originalPost.disabledPublicizeConnections,
    !current.isEqual(to: original) {
    return true
}
@astralbodies astralbodies modified the milestones: 7.1, 7.0 Feb 12, 2017
@koke
koke approved these changes Feb 14, 2017
Copy link
Member

koke left a comment

I just tested this and works great, good job on this one @elibud.
I don't see any unaddressed comments, so I'm merging this one 🎉

@koke koke merged commit 9972fd1 into wordpress-mobile:develop Feb 14, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.