Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upSupport publicize on posting #6521
Conversation
|
@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. |
|
Needs review: @jleandroperez @koke |
|
@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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
kurzee
Feb 2, 2017
Contributor
Same with this one, let's rewrite to carry on let = var declarations.
This comment has been minimized.
This comment has been minimized.
|
Since we're adding a new CoreData model here, go ahead and note the additions and changes in |
| - (NSInteger)numberOfRowsForShareSection | ||
| { | ||
| if (self.apost.blog.supportsPublicize && self.publicizeConnections.count > 0) { | ||
| return self.publicizeConnections.count + 1; |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| NSSortDescriptor *sortExternalNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"externalName" | ||
| ascending:YES | ||
| selector:@selector(caseInsensitiveCompare:)]; | ||
| NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortServiceDescriptor, sortExternalNameDescriptor, nil]; |
This comment has been minimized.
This comment has been minimized.
jleandroperez
Feb 2, 2017
Contributor
Nipticky! (no need to change anything). This feels more swifty!
NSArray *sortDescriptors = @[sortServiceDescriptor, sortExternalNameDescriptor];
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| @@ -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.
This comment has been minimized.
jleandroperez
Feb 2, 2017
Contributor
Out of curiosity, does [NSNumber: [String: String]] breaks with Core Data?
This comment has been minimized.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
jleandroperez
Feb 2, 2017
Contributor
Can we (maybe) move "value" and "1" / else, to an actual constant?
This comment has been minimized.
This comment has been minimized.
jleandroperez
Feb 2, 2017
Contributor
(Common practise looks something like this):
class X {
struct Constants {
static let publicizeValyeKey = "value"
// ...
}
}
This comment has been minimized.
This comment has been minimized.
|
|
||
| if (!NSDictionary(dictionary: disabledPublicizeConnections ?? [:]) | ||
| .isEqual(to: originalPost.disabledPublicizeConnections ?? [:])) { | ||
| return true |
This comment has been minimized.
This comment has been minimized.
jleandroperez
Feb 2, 2017
Contributor
Can't Swift dictionaries be compared? (i mean, can we avoid the NSDictionary conversion?)
This comment has been minimized.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
elibud
Feb 6, 2017
Author
Contributor
Wouldn't that prevent detecting changes when disabledPublicizeConnections = nil ?
This comment has been minimized.
This comment has been minimized.
| 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.
This comment has been minimized.
| - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section | ||
| { | ||
| if ([self tableView:tableView numberOfRowsInSection:section] == 0) { | ||
| return 0.01f; |
This comment has been minimized.
This comment has been minimized.
| - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section | ||
| { | ||
| if ([self tableView:tableView numberOfRowsInSection:section] == 0) { | ||
| return 0.01f; |
This comment has been minimized.
This comment has been minimized.
| 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.
This comment has been minimized.
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.
This comment has been minimized.
| @@ -4,13 +4,16 @@ import CoreData | |||
| extension Post { | |||
|
|
|||
| @NSManaged var commentCount: NSNumber? | |||
| @NSManaged var disabledPublicizeConnections: [NSNumber: [String: String]]? | |||
This comment has been minimized.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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
}

elibud commentedJan 31, 2017
•
edited
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:
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
POST data to enable a connection
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).